Skip to content

Instantly share code, notes, and snippets.

@ValentinFunk
Created January 18, 2019 09:58
  • Star 5 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
Star You must be signed in to star a gist
Save ValentinFunk/c84cdc175209d1a30f711abd6a81d472 to your computer and use it in GitHub Desktop.
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
Copy link

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

@H4ad
Copy link

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