Skip to content

Instantly share code, notes, and snippets.

@schmidt1024
Created June 3, 2015 13:59
Show Gist options
  • Star 44 You must be signed in to star a gist
  • Fork 11 You must be signed in to fork a gist
  • Save schmidt1024/61beeca2cce94a70c9df to your computer and use it in GitHub Desktop.
Save schmidt1024/61beeca2cce94a70c9df to your computer and use it in GitHub Desktop.
Replace all SVG images with inline SVG using jQuery
/*
* Replace all SVG images with inline SVG
*/
jQuery('img.svg').each(function(){
var $img = jQuery(this);
var imgID = $img.attr('id');
var imgClass = $img.attr('class');
var imgURL = $img.attr('src');
jQuery.get(imgURL, function(data) {
// Get the SVG tag, ignore the rest
var $svg = jQuery(data).find('svg');
// Add replaced image's ID to the new SVG
if(typeof imgID !== 'undefined') {
$svg = $svg.attr('id', imgID);
}
// Add replaced image's classes to the new SVG
if(typeof imgClass !== 'undefined') {
$svg = $svg.attr('class', imgClass+' replaced-svg');
}
// Remove any invalid XML tags as per http://validator.w3.org
$svg = $svg.removeAttr('xmlns:a');
// Replace image with new SVG
$img.replaceWith($svg);
}, 'xml');
});
@infostreams
Copy link

Suggested improvement:

    jQuery('img').filter(function() {
        return this.src.match(/.*\.svg$/);
    }).each(function(){

instead of

    jQuery('img.svg').each(function(){

so that you don't have to give the SVG images a separate class just to be able to select them.

@toph-goes-up
Copy link

toph-goes-up commented Oct 6, 2016

Also, adding in this snippet will pull its declared bounds from the source and use them for the viewBox attribute, making it scale-friendly with CSS width and height:

if(!$svg.attr('viewBox')){
                $svg.attr('viewBox', ('0 0 '
                  + $svg.attr('width').match(/[0-9]+\.[0-9]*/) + ' '
                  + $svg.attr('height').match(/[0-9]+\.[0-9]*/)));
               }

@VinceVachon
Copy link

Great code btw, really useful.
If you need it in ES6:

   $('img.svg').each((i, e) => {

    const $img = $(e);

    const imgID = $img.attr('id');

    const imgClass = $img.attr('class');

    const imgURL = $img.attr('src');

    $.get(imgURL, (data) => {
        // Get the SVG tag, ignore the rest
        let $svg = $(data).find('svg');

        // Add replaced image's ID to the new SVG
        if (typeof imgID !== 'undefined') {
            $svg = $svg.attr('id', imgID);
        }
        // Add replaced image's classes to the new SVG
        if (typeof imgClass !== 'undefined') {
            $svg = $svg.attr('class', `${imgClass}replaced-svg`);
        }

        // Remove any invalid XML tags as per http://validator.w3.org
        $svg = $svg.removeAttr('xmlns:a');

        // Check if the viewport is set, if the viewport is not set the SVG wont't scale.
        if (!$svg.attr('viewBox') && $svg.attr('height') && $svg.attr('width')) {
            $svg.attr(`viewBox 0 0  ${$svg.attr('height')} ${$svg.attr('width')}`);
        }

        // Replace image with new SVG
        $img.replaceWith($svg);
    }, 'xml');
});

@mrtag23
Copy link

mrtag23 commented Jan 16, 2017

just in case if somebody experiences problems in Internet Explorer, and gets this issue:

End-tag name does not match the corresponding start-tag name

just use javascript object instead of jQuery one here var imgURL = $img.attr('src');
so it looks smth like this var imgURL = $img[0].src;

@jaykhatriwebdesigner
Copy link

Hello I have used your code
there are issue its not working in Chrome wil you please help me on it
Thank you.

@patilkomal
Copy link

hello...this code is working finely but i want it to run on second page dynamicaly of my project in ionic2

@z1haze
Copy link

z1haze commented Apr 19, 2018

wrote this in plain js for anyone who wants it.

document.querySelectorAll('img.svg').forEach((el) => {
    const imgID = el.getAttribute('id');
    const imgClass = el.getAttribute('class');
    const imgURL = el.getAttribute('src');

    request({url: imgURL}).then((data) => {
      const parser = new DOMParser();
      const xmlDoc = parser.parseFromString(data, 'text/html');
      let svg = xmlDoc.querySelector('svg');

      if (typeof imgID !== 'undefined') {
        svg.setAttribute('id', imgID);
      }

      if(typeof imgClass !== 'undefined') {
        svg.setAttribute('class', imgClass + ' replaced-svg');
      }

      svg.removeAttribute('xmlns:a');

      el.parentNode.replaceChild(svg, el);
    })
  });

@engray
Copy link

engray commented May 29, 2018

@z1haze sadly it didn't work for me. I get Uncaught ReferenceError: request is not defined at document.querySelectorAll.forEach

@Third-Hyperion
Copy link

Is there anyway to get this to work with external url sources? I have my images pulled from an external subdomain and for some reason this does not work when using an external URL. Is it possible so that either local or external url source can be used? Thank you.

@ndq11
Copy link

ndq11 commented Jun 20, 2018

I have an issue on FF:
on ajax call browser throws XML Parsing Error: not well-formed.
I've already tried jQuery ajax call (instead of jQuery.get) with predefined headers, dataType, contentType and overriding mimeType, but it doesn't work either.
Server returns image with the same Content-Type: "image/svg+xml;charset=UTF-8".
Image is loaded.
But with parsing error.
This problem occurs only on FF.
Maybe somebody will have any idea?

$.ajax({ url: imgURL, contentType: "image/svg+xml; charset=UTF-8", headers: { Accept: "image/svg+xml; charset=UTF-8" }, success: success, dataType: 'xml', beforeSend: function (xhr) { xhr.overrideMimeType("image/svg+xml; charset=UTF-8"); } });

@nikitatrifan
Copy link

nikitatrifan commented Jun 30, 2018

Hi guys! For one of my projects which based on create react app I had to use SVG. And I did not want to eject webpack settings.

So I did a new implementation of the idea: Replace image with inline SVG using React.
Check this out:

import React from 'react'
import PropTypes from 'prop-types'
import { isEmpty } from 'lodash'

export default class Svg extends React.Component {
    static propTypes = {
        src: PropTypes.string.isRequired,
        className: PropTypes.string,
        alt: PropTypes.string
    };

    static defaultProps = {
        alt: ''
    };

    storageName = '@storage/svg';
    constructor(props) {
        super(props);
        // set up initial data
        const { src } = props;
        const markup = this.storage(src);
        const isLoaded = !isEmpty(markup);

        this.state = {
            markup, isLoaded
        };
        
        // if we have no image in a cache 
        // then load svg
        if (!isLoaded)
            this.loadSvg(src);
    }
    
    storage = (src, markup) => {
        const storage = JSON.parse(localStorage.getItem(this.storageName)) || {};
        
        if (isEmpty(markup))
            return storage[src];

        localStorage.setItem(this.storageName, JSON.stringify({
            ...storage,
            [src]: markup
        }))
    };

    loadSvg = src => {
        fetch(src)
            .then(res => res.text())
            .then(markup => {
                // save svg markup to local storage
                this.storage(src, markup);
                // show svg :)
                this.setState({
                    markup, isLoaded: true
                })
            })
            .catch(err => {
                console.warn(
                    `Can't fetch svg image ${src} because of: \n\n${err.toString()}`
                )
            })
    };

    render() {
        const { src, alt = '', ...props } = this.props;
        const { isLoaded, markup } = this.state;
        if (isLoaded) {
            return (
                <div {...props} dangerouslySetInnerHTML={{ __html: markup }}/>
            )
        }
        return (
            <img src={src} alt={alt} {...props}/>
        )
    }
}

@bondo11
Copy link

bondo11 commented Oct 26, 2018

and here is my take on it, in typescript, where it is unittestable
use it like this:
new InlineSvg().execute();

and add the class "svg-inline", to the svgs you want to inline

export default class InlineSvg {
    private imageElements: HTMLImageElement[];
    private svgElements: HTMLImageElement[];
    private classToReplace: string = "svg-inline";

    constructor() {
        this.imageElements = Array.from(document.querySelectorAll<HTMLImageElement>("img")) as HTMLImageElement[];
        this.svgElements = this.imageElements.filter((x: HTMLImageElement) =>x.classList.contains(this.classToReplace));
    }

    execute(): void {
        this.svgElements.forEach((x: HTMLImageElement) => {
            this.getSvg(x, x.src);
        });
    }

    replace(source: HTMLImageElement, target: HTMLElement): void {
        // add replaced image's ID to the new SVG
        if(typeof source.id !== "undefined") {
            target.id = source.id;
        }

        // add replaced image's classes to the new SVG
        source.classList.forEach((x: string) => {
            if(x===this.classToReplace) { return; }
            target.classList.add(x);
        });

        target.classList.add("replaced-svg");

        // remove any invalid XML tags as per http://validator.w3.org
        target.removeAttribute("xmlns:a");

        // replace image with new SVG
        source.replaceWith(target);
    }

    getSvg(source: HTMLImageElement, url: string): void {
        const http: XMLHttpRequest = new XMLHttpRequest();
        http.open("GET", url, true);
        http.setRequestHeader("Content-Type", "text/xml; charset=UTF-8");
        http.onload = () => {
            if(http.readyState === http.DONE && http.status === 200) {
                const target: HTMLElement = http.responseXML.documentElement;
                this.replace(source, target);
            }
        };
        http.send(null);
    }
}

@danielgroner
Copy link

Another simple but important addition could be to also get the replaced image's alt tag.

var imgAlt = $img.attr('alt');

// add replaced image's alt tag to the new SVG
if(typeof imgAlt !== 'undefined') {
        $svg = $svg.attr('alt', imgAlt);
}

@usame-algan
Copy link

usame-algan commented Nov 12, 2019

@z1haze @engray I've adjusted the plain Javascript version to work with fetch.

document.querySelectorAll('img.svg').forEach((el) => {
      const imgID = el.getAttribute('id');
      const imgClass = el.getAttribute('class');
      const imgURL = el.getAttribute('src');

      fetch(imgURL)
          .then(data => data.text())
          .then(response => {
                const parser = new DOMParser();
                const xmlDoc = parser.parseFromString(response, 'text/html');
                let svg = xmlDoc.querySelector('svg');

                if (typeof imgID !== 'undefined') {
                    svg.setAttribute('id', imgID);
                }

                if(typeof imgClass !== 'undefined') {
                    svg.setAttribute('class', imgClass + ' replaced-svg');
                }

                svg.removeAttribute('xmlns:a');

                el.parentNode.replaceChild(svg, el);
          })
});

@amohamdy
Copy link

amohamdy commented Jan 2, 2020

this code doesn't work on internet explorer any solution for this issue??

@subfighter3
Copy link

Great code btw, really useful.
If you need it in ES6:

   $('img.svg').each((i, e) => {

    const $img = $(e);

    const imgID = $img.attr('id');

    const imgClass = $img.attr('class');

    const imgURL = $img.attr('src');

    $.get(imgURL, (data) => {
        // Get the SVG tag, ignore the rest
        let $svg = $(data).find('svg');

        // Add replaced image's ID to the new SVG
        if (typeof imgID !== 'undefined') {
            $svg = $svg.attr('id', imgID);
        }
        // Add replaced image's classes to the new SVG
        if (typeof imgClass !== 'undefined') {
            $svg = $svg.attr('class', `${imgClass}replaced-svg`);
        }

        // Remove any invalid XML tags as per http://validator.w3.org
        $svg = $svg.removeAttr('xmlns:a');

        // Check if the viewport is set, if the viewport is not set the SVG wont't scale.
        if (!$svg.attr('viewBox') && $svg.attr('height') && $svg.attr('width')) {
            $svg.attr(`viewBox 0 0  ${$svg.attr('height')} ${$svg.attr('width')}`);
        }

        // Replace image with new SVG
        $img.replaceWith($svg);
    }, 'xml');
});

Thank you @VinceVachon, I found a small error here:
$svg = $svg.attr("class", ${imgClass}replaced-svg);

I added a space between the original classes and "replaced-img" new class:
$svg = $svg.attr("class", ${imgClass} replaced-svg);

While the plain JS version provided by @usame-algan gives me an error: "fetch(...) is undefined" and is not working for me...

Anyway thank everybody for contributing on this useful tool!

@panatapattu
Copy link

This is a great solution. Is there any way to use this with owl carousel? When new carousel items loading, those are not rendering to SVGs.

@Warface
Copy link

Warface commented Jan 19, 2023

this code doesn't work on internet explorer any solution for this issue??

Yeah don't use Internet Explorer ;)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment