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?
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?
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>
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));
}
};
});
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.
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
Related
I have some SVG files whereby one includes the other by using <use /> elements. To merge all those files into one resulting one, I have written some code that transforms ./main.svg from below:
#file: ./svg/shared.svg
<svg>
<image xlink:href="../img/bg.png" id="bg" />
</svg>
#file: ./svg/index.svg
<svg id="index">
<use xlink:href="./shared.svg#bg" />
</svg>
#file: ./main.svg
<svg>
<use xlink:href="./svg/index.svg#index" />
</svg>
into:
<svg>
<defs>
<svg id="svg-index">
<defs>
<image xlink:href="./img/bg.png" id="img-bg" />
</defs>
<use xlink:href="#img-bg" />
</svg>
</defs>
<use xlink:href="#svg-index" />
</svg>
So basically all urls and Ids are resolved and referenced nodes are imported. The script works fine, in Firefox and the result works as well in Inkscape. BUT in chromium the images do not show up. They are loaded, what I can see from the networking tab.
If I modify the images' xlink:href (inspector panel) by adding a little #qwertz at the end of one image, all of them show up, so that seams to be browser related, since the result works in FF and Inkscape.
Any Idea how to fix that?
UPDATE
One thing that may have some effect here is whether the manipulated SVG is already inserted into the dom or not. So this code makes the images appear:
setTimeout(function () {
[...document.querySelectorAll('image')].forEach(
img => img.setAttribute('xlink:href',
img.getAttribute('xlink:href')
)
);
}, 0)
But that feels really hackish and I am really interested in a »cleaner« solution here…
So I have a single SVG file that holds a collection of different paths:
<!-- icons.svg -->
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<path id="foo" .../>
<path id="bar" .../>
...
</svg>
and within my webpages, I use these SVG icons:
<!-- index.html -->
<svg viewBox="0 0 256 256">
<use xlink:href="icons.svg#foo">
</svg>
and this works just fine and exactly how I intend -- we select the SVG with the id foo from the icons.svg file.
Now, if I try to give the xlink:href attribute for the <use> tag an _absolute_ path to my icons.svg file, it fails and returns nothing and seems to be unable to find the file..
<svg viewBox="0 0 256 256">
<use xlink:href="http://localhost:8080/icons.svg#foo">
</svg>
It's important to note that the absolute path is correct.
The documentation on xlink seems to suggest that absolute paths are valid values, which makes me wonder why it doesn't work here for me -- am I missing something?
Is there an alternative approach I should be considering? Is this currently not the approach I want to take to achieve something like this?
You have to be sure you are loading your external svg file from the same protocol and port, otherwise, browsers will block the request, according to the same-origin policy.
Two pages have the same origin if the protocol, port (if one is specified), and host are the same for both pages.
(emphasize mine)
I'm trying to hack together a sprite sheet from a set of icons. I know almost nothing about SVG. I can get the simple icons to work, but an icon with a clip path isn't displaying properly. From what I can tell it seems like it's not using the clip path.
The sprite works in jsfilddle and it works if I just load the svg on it's own and include a < use > statement in the SVG. But if I have a separate < use > it doesn't work.
All my testing has been done in Chrome (50.0.2661.94)
<svg xmlns:xlink="http://www.w3.org/1999/xlink" xmlns="http://www.w3.org/2000/svg" version="1.1">
<defs>
<clipPath id="folder-clip-0">
<path d="..." />
</clipPath>
<symbol id="folder" viewBox="0 0 32 32">
<g class="container" data-width="32" data-height="27" transform="translate(0 2)">
<path d="..." class="..." />
<path class="..." d="..." />
<path clip-path="url(#folder-clip-0)" d="..." class="..." />
</g>
</symbol>
</defs>
</svg>
I'm using it like so:
<svg>
<use
xmlns:xlink="http://www.w3.org/1999/xlink"
xlink:href="/img/path/sprite.svg#folder">
</use>
</svg>
When I use the separate statement it looks like this:
But it should look like this:
The color difference is not relevant, it's just the background when the image was taken.
Edit:
I just discovered that if I dump the whole sprite sheet into the page HTML and reference it locally instead of an external file it works. So I don't know what's wrong with my external reference.
e.g.
<svg>
<use xlinkHref={"/img/path/not/work/sprite.svg#folder"}></use>
</svg>
vs.
<svg>
<symbol id="folder"></symbol>
</svg>
<svg>
<use xlinkHref={"#folder"}></use>
</svg>
This works for me as a fallback, but I'd rather have an external SVG file instead of embedding it in my HTML.
Edit 2:
If the SVG sprite sheet is embeded in the HTML directly using the external link shows the icon correctly.
This seems to be a browser support issue. Using the external reference works as expected in Firefox. Chrome doesn't handle clip paths and some other functions in external references. There's an outstanding bug report filed. Safari also doesn't support it.
Related StackOverflow ticket: Why can't I reference an SVG linear gradient defined in an external file (paint server)?
Open bugs:
https://code.google.com/p/chromium/issues/detail?id=109212
https://bugs.webkit.org/show_bug.cgi?id=105904
I'm trying to define my iconset-svg using a symbol tag instead of a g tag but it isn't working as the icon isn't rendered. If I define some icon using it works.
The code:
<iron-iconset-svg name="br-icons">
<svg style="display: none">
<defs>
<symbol id="icon-menu" viewBox="0 0 1024 1024">
<title>menu</title>
<path class="path1" d="M64 192h896v192h-896zM64 448h896v192h-896zM64 704h896v192h-896z"></path>
</symbol>
</defs>
</svg>
</iron-iconset-svg>
I am using it like this:
<iron-icon icon="br-icons:icon-menu"></iron-icon>
Does anyone know how I can get this working?
The response I got from the Polymer team was "you need to use a <use> element with a <symbol>, which doesn't let you style it". So I think for now you'll want to stick with <g>. Apologies if I'm mistaken here, I am not an SVG expert by any means :)
Safari browser (I'm testing under windows) seems having problem in displaying Svg Image element.
<!DOCTYPE html>
<html>
<body>
<h1>My first SVG</h1>
<img src="image.svg" />
</body>
</html>
And here is the content of the image.svg:
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink">
<rect x="50" y="50" width="100" height="100" style="fill:blue"></rect>
<rect id="foo" x="50" y="150" width="500" height="500" style="fill:green"></rect>
<image x="50" y="10" width="200" height="200" xlink:href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAAHElEQVQI12P4//8/w38GIAXDIBKE0DHxgljNBAAO9TXL0Y4OHwAAAABJRU5ErkJggg=="></image>
</svg>
Is there any solution or workaround to make this work in Safari?
In the <image> tag inside the svg element, href works fine in Chrome. To work in older versions of Safari, you need xlink:href. (Also applies to the <use> tag.) Keep in mind xlink:href is deprecated and is being replaced by href. However, it was not supported until Safari 12.
https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/href
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" ...>
<image href="data..." xlink:href="data...">
</svg>
I think there are two problems here:
You haven't said anything about how large your SVG image is. As a rule, you should at least include a viewBox attribute in the <svg> tag. For example:
<svg width="250" height="250" viewBox="0 0 250 250" ... >
The other problem is that Safari isn't particularly brilliant at rendering SVGs. However, it tends to do better when you embed them with an <iframe> or <object> tag instead of using <img>. For example:
<object data="image.svg" type="image/svg+xml"></object>
Also, make sure your server is delivering SVG content with the correct MIME type (Content-Type: image/svg+xml), as this can cause problems too.
So give this a try:
HTML source:
<!DOCTYPE html>
<html>
<body>
<h1>My first SVG</h1>
<object data="image.svg" type="image/svg+xml"></object>
</body>
</html>
image.svg:
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg width="250" height="250" viewBox="0 0 250 250"
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink">
<rect x="50" y="50" width="100" height="100" style="fill:blue"></rect>
<rect id="foo" x="50" y="150" width="500" height="500" style="fill:green"></rect>
<image x="50" y="10" width="200" height="200" xlink:href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAAHElEQVQI12P4//8/w38GIAXDIBKE0DHxgljNBAAO9TXL0Y4OHwAAAABJRU5ErkJggg=="></image>
</svg>
Be sure to supply your <svg> tag with a height, width, and view box like so. Safari doesn't like it when height or width is set to auto for some reason. ⤵︎
<svg height="16px" width="16px" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100"><path d="M60......"></path></svg>
I just discovered this same problem when I checked an app I was working on in Safari, after having been using Chrome most of the time. I wrote this bit of TypeScript/jQuery code (easily enough turned into plain JavaScript) to solve the problem:
export function setSvgHref(elem: JQuery, href: string) {
elem.attr('href', href);
if (isSafari()) {
elem.each(function() {
this.setAttributeNS('http://www.w3.org/1999/xlink', 'href', href);
});
}
}
In my case, the problem was related to <mask> tags in svg.
I deleted the following line in my svg component and it started to work on Safari.
<mask id="y9iogdw0wd" fill="#fff">
<use xlink:href="#jz8vxv0q6c"/>
</mask>
I had a problem with a wordmark (text that I use as a logo) that I saved as a .svg file. It was on my page with a <img src="...svg"> but did not appear properly in Safari (current version as of July 2020). The SVG worked fine with FireFox, Chrome, and Brave.
I had created the SVG in Inkscape. I selected the entire object, then used Path -> Object to Path... and saved the resulting file.
This rendered properly in all four browsers. (I'm writing this here in case I have this problem again: it'll tell me what I did to fix it.)