Skip to content

Instantly share code, notes, and snippets.

@schmidt1024
Created June 3, 2015 13:59
Show Gist options
  • 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');
});
@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