From ac6aff4e8fa7383926abb354df816daf71bba5db Mon Sep 17 00:00:00 2001 From: iconfu Date: Wed, 17 Oct 2018 11:06:25 +0200 Subject: [PATCH] Version 1.1.2 --- README.md | 4 +- dist/svg-inject.js | 145 +++++++++++++++++++++---------------- dist/svg-inject.min.js | 16 ++-- examples/svg-inject.js | 145 +++++++++++++++++++++---------------- examples/svg-inject.min.js | 16 ++-- package.json | 2 +- src/svg-inject.js | 2 +- test/js/svg-inject.js | 145 +++++++++++++++++++++---------------- test/js/svg-inject.min.js | 16 ++-- 9 files changed, 280 insertions(+), 211 deletions(-) diff --git a/README.md b/README.md index cf65c35..cbc8b68 100644 --- a/README.md +++ b/README.md @@ -48,9 +48,9 @@ Include the SVGInject Javascript file in the `` element of the HTML docume ``` -Download plain version (v1.1.1): [svg-inject.js](https://raw.githubusercontent.com/iconfu/svg-inject/v1.1.1/dist/svg-inject.js) +Download plain version (v1.1.2): [svg-inject.js](https://raw.githubusercontent.com/iconfu/svg-inject/v1.1.2/dist/svg-inject.js) -Download minified version (v1.1.1): [svg-inject.min.js](https://raw.githubusercontent.com/iconfu/svg-inject/v1.1.1/dist/svg-inject.min.js) +Download minified version (v1.1.2): [svg-inject.min.js](https://raw.githubusercontent.com/iconfu/svg-inject/v1.1.2/dist/svg-inject.min.js) ### npm diff --git a/dist/svg-inject.js b/dist/svg-inject.js index 2b19fbb..861e25e 100644 --- a/dist/svg-inject.js +++ b/dist/svg-inject.js @@ -1,5 +1,5 @@ /** - * SVGInject - Version 1.1.1 + * SVGInject - Version 1.1.2 * A tiny, intuitive, robust, caching solution for injecting SVG files inline into the DOM. * * https://github.com/iconfu/svg-inject @@ -10,24 +10,27 @@ (function(window, document) { // constants for better minification + var _CREATE_ELEMENT_ = 'createElement'; + var _GET_ELEMENTS_BY_TAG_NAME_ = 'getElementsByTagName'; + var _LENGTH_ = 'length'; + var _STYLE_ = 'style'; + var _TITLE_ = 'title'; + var _UNDEFINED_ = 'undefined'; + var NULL = null; - var TRUE = true; - var LENGTH = 'length'; - var CREATE_ELEMENT = 'createElement'; - var TITLE = 'title'; - var __SVGINJECT = '__svgInject'; // constants + var __SVGINJECT = '__svgInject'; var LOAD_FAIL = 'LOAD_FAIL'; var SVG_NOT_SUPPORTED = 'SVG_NOT_SUPPORTED'; var SVG_INVALID = 'SVG_INVALID'; var ATTRIBUTE_EXCLUSION_NAMES = ['src', 'alt', 'onload', 'onerror']; - var A_ELEMENT = document[CREATE_ELEMENT]('a'); - var IS_SVG_SUPPORTED = typeof SVGRect != 'undefined'; + var A_ELEMENT = document[_CREATE_ELEMENT_]('a'); + var IS_SVG_SUPPORTED = typeof SVGRect != _UNDEFINED_; var DEFAULT_OPTIONS = { - useCache: TRUE, - copyAttributes: TRUE, - makeIdsUnique: TRUE + useCache: true, + copyAttributes: true, + makeIdsUnique: true }; // Map of IRI referenceable tag names to properties that can reference them. This is defined in // https://www.w3.org/TR/SVG11/linking.html#processingIRI @@ -50,24 +53,28 @@ var xmlSerializer; var domParser; + // Returns the XMLSerializer instance. Creates it first if it does not exist yet. function getXMLSerializer() { xmlSerializer = xmlSerializer || new XMLSerializer(); return xmlSerializer; } + // Returns the DOMParser instance. Creates it first if it does not exist yet. function getDOMParser() { domParser = domParser || new DOMParser(); return domParser; } + // Returns the absolute url for the specified url function getAbsoluteUrl(url) { A_ELEMENT.href = url; return A_ELEMENT.href; } + // Load svg with an XHR request function loadSvg(url, callback, errorCallback) { if (url) { @@ -88,29 +95,33 @@ } } }; - req.open('GET', url, TRUE); + req.open('GET', url, true); req.send(); } } + // Copy attributes from img element to svg element function copyAttributes(imgElem, svgElem) { + var attribute; + var attributeName; + var attributeValue; var attributes = imgElem.attributes; - for (var i = 0; i < attributes[LENGTH]; ++i) { - var attribute = attributes[i]; - var attributeName = attribute.name; + for (var i = 0; i < attributes[_LENGTH_]; i++) { + attribute = attributes[i]; + attributeName = attribute.name; // Only copy attributes not explicitly excluded from copying if (ATTRIBUTE_EXCLUSION_NAMES.indexOf(attributeName) == -1) { - var attributeValue = attribute.value; + attributeValue = attribute.value; // If img attribute is "title", insert a title element into SVG element - if (attributeName == TITLE) { + if (attributeName == _TITLE_) { // Create title element - var titleElem = document.createElementNS('http://www.w3.org/2000/svg', TITLE); + var titleElem = document[_CREATE_ELEMENT_ + 'NS']('http://www.w3.org/2000/svg', _TITLE_); titleElem.textContent = attributeValue; // If the SVG element's first child is a title element, replace it with the new title // element, otherwise insert the new title element as first child var firstElementChild = svgElem.firstElementChild; - if (firstElementChild && firstElementChild.tagName.toLowerCase() == TITLE) { + if (firstElementChild && firstElementChild.tagName.toLowerCase() == _TITLE_) { svgElem.replaceChild(titleElem, firstElementChild); } else { svgElem.insertBefore(titleElem, firstElementChild); @@ -123,6 +134,7 @@ } } + // This function appends a suffix to IDs of referenced elements in the in order to to avoid ID collision // between multiple injected SVGs. The suffix has the form "--inject-X", where X is a running number which is // incremented with each injection. References to the ids are adjusted accordingly. @@ -136,10 +148,9 @@ var idElements = svgElem.querySelectorAll('[id]'); var idElem; var tagName; - var iriPropertiesArr = []; var iriTagNames = {}; - var mappedProperties; - for (i = 0; i < idElements[LENGTH]; i++) { + var iriProperties = []; + for (i = 0; i < idElements[_LENGTH_]; i++) { idElem = idElements[i]; tagName = idElem.tagName; // Make ID unique if tag name is IRI referenceable @@ -156,43 +167,44 @@ }); } } + // Get all properties that are mapped to the found tags for (tagName in iriTagNames) { - // Collect all properties mapped to found tags - mappedProperties = IRI_TAG_PROPERTIES_MAP[tagName] || [tagName]; - for (j = 0; j < mappedProperties[LENGTH]; j++) { - if (iriPropertiesArr.indexOf(mappedProperties[j])) { - iriPropertiesArr.push(mappedProperties[j]); + (IRI_TAG_PROPERTIES_MAP[tagName] || [tagName]).forEach(function (mappedProperty) { + // Add mapped properties to array of iri referencing properties. + // Use linear search here because the number of possible entries is very small (maximum 11) + if (iriProperties.indexOf(mappedProperty) < 0) { + iriProperties.push(mappedProperty); } - } + }); } // Replace IDs with new IDs in all references - // Get an array of all iri referenceable property names that were found - if (iriPropertiesArr[LENGTH]) { + if (iriProperties[_LENGTH_]) { // Add "style" to properties, because it may contain references in the form 'style="fill:url(#myFill)"' - iriPropertiesArr.push('style'); + iriProperties.push(_STYLE_); // Regular expression for functional notations of an IRI references. This will find occurences in the form // url(#anyId) or url("#anyId") (for Internet Explorer) - var funcIriRegExp = new RegExp('url\\("?#([a-zA-Z][\\w:.-]*)"?\\)', 'g'); - // Run through all elements of the SVG and replace ids in references - var allElements = svgElem.querySelectorAll('*'); + var funcIriRegex = /url\("?#([a-zA-Z][\w:.-]*)"?\)/g; + // Run through all elements of the SVG and replace ids in references. It seems that getElementsByTagName('*') + // performs faster than querySelectorAll('*') in this case. + var allElements = svgElem[_GET_ELEMENTS_BY_TAG_NAME_]('*'); var element; var propertyName; var value; var newValue; - for (i = 0; i < allElements[LENGTH]; i++) { + for (i = 0; i < allElements[_LENGTH_]; i++) { element = allElements[i]; - if (element.tagName == 'style') { + if (element.tagName == _STYLE_) { value = element.textContent; - newValue = value && value.replace(funcIriRegExp, 'url(#$1' + idSuffix + ')'); + newValue = value && value.replace(funcIriRegex, 'url(#$1' + idSuffix + ')'); if (newValue !== value) { element.textContent = newValue; } } else if (element.hasAttributes()) { // Run through all property names for which ids were found - for (j = 0; j < iriPropertiesArr[LENGTH]; j++) { - propertyName = iriPropertiesArr[j]; + for (j = 0; j < iriProperties[_LENGTH_]; j++) { + propertyName = iriProperties[j]; value = element.getAttribute(propertyName); - newValue = value && value.replace(funcIriRegExp, 'url(#$1' + idSuffix + ')'); + newValue = value && value.replace(funcIriRegex, 'url(#$1' + idSuffix + ')'); if (newValue !== value) { element.setAttribute(propertyName, newValue); } @@ -202,6 +214,7 @@ } } + // inject svg by replacing the img element with the svg element in the DOM function inject(imgElem, svgElem, absUrl, options) { if (svgElem) { @@ -232,62 +245,60 @@ } } + // Merges any number of options objects into a new object function mergeOptions() { var mergedOptions = {}; var args = arguments; // Iterate over all specified options objects and add all properties to the new options object - for (var i = 0; i < args[LENGTH]; ++i) { + for (var i = 0; i < args[_LENGTH_]; i++) { var argument = args[i]; - if (argument) { for (var key in argument) { if (argument.hasOwnProperty(key)) { mergedOptions[key] = argument[key]; } } } - } return mergedOptions; } + // Adds the specified CSS to the document's element function addStyleToHead(css) { - var head = document.getElementsByTagName('head')[0]; + var head = document[_GET_ELEMENTS_BY_TAG_NAME_]('head')[0]; if (head) { - var style = document[CREATE_ELEMENT]('style'); + var style = document[_CREATE_ELEMENT_](_STYLE_); style.type = 'text/css'; style.appendChild(document.createTextNode(css)); head.appendChild(style); } } - // Builds an SVG element from the specified SVG string function buildSvgElement(svgStr, verify) { var svgDoc; - try { // Parse the SVG string with DOMParser svgDoc = getDOMParser().parseFromString(svgStr, 'text/xml'); } catch(e) { return NULL; } - - if (verify && svgDoc.getElementsByTagName('parsererror').length) { + if (verify && svgDoc[_GET_ELEMENTS_BY_TAG_NAME_]('parsererror')[_LENGTH_]) { // DOMParser does not throw an exception, but instead puts parsererror tags in the document return NULL; } - return svgDoc.documentElement; } + function removeOnLoadAttribute(imgElem) { // Remove the onload attribute. Should only be used to remove the unstyled image flash protection and // make the element visible, not for removing the event listener. imgElem.removeAttribute('onload'); } + function fail(imgElem, status, options) { imgElem[__SVGINJECT] = FAIL; if (options.onFail) { @@ -295,29 +306,35 @@ } } + function svgInvalid(imgElem, options) { removeOnLoadAttribute(imgElem); fail(imgElem, SVG_INVALID, options); } + function svgNotSupported(imgElem, options) { removeOnLoadAttribute(imgElem); fail(imgElem, SVG_NOT_SUPPORTED, options); } + function loadFail(imgElem, options) { fail(imgElem, LOAD_FAIL, options); } + function removeEventListeners(imgElem) { imgElem.onload = NULL; imgElem.onerror = NULL; } + function throwImgNotSet() { throw new Error('img not set'); } + function createSVGInject(globalName, options) { var defaultOptions = mergeOptions(DEFAULT_OPTIONS, options); var svgLoadCache = {}; @@ -328,6 +345,7 @@ addStyleToHead('img[onload^="' + globalName + '("]{visibility:hidden;}'); } + /** * SVGInject * @@ -358,9 +376,8 @@ */ function SVGInject(img, options) { options = mergeOptions(defaultOptions, options); - - if (img && typeof img[LENGTH] != 'undefined') { - for (var i = 0; i < img[LENGTH]; ++i) { + if (img && typeof img[_LENGTH_] != _UNDEFINED_) { + for (var i = 0; i < img[_LENGTH_]; i++) { SVGInjectElement(img[i], options); } } else { @@ -368,6 +385,7 @@ } } + // Injects a single svg element. Options must be already merged with the default options. function SVGInjectElement(imgElem, options) { if (imgElem) { @@ -381,25 +399,25 @@ // Invoke beforeLoad hook if set. If the beforeLoad returns a value use it as the src for the load // URL path. Else use the imgElem src attribute value. - var src = (options.beforeLoad && options.beforeLoad(imgElem)) || imgElem.getAttribute('src'); + var beforeLoad = options.beforeLoad; + var src = (beforeLoad && beforeLoad(imgElem)) || imgElem.getAttribute('src'); - if (src === null) { + if (src === NULL) { // If no image src attribute is set do no injection. This can only be reached by using javascript // because if no src attribute is set the onload and onerror events do not get called return; } imgElem[__SVGINJECT] = INJECT; - + var absUrl = getAbsoluteUrl(src); var useCache = options.useCache; var setSvgLoadCacheValue = function(val) { if (useCache) { - var svgLoad = svgLoadCache[absUrl]; - for (var i = 0; i < svgLoad[LENGTH]; ++i) { - svgLoad[i](val); - } + svgLoadCache[absUrl].forEach(function(svgLoad) { + svgLoad(val); + }); svgLoadCache[absUrl] = val; } }; @@ -417,8 +435,10 @@ } }; - if (svgLoad !== undefined) { + if (typeof svgLoad != _UNDEFINED_) { + // Value for url exists in cache if (Array.isArray(svgLoad)) { + // Same url has been cached, but value has not been loaded yet svgLoad.push(handleLoadValue); } else { handleLoadValue(svgLoad); @@ -466,6 +486,7 @@ } } + /** * Sets the default [options](#options) for SVGInject. * @@ -475,9 +496,11 @@ defaultOptions = mergeOptions(defaultOptions, options); }; + // Create a new instance of SVGInject SVGInject.create = createSVGInject; + /** * Used in onerror Event of an `` element to handle cases when the loading the original src fails * (for example if file is not found or if the browser does not support SVG). This triggers a call to the diff --git a/dist/svg-inject.min.js b/dist/svg-inject.min.js index ea10108..46ebeef 100644 --- a/dist/svg-inject.min.js +++ b/dist/svg-inject.min.js @@ -1,8 +1,8 @@ -!function(a,s){var d,o,f=null,m=!0,g="length",u="createElement",c="title",p="__svgInject",v="LOAD_FAIL",r="SVG_NOT_SUPPORTED",h="SVG_INVALID",y=["src","alt","onload","onerror"],A=s[u]("a"),b="undefined"!=typeof SVGRect,l={useCache:m,copyAttributes:m,makeIdsUnique:m},w={clipPath:["clip-path"],"color-profile":f,cursor:f,filter:f,linearGradient:["fill","stroke"],marker:["marker","marker-end","marker-mid","marker-start"],mask:f,pattern:["fill","stroke"],radialGradient:["fill","stroke"]},x=1,E=2,S=3, -k=1;function C(e,t,r,n){if(t){t.setAttribute("data-inject-url",r);var i=e.parentNode;if(i){n.copyAttributes&&function l(e,t){for(var r=e.attributes,n=0;n in order to to avoid ID collision // between multiple injected SVGs. The suffix has the form "--inject-X", where X is a running number which is // incremented with each injection. References to the ids are adjusted accordingly. @@ -136,10 +148,9 @@ var idElements = svgElem.querySelectorAll('[id]'); var idElem; var tagName; - var iriPropertiesArr = []; var iriTagNames = {}; - var mappedProperties; - for (i = 0; i < idElements[LENGTH]; i++) { + var iriProperties = []; + for (i = 0; i < idElements[_LENGTH_]; i++) { idElem = idElements[i]; tagName = idElem.tagName; // Make ID unique if tag name is IRI referenceable @@ -156,43 +167,44 @@ }); } } + // Get all properties that are mapped to the found tags for (tagName in iriTagNames) { - // Collect all properties mapped to found tags - mappedProperties = IRI_TAG_PROPERTIES_MAP[tagName] || [tagName]; - for (j = 0; j < mappedProperties[LENGTH]; j++) { - if (iriPropertiesArr.indexOf(mappedProperties[j])) { - iriPropertiesArr.push(mappedProperties[j]); + (IRI_TAG_PROPERTIES_MAP[tagName] || [tagName]).forEach(function (mappedProperty) { + // Add mapped properties to array of iri referencing properties. + // Use linear search here because the number of possible entries is very small (maximum 11) + if (iriProperties.indexOf(mappedProperty) < 0) { + iriProperties.push(mappedProperty); } - } + }); } // Replace IDs with new IDs in all references - // Get an array of all iri referenceable property names that were found - if (iriPropertiesArr[LENGTH]) { + if (iriProperties[_LENGTH_]) { // Add "style" to properties, because it may contain references in the form 'style="fill:url(#myFill)"' - iriPropertiesArr.push('style'); + iriProperties.push(_STYLE_); // Regular expression for functional notations of an IRI references. This will find occurences in the form // url(#anyId) or url("#anyId") (for Internet Explorer) - var funcIriRegExp = new RegExp('url\\("?#([a-zA-Z][\\w:.-]*)"?\\)', 'g'); - // Run through all elements of the SVG and replace ids in references - var allElements = svgElem.querySelectorAll('*'); + var funcIriRegex = /url\("?#([a-zA-Z][\w:.-]*)"?\)/g; + // Run through all elements of the SVG and replace ids in references. It seems that getElementsByTagName('*') + // performs faster than querySelectorAll('*') in this case. + var allElements = svgElem[_GET_ELEMENTS_BY_TAG_NAME_]('*'); var element; var propertyName; var value; var newValue; - for (i = 0; i < allElements[LENGTH]; i++) { + for (i = 0; i < allElements[_LENGTH_]; i++) { element = allElements[i]; - if (element.tagName == 'style') { + if (element.tagName == _STYLE_) { value = element.textContent; - newValue = value && value.replace(funcIriRegExp, 'url(#$1' + idSuffix + ')'); + newValue = value && value.replace(funcIriRegex, 'url(#$1' + idSuffix + ')'); if (newValue !== value) { element.textContent = newValue; } } else if (element.hasAttributes()) { // Run through all property names for which ids were found - for (j = 0; j < iriPropertiesArr[LENGTH]; j++) { - propertyName = iriPropertiesArr[j]; + for (j = 0; j < iriProperties[_LENGTH_]; j++) { + propertyName = iriProperties[j]; value = element.getAttribute(propertyName); - newValue = value && value.replace(funcIriRegExp, 'url(#$1' + idSuffix + ')'); + newValue = value && value.replace(funcIriRegex, 'url(#$1' + idSuffix + ')'); if (newValue !== value) { element.setAttribute(propertyName, newValue); } @@ -202,6 +214,7 @@ } } + // inject svg by replacing the img element with the svg element in the DOM function inject(imgElem, svgElem, absUrl, options) { if (svgElem) { @@ -232,62 +245,60 @@ } } + // Merges any number of options objects into a new object function mergeOptions() { var mergedOptions = {}; var args = arguments; // Iterate over all specified options objects and add all properties to the new options object - for (var i = 0; i < args[LENGTH]; ++i) { + for (var i = 0; i < args[_LENGTH_]; i++) { var argument = args[i]; - if (argument) { for (var key in argument) { if (argument.hasOwnProperty(key)) { mergedOptions[key] = argument[key]; } } } - } return mergedOptions; } + // Adds the specified CSS to the document's element function addStyleToHead(css) { - var head = document.getElementsByTagName('head')[0]; + var head = document[_GET_ELEMENTS_BY_TAG_NAME_]('head')[0]; if (head) { - var style = document[CREATE_ELEMENT]('style'); + var style = document[_CREATE_ELEMENT_](_STYLE_); style.type = 'text/css'; style.appendChild(document.createTextNode(css)); head.appendChild(style); } } - // Builds an SVG element from the specified SVG string function buildSvgElement(svgStr, verify) { var svgDoc; - try { // Parse the SVG string with DOMParser svgDoc = getDOMParser().parseFromString(svgStr, 'text/xml'); } catch(e) { return NULL; } - - if (verify && svgDoc.getElementsByTagName('parsererror').length) { + if (verify && svgDoc[_GET_ELEMENTS_BY_TAG_NAME_]('parsererror')[_LENGTH_]) { // DOMParser does not throw an exception, but instead puts parsererror tags in the document return NULL; } - return svgDoc.documentElement; } + function removeOnLoadAttribute(imgElem) { // Remove the onload attribute. Should only be used to remove the unstyled image flash protection and // make the element visible, not for removing the event listener. imgElem.removeAttribute('onload'); } + function fail(imgElem, status, options) { imgElem[__SVGINJECT] = FAIL; if (options.onFail) { @@ -295,29 +306,35 @@ } } + function svgInvalid(imgElem, options) { removeOnLoadAttribute(imgElem); fail(imgElem, SVG_INVALID, options); } + function svgNotSupported(imgElem, options) { removeOnLoadAttribute(imgElem); fail(imgElem, SVG_NOT_SUPPORTED, options); } + function loadFail(imgElem, options) { fail(imgElem, LOAD_FAIL, options); } + function removeEventListeners(imgElem) { imgElem.onload = NULL; imgElem.onerror = NULL; } + function throwImgNotSet() { throw new Error('img not set'); } + function createSVGInject(globalName, options) { var defaultOptions = mergeOptions(DEFAULT_OPTIONS, options); var svgLoadCache = {}; @@ -328,6 +345,7 @@ addStyleToHead('img[onload^="' + globalName + '("]{visibility:hidden;}'); } + /** * SVGInject * @@ -358,9 +376,8 @@ */ function SVGInject(img, options) { options = mergeOptions(defaultOptions, options); - - if (img && typeof img[LENGTH] != 'undefined') { - for (var i = 0; i < img[LENGTH]; ++i) { + if (img && typeof img[_LENGTH_] != _UNDEFINED_) { + for (var i = 0; i < img[_LENGTH_]; i++) { SVGInjectElement(img[i], options); } } else { @@ -368,6 +385,7 @@ } } + // Injects a single svg element. Options must be already merged with the default options. function SVGInjectElement(imgElem, options) { if (imgElem) { @@ -381,25 +399,25 @@ // Invoke beforeLoad hook if set. If the beforeLoad returns a value use it as the src for the load // URL path. Else use the imgElem src attribute value. - var src = (options.beforeLoad && options.beforeLoad(imgElem)) || imgElem.getAttribute('src'); + var beforeLoad = options.beforeLoad; + var src = (beforeLoad && beforeLoad(imgElem)) || imgElem.getAttribute('src'); - if (src === null) { + if (src === NULL) { // If no image src attribute is set do no injection. This can only be reached by using javascript // because if no src attribute is set the onload and onerror events do not get called return; } imgElem[__SVGINJECT] = INJECT; - + var absUrl = getAbsoluteUrl(src); var useCache = options.useCache; var setSvgLoadCacheValue = function(val) { if (useCache) { - var svgLoad = svgLoadCache[absUrl]; - for (var i = 0; i < svgLoad[LENGTH]; ++i) { - svgLoad[i](val); - } + svgLoadCache[absUrl].forEach(function(svgLoad) { + svgLoad(val); + }); svgLoadCache[absUrl] = val; } }; @@ -417,8 +435,10 @@ } }; - if (svgLoad !== undefined) { + if (typeof svgLoad != _UNDEFINED_) { + // Value for url exists in cache if (Array.isArray(svgLoad)) { + // Same url has been cached, but value has not been loaded yet svgLoad.push(handleLoadValue); } else { handleLoadValue(svgLoad); @@ -466,6 +486,7 @@ } } + /** * Sets the default [options](#options) for SVGInject. * @@ -475,9 +496,11 @@ defaultOptions = mergeOptions(defaultOptions, options); }; + // Create a new instance of SVGInject SVGInject.create = createSVGInject; + /** * Used in onerror Event of an `` element to handle cases when the loading the original src fails * (for example if file is not found or if the browser does not support SVG). This triggers a call to the diff --git a/examples/svg-inject.min.js b/examples/svg-inject.min.js index ea10108..46ebeef 100644 --- a/examples/svg-inject.min.js +++ b/examples/svg-inject.min.js @@ -1,8 +1,8 @@ -!function(a,s){var d,o,f=null,m=!0,g="length",u="createElement",c="title",p="__svgInject",v="LOAD_FAIL",r="SVG_NOT_SUPPORTED",h="SVG_INVALID",y=["src","alt","onload","onerror"],A=s[u]("a"),b="undefined"!=typeof SVGRect,l={useCache:m,copyAttributes:m,makeIdsUnique:m},w={clipPath:["clip-path"],"color-profile":f,cursor:f,filter:f,linearGradient:["fill","stroke"],marker:["marker","marker-end","marker-mid","marker-start"],mask:f,pattern:["fill","stroke"],radialGradient:["fill","stroke"]},x=1,E=2,S=3, -k=1;function C(e,t,r,n){if(t){t.setAttribute("data-inject-url",r);var i=e.parentNode;if(i){n.copyAttributes&&function l(e,t){for(var r=e.attributes,n=0;n in order to to avoid ID collision // between multiple injected SVGs. The suffix has the form "--inject-X", where X is a running number which is // incremented with each injection. References to the ids are adjusted accordingly. @@ -136,10 +148,9 @@ var idElements = svgElem.querySelectorAll('[id]'); var idElem; var tagName; - var iriPropertiesArr = []; var iriTagNames = {}; - var mappedProperties; - for (i = 0; i < idElements[LENGTH]; i++) { + var iriProperties = []; + for (i = 0; i < idElements[_LENGTH_]; i++) { idElem = idElements[i]; tagName = idElem.tagName; // Make ID unique if tag name is IRI referenceable @@ -156,43 +167,44 @@ }); } } + // Get all properties that are mapped to the found tags for (tagName in iriTagNames) { - // Collect all properties mapped to found tags - mappedProperties = IRI_TAG_PROPERTIES_MAP[tagName] || [tagName]; - for (j = 0; j < mappedProperties[LENGTH]; j++) { - if (iriPropertiesArr.indexOf(mappedProperties[j])) { - iriPropertiesArr.push(mappedProperties[j]); + (IRI_TAG_PROPERTIES_MAP[tagName] || [tagName]).forEach(function (mappedProperty) { + // Add mapped properties to array of iri referencing properties. + // Use linear search here because the number of possible entries is very small (maximum 11) + if (iriProperties.indexOf(mappedProperty) < 0) { + iriProperties.push(mappedProperty); } - } + }); } // Replace IDs with new IDs in all references - // Get an array of all iri referenceable property names that were found - if (iriPropertiesArr[LENGTH]) { + if (iriProperties[_LENGTH_]) { // Add "style" to properties, because it may contain references in the form 'style="fill:url(#myFill)"' - iriPropertiesArr.push('style'); + iriProperties.push(_STYLE_); // Regular expression for functional notations of an IRI references. This will find occurences in the form // url(#anyId) or url("#anyId") (for Internet Explorer) - var funcIriRegExp = new RegExp('url\\("?#([a-zA-Z][\\w:.-]*)"?\\)', 'g'); - // Run through all elements of the SVG and replace ids in references - var allElements = svgElem.querySelectorAll('*'); + var funcIriRegex = /url\("?#([a-zA-Z][\w:.-]*)"?\)/g; + // Run through all elements of the SVG and replace ids in references. It seems that getElementsByTagName('*') + // performs faster than querySelectorAll('*') in this case. + var allElements = svgElem[_GET_ELEMENTS_BY_TAG_NAME_]('*'); var element; var propertyName; var value; var newValue; - for (i = 0; i < allElements[LENGTH]; i++) { + for (i = 0; i < allElements[_LENGTH_]; i++) { element = allElements[i]; - if (element.tagName == 'style') { + if (element.tagName == _STYLE_) { value = element.textContent; - newValue = value && value.replace(funcIriRegExp, 'url(#$1' + idSuffix + ')'); + newValue = value && value.replace(funcIriRegex, 'url(#$1' + idSuffix + ')'); if (newValue !== value) { element.textContent = newValue; } } else if (element.hasAttributes()) { // Run through all property names for which ids were found - for (j = 0; j < iriPropertiesArr[LENGTH]; j++) { - propertyName = iriPropertiesArr[j]; + for (j = 0; j < iriProperties[_LENGTH_]; j++) { + propertyName = iriProperties[j]; value = element.getAttribute(propertyName); - newValue = value && value.replace(funcIriRegExp, 'url(#$1' + idSuffix + ')'); + newValue = value && value.replace(funcIriRegex, 'url(#$1' + idSuffix + ')'); if (newValue !== value) { element.setAttribute(propertyName, newValue); } @@ -202,6 +214,7 @@ } } + // inject svg by replacing the img element with the svg element in the DOM function inject(imgElem, svgElem, absUrl, options) { if (svgElem) { @@ -232,62 +245,60 @@ } } + // Merges any number of options objects into a new object function mergeOptions() { var mergedOptions = {}; var args = arguments; // Iterate over all specified options objects and add all properties to the new options object - for (var i = 0; i < args[LENGTH]; ++i) { + for (var i = 0; i < args[_LENGTH_]; i++) { var argument = args[i]; - if (argument) { for (var key in argument) { if (argument.hasOwnProperty(key)) { mergedOptions[key] = argument[key]; } } } - } return mergedOptions; } + // Adds the specified CSS to the document's element function addStyleToHead(css) { - var head = document.getElementsByTagName('head')[0]; + var head = document[_GET_ELEMENTS_BY_TAG_NAME_]('head')[0]; if (head) { - var style = document[CREATE_ELEMENT]('style'); + var style = document[_CREATE_ELEMENT_](_STYLE_); style.type = 'text/css'; style.appendChild(document.createTextNode(css)); head.appendChild(style); } } - // Builds an SVG element from the specified SVG string function buildSvgElement(svgStr, verify) { var svgDoc; - try { // Parse the SVG string with DOMParser svgDoc = getDOMParser().parseFromString(svgStr, 'text/xml'); } catch(e) { return NULL; } - - if (verify && svgDoc.getElementsByTagName('parsererror').length) { + if (verify && svgDoc[_GET_ELEMENTS_BY_TAG_NAME_]('parsererror')[_LENGTH_]) { // DOMParser does not throw an exception, but instead puts parsererror tags in the document return NULL; } - return svgDoc.documentElement; } + function removeOnLoadAttribute(imgElem) { // Remove the onload attribute. Should only be used to remove the unstyled image flash protection and // make the element visible, not for removing the event listener. imgElem.removeAttribute('onload'); } + function fail(imgElem, status, options) { imgElem[__SVGINJECT] = FAIL; if (options.onFail) { @@ -295,29 +306,35 @@ } } + function svgInvalid(imgElem, options) { removeOnLoadAttribute(imgElem); fail(imgElem, SVG_INVALID, options); } + function svgNotSupported(imgElem, options) { removeOnLoadAttribute(imgElem); fail(imgElem, SVG_NOT_SUPPORTED, options); } + function loadFail(imgElem, options) { fail(imgElem, LOAD_FAIL, options); } + function removeEventListeners(imgElem) { imgElem.onload = NULL; imgElem.onerror = NULL; } + function throwImgNotSet() { throw new Error('img not set'); } + function createSVGInject(globalName, options) { var defaultOptions = mergeOptions(DEFAULT_OPTIONS, options); var svgLoadCache = {}; @@ -328,6 +345,7 @@ addStyleToHead('img[onload^="' + globalName + '("]{visibility:hidden;}'); } + /** * SVGInject * @@ -358,9 +376,8 @@ */ function SVGInject(img, options) { options = mergeOptions(defaultOptions, options); - - if (img && typeof img[LENGTH] != 'undefined') { - for (var i = 0; i < img[LENGTH]; ++i) { + if (img && typeof img[_LENGTH_] != _UNDEFINED_) { + for (var i = 0; i < img[_LENGTH_]; i++) { SVGInjectElement(img[i], options); } } else { @@ -368,6 +385,7 @@ } } + // Injects a single svg element. Options must be already merged with the default options. function SVGInjectElement(imgElem, options) { if (imgElem) { @@ -381,25 +399,25 @@ // Invoke beforeLoad hook if set. If the beforeLoad returns a value use it as the src for the load // URL path. Else use the imgElem src attribute value. - var src = (options.beforeLoad && options.beforeLoad(imgElem)) || imgElem.getAttribute('src'); + var beforeLoad = options.beforeLoad; + var src = (beforeLoad && beforeLoad(imgElem)) || imgElem.getAttribute('src'); - if (src === null) { + if (src === NULL) { // If no image src attribute is set do no injection. This can only be reached by using javascript // because if no src attribute is set the onload and onerror events do not get called return; } imgElem[__SVGINJECT] = INJECT; - + var absUrl = getAbsoluteUrl(src); var useCache = options.useCache; var setSvgLoadCacheValue = function(val) { if (useCache) { - var svgLoad = svgLoadCache[absUrl]; - for (var i = 0; i < svgLoad[LENGTH]; ++i) { - svgLoad[i](val); - } + svgLoadCache[absUrl].forEach(function(svgLoad) { + svgLoad(val); + }); svgLoadCache[absUrl] = val; } }; @@ -417,8 +435,10 @@ } }; - if (svgLoad !== undefined) { + if (typeof svgLoad != _UNDEFINED_) { + // Value for url exists in cache if (Array.isArray(svgLoad)) { + // Same url has been cached, but value has not been loaded yet svgLoad.push(handleLoadValue); } else { handleLoadValue(svgLoad); @@ -466,6 +486,7 @@ } } + /** * Sets the default [options](#options) for SVGInject. * @@ -475,9 +496,11 @@ defaultOptions = mergeOptions(defaultOptions, options); }; + // Create a new instance of SVGInject SVGInject.create = createSVGInject; + /** * Used in onerror Event of an `` element to handle cases when the loading the original src fails * (for example if file is not found or if the browser does not support SVG). This triggers a call to the diff --git a/test/js/svg-inject.min.js b/test/js/svg-inject.min.js index ea10108..46ebeef 100644 --- a/test/js/svg-inject.min.js +++ b/test/js/svg-inject.min.js @@ -1,8 +1,8 @@ -!function(a,s){var d,o,f=null,m=!0,g="length",u="createElement",c="title",p="__svgInject",v="LOAD_FAIL",r="SVG_NOT_SUPPORTED",h="SVG_INVALID",y=["src","alt","onload","onerror"],A=s[u]("a"),b="undefined"!=typeof SVGRect,l={useCache:m,copyAttributes:m,makeIdsUnique:m},w={clipPath:["clip-path"],"color-profile":f,cursor:f,filter:f,linearGradient:["fill","stroke"],marker:["marker","marker-end","marker-mid","marker-start"],mask:f,pattern:["fill","stroke"],radialGradient:["fill","stroke"]},x=1,E=2,S=3, -k=1;function C(e,t,r,n){if(t){t.setAttribute("data-inject-url",r);var i=e.parentNode;if(i){n.copyAttributes&&function l(e,t){for(var r=e.attributes,n=0;n