35

I came accross a strange behaviour when using SVG with AngularJS. I'm using the $routeProvider service to configure my routes. When I put this simple SVG in my templates, everything is fine:

<div id="my-template">
    <svg xmlns="http://www.w3.org/2000/svg" version="1.1">
        <rect fill="red" height="200" width="300" />
    </svg>
    // ...
</div>

But when I add a filter, with this code for instance:

<div id="my-template">
    <svg xmlns="http://www.w3.org/2000/svg" version="1.1">
        <defs>
            <filter id="blurred">
                <feGaussianBlur stdDeviation="5"/>
            </filter>
        </defs>
        <rect style="filter:url(#blurred)" fill="red" height="200" width="300" />
    </svg>
</div>

Then:

  • It works on my homepage.
  • With Firefox, the SVG isn't visible anymore on the other pages, but it still leaves space where it would have been. With Chrome, the SVG is visible, but not blurred at all.
  • The SVG is visible again when I remove manually (with Firebug) the filter style.

Here is the routes configuration:

$routeProvider
    .when('/site/other-page/', {
            templateUrl : 'view/Site/OtherPage.html',
            controller : 'Site.OtherPage'
    })
    .when('/', {
            templateUrl : 'view/Site/Home.html',
            controller : 'Site.Home'
    })
    .otherwise({
        redirectTo : '/'
    })
;

Fiddle

Please notice that I've failed to reproduce the problem with Chrome in a Fiddle, although it "works" with Firefox.

I've tried to no avail to create my whole SVG with document.createElementNS().

Does someone has an idea of what is happening?

5
  • 1
    Is it a browser specific issue? Does it work with Chrome? Commented Nov 2, 2013 at 14:20
  • I think what is happening is that angularjs is applying the filter operator to the svg element and because url(#blurred) evaluates to false in an angularjs context it hids the <rect> element
    – fabmilo
    Commented Nov 2, 2013 at 14:24
  • @musically_ut I've edited my question: in Chrome, the rect is visible but not blurred.
    – Blackhole
    Commented Nov 2, 2013 at 14:26
  • @fabrizioM Sounds a bit strange… Why not on the homepage, then?
    – Blackhole
    Commented Nov 2, 2013 at 14:29
  • why aren't you using the normal filter syntax <rect filter="url(#blurred)"> ? Commented Nov 3, 2013 at 0:12

5 Answers 5

72

The problem

The problem is that there is a <base> tag in my HTML page. Therefore, the IRI used to identify the filter is not anymore relative to the current page, but to the URL indicated in the <base> tag.

This URL was also the URL of my home page, http://example.com/my-folder/ for instance.

For the pages other than the home page, http://example.com/my-folder/site/other-page/ for example, #blurred was computed to the absolute URL http://example.com/my-folder/#blurred. But for a simple GET request, without JavaScript, and therefore without AngularJS, this is simply my base page, with no template loaded. Thus, the #blurred filter doesn't exist on this pages.

In such cases, Firefox doesn't render the <rect> (which is the normal behaviour, see the W3C recommandation). Chrome simply doesn't apply the filter.

For the home page, #blurred is also computed to the absolute URL http://example.com/my-folder/#blurred. But this time, this is also the current URL. There is no need to send a GET request, and thus the #blurred filter exists.

I should have seen the additional request to http://example.com/my-folder/, but in my defense, it was lost in a plethora of other requests to JavaScript files.

The solution

If the <base> tag is mandatory, the solution is to use an absolute IRI to identify the filter. With the help of AngularJS, this is pretty simple. In the controller or in the directive that is linked to the SVG, inject the $location service and use the absUrl() getter:

$scope.absUrl = $location.absUrl();

Now, in the SVG, just use this property:

<svg xmlns="http://www.w3.org/2000/svg" version="1.1">
    <defs>
        <filter id="blurred">
            <feGaussianBlur stdDeviation="5"/>
        </filter>
    </defs>
    <rect style="filter:url({{absUrl}}#blurred)" fill="red" height="200" width="300" />
</svg>

Related: SVG Gradient turns black when there is a BASE tag in HTML page?

4
  • Thank you for sharing your finding. I upvote it as it will probably soon help me :)
    – Rémi
    Commented Nov 3, 2013 at 17:33
  • Works well on Chrome and Firefox, but it doesn't on IE11. Funny thing is, it works just fine on IE11 without the fix. Weird.
    – alans
    Commented Apr 7, 2016 at 17:25
  • if using Angular2, there is a better solution to remove the base tag stackoverflow.com/a/34535256/3218806
    – maxbellec
    Commented Aug 1, 2016 at 7:19
  • dynamically created filters or gradient is not working due to angular routing
    – Kira
    Commented Jun 2, 2017 at 12:52
1

It looks like a behaviour I observed before. The root cause is that you end up having multiple elements (filters) with the same id (blurred). Different browsers handle it differently...

Here is what I did to reproduce your case: http://jsfiddle.net/z5cwZ/ It has two svg and one is hidden, firefox shows none.

There are two possibilities to avoid conflicting ids. First you can generate unique ids from your template (I can't help in doing it with angularjs tough). Here is an example: http://jsfiddle.net/KbCLB/1/

Second possibility, and it may be easier with angularjs, is to put the filter outside of the individual svgs (http://jsfiddle.net/zAbgr/1/):

<svg xmlns="http://www.w3.org/2000/svg" version="1.1" width="0" height="0">
    <defs>
        <filter id="blurred">
            <feGaussianBlur stdDeviation="10" />
        </filter>
    </defs>
</svg>
<svg xmlns="http://www.w3.org/2000/svg" version="1.1" style="display:none">
    <rect style="filter:url(#blurred)" fill="red" height="200" width="300" />
</svg>
<br/>
<svg xmlns="http://www.w3.org/2000/svg" version="1.1">
    <rect style="filter:url(#blurred)" fill="red" height="200" width="300" />
</svg>
9
  • The problem is not here ;-). When I change the page, the previous filter, with the ID, is removed by Angular, as the rest of the page. Furthermore, if I name differently the filter on each page, that doesn't solve the problem.
    – Blackhole
    Commented Nov 2, 2013 at 15:32
  • I didn't mean that when you change page the old elements remain. I mean that you have a id="blurred" in your template and in all elements generated from your template (on a given page). I'd be interested to know if my second solution solves the problem (putting the filter outside the template and using it in the template).
    – Rémi
    Commented Nov 2, 2013 at 15:44
  • Alas, no evolution with your proposal…
    – Blackhole
    Commented Nov 2, 2013 at 18:30
  • Ok, thanks. Too bad :( Can you copy your DOM (with firefox, open the inspector, right click the html tag and do "copy outer HTML") and share it so we can have a look?
    – Rémi
    Commented Nov 2, 2013 at 19:51
  • That's the one I've past in my question. And that's one of the strangest thing with this problem: the DOM, the properties of the elements, … It seems like everything is the same between the two pages!
    – Blackhole
    Commented Nov 2, 2013 at 19:58
0

Just ran into this problem. Expanding from @Blackhole's answer, my solution was to add a directive that changes the value of every fill attribute.

angular.module('myApp').directive('fill', function(
    $location
) { 'use strict';
    var absUrl = 'url(' + $location.absUrl() + '#';

    return {
        restrict: 'A',
        link: function($scope, $element, $attrs) {
            $attrs.$set('fill', $attrs.fill.replace(/url\(#/g, absUrl));
        }
    };
});
0

I recently ran into this issue myself, if the svg is used on different routes you will have to add the $location to each path. A better way to implement this is to just use a window.location.href instead of the $location service.

0

Also had issues with svg link:hrefs, and yes the problem is because of the <base> - I had errors being thrown when concatenating the absolute url because of the Strict Contextual Escaping restrictions.

My solution was to write a filter that added the absolute url and also trusted the concatenation.

angular.module('myApp').filter('absUrl', ['$location', '$sce', function ($location, $sce) {
    return function (id) {
        return $sce.trustAsResourceUrl($location.absUrl() + id);
    };
}]);

And then use it like this: {{'#SVGID_67_' | absUrl}

Result : http://www.example.com/deep/url/to/page#SVGID_67_ and no $sce errors

Not the answer you're looking for? Browse other questions tagged or ask your own question.