Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
Fixes SVG Gradients for Safari by changing fill URLs to the current pathname. Also fixes xlink:href URLs.

Snippet to fix SVG issues in Angular (2+) with Safari, Firefox and Chrome

This listens to Angular route changes and on route change does the following:

  1. Replaces the link in <use xlink:href="#some-id"></use> by a path prefixed version
  2. Replaces the fill property in referenced SVGs by a path prefixed version
  3. Replaces the style in <svg style="fill: url(#gradient)"> with a prefixed version

Adapted from Gist by Leon Derijke

ngOnInit() {
this.router.events
.pipe(
filter(x => x instanceof NavigationEnd),
takeUntil(this.destroy$)
)
.subscribe((evt: NavigationEnd) => {
this.zone.runOutsideAngular(() => {
setTimeout(() => {
if (!SERVER) { // you should disable this for HMR
setTimeout(() => {
this.changeSvgPaths();
}, 50);
}
}, 0);
});
});
}
/**
* SVG Fixer
*
* Fixes references to inline SVG elements when the <base> tag is in use.
* Firefox won't display SVG icons referenced with
* `<svg><use xlink:href="#id-of-icon-def"></use></svg>` when the <base> tag is on the page.
*
* More info:
* - http://stackoverflow.com/a/18265336/796152
* - http://www.w3.org/TR/SVG/linking.html
*
* One would think that setting the `xml:base` attribute fixes things,
* but that is being removed from the platform: https://code.google.com/p/chromium/issues/detail?id=341854
* https://gist.github.com/leonderijke/c5cf7c5b2e424c0061d2
*/
private changeSvgPaths() {
/**
* Current URL, without the hash
*/
let baseUrl = window.location.pathname
.replace(window.location.hash, '')
.replace(/\/$/gi, '');
/**
* Find all `use` elements with a namespaced `href` attribute, e.g.
* <use xlink:href="#some-id"></use>
*
*/
[].slice
.call(document.querySelectorAll('use[*|href]'))
/**
* Filter out all elements whose namespaced `href` attribute doesn't
* start with `#` (i.e. all non-relative IRI's)
*
* Note: we're assuming the `xlink` prefix for the XLink namespace!
*/
.filter(function(element) {
return (
element
.getAttributeNS('http://www.w3.org/1999/xlink', 'href')
.indexOf('#') !== -1
);
})
/**
* Prepend `window.location` to the namespaced `href` attribute value,
* in order to make it an absolute IRI
*
* Note: we're assuming the `xlink` prefix for the XLink namespace!
*/
.forEach(function(element: HTMLElement) {
const oldHref = element.getAttributeNS(
'http://www.w3.org/1999/xlink',
'href'
) as string;
const idPart = oldHref.slice(oldHref.indexOf('#'));
element.setAttributeNS(
'http://www.w3.org/1999/xlink',
'xlink:href',
baseUrl + idPart
);
});
[].slice
.call(document.querySelectorAll('svg'))
.filter(function(element: SVGElement) {
return (
'fill' in element.style && element.style.fill.indexOf('url(') !== -1
);
})
.forEach(function(element: SVGElement) {
let attrVal = element.style.fill;
element.style.fill = `url(${baseUrl}${attrVal.slice(
attrVal.indexOf('#')
)}`;
});
[].slice
.call(document.querySelectorAll('*[fill]'))
/**
* Filter out all elements whose namespaced `fill` attributes doesn't
* which doesnt have cross referenced values
*
* Note: we're assuming the `url(` prefix for the cross referenced fills !
*/
.filter(function(element) {
return element.getAttribute('fill').indexOf('url(') !== -1;
})
/**
* Insert `window.location` to the `fill` attribute value,
* in order to make it an absolute IRI
*
*/
.forEach(function(element) {
let attrVal = element.getAttribute('fill');
element.setAttribute(
'fill',
`url(${baseUrl}${attrVal.slice(attrVal.indexOf('#'))}`
);
});
}
@yacineblr

This comment has been minimized.

Copy link

@yacineblr yacineblr commented Aug 8, 2019

I'ts work for Angular 8, thanks you !!!!

@H4ad

This comment has been minimized.

Copy link

@H4ad H4ad commented Aug 5, 2020

If anyone use ion-icon, this can resolve the bug for Shadow Root.

[].slice
          .call(document.querySelectorAll('ion-icon'))
          .map(node => node.shadowRoot)
          .map(root => root.querySelectorAll('*[fill]'))
          .reduce((acc, nodes) => [...acc, ...[].slice.call(nodes)])
          .filter(function(element) {
            return element.getAttribute('fill').indexOf('url(') !== -1;
          })
          .forEach(function(element) {
            let attrVal = element.getAttribute('fill');

            element.setAttribute(
              'fill',
              `url(${baseUrl}${attrVal.slice(attrVal.indexOf('#'))}`
            );
          });
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.