123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450 |
- /**
- * SVGInjector v1.1.3-RC - Fast, caching, dynamic inline SVG DOM injection library
- * https://github.com/iconic/SVGInjector
- *
- * Copyright (c) 2014 Waybury <hello@waybury.com>
- * @license MIT
- */
-
- (function (window, document) {
-
- 'use strict';
-
- // Environment
- var isLocal = window.location.protocol === 'file:';
- var hasSvgSupport = document.implementation.hasFeature('http://www.w3.org/TR/SVG11/feature#BasicStructure', '1.1');
-
- function uniqueClasses(list) {
- list = list.split(' ');
-
- var hash = {};
- var i = list.length;
- var out = [];
-
- while (i--) {
- if (!hash.hasOwnProperty(list[i])) {
- hash[list[i]] = 1;
- out.unshift(list[i]);
- }
- }
-
- return out.join(' ');
- }
-
- /**
- * cache (or polyfill for <= IE8) Array.forEach()
- * source: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/forEach
- */
- var forEach = Array.prototype.forEach || function (fn, scope) {
- if (this === void 0 || this === null || typeof fn !== 'function') {
- throw new TypeError();
- }
-
- /* jshint bitwise: false */
- var i, len = this.length >>> 0;
- /* jshint bitwise: true */
-
- for (i = 0; i < len; ++i) {
- if (i in this) {
- fn.call(scope, this[i], i, this);
- }
- }
- };
-
- // SVG Cache
- var svgCache = {};
-
- var injectCount = 0;
- var injectedElements = [];
-
- // Request Queue
- var requestQueue = [];
-
- // Script running status
- var ranScripts = {};
-
- var cloneSvg = function (sourceSvg) {
- return sourceSvg.cloneNode(true);
- };
-
- var queueRequest = function (url, callback) {
- requestQueue[url] = requestQueue[url] || [];
- requestQueue[url].push(callback);
- };
-
- var processRequestQueue = function (url) {
- for (var i = 0, len = requestQueue[url].length; i < len; i++) {
- // Make these calls async so we avoid blocking the page/renderer
- /* jshint loopfunc: true */
- (function (index) {
- setTimeout(function () {
- requestQueue[url][index](cloneSvg(svgCache[url]));
- }, 0);
- })(i);
- /* jshint loopfunc: false */
- }
- };
-
- var loadSvg = function (url, callback) {
- if (svgCache[url] !== undefined) {
- if (svgCache[url] instanceof SVGSVGElement) {
- // We already have it in cache, so use it
- callback(cloneSvg(svgCache[url]));
- }
- else {
- // We don't have it in cache yet, but we are loading it, so queue this request
- queueRequest(url, callback);
- }
- }
- else {
-
- if (!window.XMLHttpRequest) {
- callback('Browser does not support XMLHttpRequest');
- return false;
- }
-
- // Seed the cache to indicate we are loading this URL already
- svgCache[url] = {};
- queueRequest(url, callback);
-
- var httpRequest = new XMLHttpRequest();
-
- httpRequest.onreadystatechange = function () {
- // readyState 4 = complete
- if (httpRequest.readyState === 4) {
-
- // Handle status
- if (httpRequest.status === 404 || httpRequest.responseXML === null) {
- callback('Unable to load SVG file: ' + url);
-
- if (isLocal) callback('Note: SVG injection ajax calls do not work locally without adjusting security setting in your browser. Or consider using a local webserver.');
-
- callback();
- return false;
- }
-
- // 200 success from server, or 0 when using file:// protocol locally
- if (httpRequest.status === 200 || (isLocal && httpRequest.status === 0)) {
-
- /* globals Document */
- if (httpRequest.responseXML instanceof Document) {
- // Cache it
- svgCache[url] = httpRequest.responseXML.documentElement;
- }
- /* globals -Document */
-
- // IE9 doesn't create a responseXML Document object from loaded SVG,
- // and throws a "DOM Exception: HIERARCHY_REQUEST_ERR (3)" error when injected.
- //
- // So, we'll just create our own manually via the DOMParser using
- // the the raw XML responseText.
- //
- // :NOTE: IE8 and older doesn't have DOMParser, but they can't do SVG either, so...
- else if (DOMParser && (DOMParser instanceof Function)) {
- var xmlDoc;
- try {
- var parser = new DOMParser();
- xmlDoc = parser.parseFromString(httpRequest.responseText, 'text/xml');
- }
- catch (e) {
- xmlDoc = undefined;
- }
-
- if (!xmlDoc || xmlDoc.getElementsByTagName('parsererror').length) {
- callback('Unable to parse SVG file: ' + url);
- return false;
- }
- else {
- // Cache it
- svgCache[url] = xmlDoc.documentElement;
- }
- }
-
- // We've loaded a new asset, so process any requests waiting for it
- processRequestQueue(url);
- }
- else {
- callback('There was a problem injecting the SVG: ' + httpRequest.status + ' ' + httpRequest.statusText);
- return false;
- }
- }
- };
-
- httpRequest.open('GET', url);
-
- // Treat and parse the response as XML, even if the
- // server sends us a different mimetype
- if (httpRequest.overrideMimeType) httpRequest.overrideMimeType('text/xml');
-
- httpRequest.send();
- }
- };
-
- // Inject a single element
- var injectElement = function (el, evalScripts, pngFallback, callback) {
-
- // Grab the src or data-src attribute
- var imgUrl = el.getAttribute('data-src') || el.getAttribute('src');
-
- // We can only inject SVG
- if (!(/\.svg/i).test(imgUrl)) {
- callback('Attempted to inject a file with a non-svg extension: ' + imgUrl);
- return;
- }
-
- // If we don't have SVG support try to fall back to a png,
- // either defined per-element via data-fallback or data-png,
- // or globally via the pngFallback directory setting
- if (!hasSvgSupport) {
- var perElementFallback = el.getAttribute('data-fallback') || el.getAttribute('data-png');
-
- // Per-element specific PNG fallback defined, so use that
- if (perElementFallback) {
- el.setAttribute('src', perElementFallback);
- callback(null);
- }
- // Global PNG fallback directoriy defined, use the same-named PNG
- else if (pngFallback) {
- el.setAttribute('src', pngFallback + '/' + imgUrl.split('/').pop().replace('.svg', '.png'));
- callback(null);
- }
- // um...
- else {
- callback('This browser does not support SVG and no PNG fallback was defined.');
- }
-
- return;
- }
-
- // Make sure we aren't already in the process of injecting this element to
- // avoid a race condition if multiple injections for the same element are run.
- // :NOTE: Using indexOf() only _after_ we check for SVG support and bail,
- // so no need for IE8 indexOf() polyfill
- if (injectedElements.indexOf(el) !== -1) {
- return;
- }
-
- // Remember the request to inject this element, in case other injection
- // calls are also trying to replace this element before we finish
- injectedElements.push(el);
-
- // Try to avoid loading the orginal image src if possible.
- el.setAttribute('src', '');
-
- // Load it up
- loadSvg(imgUrl, function (svg) {
-
- if (typeof svg === 'undefined' || typeof svg === 'string') {
- callback(svg);
- return false;
- }
-
- var imgId = el.getAttribute('id');
- if (imgId) {
- svg.setAttribute('id', imgId);
- }
-
- var imgTitle = el.getAttribute('title');
- if (imgTitle) {
- svg.setAttribute('title', imgTitle);
- }
-
- // Concat the SVG classes + 'injected-svg' + the img classes
- var classMerge = [].concat(svg.getAttribute('class') || [], 'injected-svg', el.getAttribute('class') || []).join(' ');
- svg.setAttribute('class', uniqueClasses(classMerge));
-
- var imgStyle = el.getAttribute('style');
- if (imgStyle) {
- svg.setAttribute('style', imgStyle);
- }
-
- // Copy all the data elements to the svg
- var imgData = [].filter.call(el.attributes, function (at) {
- return (/^data-\w[\w\-]*$/).test(at.name);
- });
- forEach.call(imgData, function (dataAttr) {
- if (dataAttr.name && dataAttr.value) {
- svg.setAttribute(dataAttr.name, dataAttr.value);
- }
- });
-
- // Make sure any internally referenced clipPath ids and their
- // clip-path references are unique.
- //
- // This addresses the issue of having multiple instances of the
- // same SVG on a page and only the first clipPath id is referenced.
- //
- // Browsers often shortcut the SVG Spec and don't use clipPaths
- // contained in parent elements that are hidden, so if you hide the first
- // SVG instance on the page, then all other instances lose their clipping.
- // Reference: https://bugzilla.mozilla.org/show_bug.cgi?id=376027
- var clipPaths = svg.querySelectorAll('defs clipPath[id]');
- var newClipPathName;
- for (var g = 0, clipPathsLen = clipPaths.length; g < clipPathsLen; g++) {
- newClipPathName = clipPaths[g].id + '-' + injectCount;
- // :NOTE: using a substring match attr selector here to deal with IE "adding extra quotes in url() attrs"
- var usingClipPath = svg.querySelectorAll('[clip-path*="' + clipPaths[g].id + '"]');
- for (var h = 0, usingClipPathLen = usingClipPath.length; h < usingClipPathLen; h++) {
- usingClipPath[h].setAttribute('clip-path', 'url(#' + newClipPathName + ')');
- }
- clipPaths[g].id = newClipPathName;
- }
-
- // Do the same for masks
- var masks = svg.querySelectorAll('defs mask[id]');
- var newMaskName;
- for (var i = 0, masksLen = masks.length; i < masksLen; i++) {
- newMaskName = masks[i].id + '-' + injectCount;
- // :NOTE: using a substring match attr selector here to deal with IE "adding extra quotes in url() attrs"
- var usingMask = svg.querySelectorAll('[mask*="' + masks[i].id + '"]');
- for (var j = 0, usingMaskLen = usingMask.length; j < usingMaskLen; j++) {
- usingMask[j].setAttribute('mask', 'url(#' + newMaskName + ')');
- }
- masks[i].id = newMaskName;
- }
-
- // Remove any unwanted/invalid namespaces that might have been added by SVG editing tools
- svg.removeAttribute('xmlns:a');
-
- // Post page load injected SVGs don't automatically have their script
- // elements run, so we'll need to make that happen, if requested
-
- // Find then prune the scripts
- var scripts = svg.querySelectorAll('script');
- var scriptsToEval = [];
- var script, scriptType;
-
- for (var k = 0, scriptsLen = scripts.length; k < scriptsLen; k++) {
- scriptType = scripts[k].getAttribute('type');
-
- // Only process javascript types.
- // SVG defaults to 'application/ecmascript' for unset types
- if (!scriptType || scriptType === 'application/ecmascript' || scriptType === 'application/javascript') {
-
- // innerText for IE, textContent for other browsers
- script = scripts[k].innerText || scripts[k].textContent;
-
- // Stash
- scriptsToEval.push(script);
-
- // Tidy up and remove the script element since we don't need it anymore
- svg.removeChild(scripts[k]);
- }
- }
-
- // Run/Eval the scripts if needed
- if (scriptsToEval.length > 0 && (evalScripts === 'always' || (evalScripts === 'once' && !ranScripts[imgUrl]))) {
- for (var l = 0, scriptsToEvalLen = scriptsToEval.length; l < scriptsToEvalLen; l++) {
-
- // :NOTE: Yup, this is a form of eval, but it is being used to eval code
- // the caller has explictely asked to be loaded, and the code is in a caller
- // defined SVG file... not raw user input.
- //
- // Also, the code is evaluated in a closure and not in the global scope.
- // If you need to put something in global scope, use 'window'
- new Function(scriptsToEval[l])(window); // jshint ignore:line
- }
-
- // Remember we already ran scripts for this svg
- ranScripts[imgUrl] = true;
- }
-
- // :WORKAROUND:
- // IE doesn't evaluate <style> tags in SVGs that are dynamically added to the page.
- // This trick will trigger IE to read and use any existing SVG <style> tags.
- //
- // Reference: https://github.com/iconic/SVGInjector/issues/23
- var styleTags = svg.querySelectorAll('style');
- forEach.call(styleTags, function (styleTag) {
- styleTag.textContent += '';
- });
-
- // Replace the image with the svg
- el.parentNode.replaceChild(svg, el);
-
- // Now that we no longer need it, drop references
- // to the original element so it can be GC'd
- delete injectedElements[injectedElements.indexOf(el)];
- el = null;
-
- // Increment the injected count
- injectCount++;
-
- callback(svg);
- });
- };
-
- /**
- * SVGInjector
- *
- * Replace the given elements with their full inline SVG DOM elements.
- *
- * :NOTE: We are using get/setAttribute with SVG because the SVG DOM spec differs from HTML DOM and
- * can return other unexpected object types when trying to directly access svg properties.
- * ex: "className" returns a SVGAnimatedString with the class value found in the "baseVal" property,
- * instead of simple string like with HTML Elements.
- *
- * @param {mixes} Array of or single DOM element
- * @param {object} options
- * @param {function} callback
- * @return {object} Instance of SVGInjector
- */
- var SVGInjector = function (elements, options, done) {
-
- // Options & defaults
- options = options || {};
-
- // Should we run the scripts blocks found in the SVG
- // 'always' - Run them every time
- // 'once' - Only run scripts once for each SVG
- // [false|'never'] - Ignore scripts
- var evalScripts = options.evalScripts || 'always';
-
- // Location of fallback pngs, if desired
- var pngFallback = options.pngFallback || false;
-
- // Callback to run during each SVG injection, returning the SVG injected
- var eachCallback = options.each;
-
- // Do the injection...
- if (elements.length !== undefined) {
- var elementsLoaded = 0;
- forEach.call(elements, function (element) {
- injectElement(element, evalScripts, pngFallback, function (svg) {
- if (eachCallback && typeof eachCallback === 'function') eachCallback(svg);
- if (done && elements.length === ++elementsLoaded) done(elementsLoaded);
- });
- });
- }
- else {
- if (elements) {
- injectElement(elements, evalScripts, pngFallback, function (svg) {
- if (eachCallback && typeof eachCallback === 'function') eachCallback(svg);
- if (done) done(1);
- elements = null;
- });
- }
- else {
- if (done) done(0);
- }
- }
- };
-
- /* global module, exports: true, define */
- // Node.js or CommonJS
- if (typeof module === 'object' && typeof module.exports === 'object') {
- module.exports = exports = SVGInjector;
- }
- // AMD support
- else if (typeof define === 'function' && define.amd) {
- define(function () {
- return SVGInjector;
- });
- }
- // Otherwise, attach to window as global
- else if (typeof window === 'object') {
- window.SVGInjector = SVGInjector;
- }
- /* global -module, -exports, -define */
-
- }(window, document));
|