svg-inject.js 26 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697
  1. /**
  2. * SVGInject - Version 1.2.3
  3. * A tiny, intuitive, robust, caching solution for injecting SVG files inline into the DOM.
  4. *
  5. * https://github.com/iconfu/svg-inject
  6. *
  7. * Copyright (c) 2018 INCORS, the creators of iconfu.com
  8. * @license MIT License - https://github.com/iconfu/svg-inject/blob/master/LICENSE
  9. */
  10. (function(window, document) {
  11. // constants for better minification
  12. var _CREATE_ELEMENT_ = 'createElement';
  13. var _GET_ELEMENTS_BY_TAG_NAME_ = 'getElementsByTagName';
  14. var _LENGTH_ = 'length';
  15. var _STYLE_ = 'style';
  16. var _TITLE_ = 'title';
  17. var _UNDEFINED_ = 'undefined';
  18. var _SET_ATTRIBUTE_ = 'setAttribute';
  19. var _GET_ATTRIBUTE_ = 'getAttribute';
  20. var NULL = null;
  21. // constants
  22. var __SVGINJECT = '__svgInject';
  23. var ID_SUFFIX = '--inject-';
  24. var ID_SUFFIX_REGEX = new RegExp(ID_SUFFIX + '\\d+', "g");
  25. var LOAD_FAIL = 'LOAD_FAIL';
  26. var SVG_NOT_SUPPORTED = 'SVG_NOT_SUPPORTED';
  27. var SVG_INVALID = 'SVG_INVALID';
  28. var ATTRIBUTE_EXCLUSION_NAMES = ['src', 'alt', 'onload', 'onerror'];
  29. var A_ELEMENT = document[_CREATE_ELEMENT_]('a');
  30. var IS_SVG_SUPPORTED = typeof SVGRect != _UNDEFINED_;
  31. var DEFAULT_OPTIONS = {
  32. useCache: true,
  33. copyAttributes: true,
  34. makeIdsUnique: true
  35. };
  36. // Map of IRI referenceable tag names to properties that can reference them. This is defined in
  37. // https://www.w3.org/TR/SVG11/linking.html#processingIRI
  38. var IRI_TAG_PROPERTIES_MAP = {
  39. clipPath: ['clip-path'],
  40. 'color-profile': NULL,
  41. cursor: NULL,
  42. filter: NULL,
  43. linearGradient: ['fill', 'stroke'],
  44. marker: ['marker', 'marker-end', 'marker-mid', 'marker-start'],
  45. mask: NULL,
  46. pattern: ['fill', 'stroke'],
  47. radialGradient: ['fill', 'stroke']
  48. };
  49. var INJECTED = 1;
  50. var FAIL = 2;
  51. var uniqueIdCounter = 1;
  52. var xmlSerializer;
  53. var domParser;
  54. // creates an SVG document from an SVG string
  55. function svgStringToSvgDoc(svgStr) {
  56. domParser = domParser || new DOMParser();
  57. return domParser.parseFromString(svgStr, 'text/xml');
  58. }
  59. // searializes an SVG element to an SVG string
  60. function svgElemToSvgString(svgElement) {
  61. xmlSerializer = xmlSerializer || new XMLSerializer();
  62. return xmlSerializer.serializeToString(svgElement);
  63. }
  64. // Returns the absolute url for the specified url
  65. function getAbsoluteUrl(url) {
  66. A_ELEMENT.href = url;
  67. return A_ELEMENT.href;
  68. }
  69. // Load svg with an XHR request
  70. function loadSvg(url, callback, errorCallback) {
  71. if (url) {
  72. var req = new XMLHttpRequest();
  73. req.onreadystatechange = function() {
  74. if (req.readyState == 4) {
  75. // readyState is DONE
  76. var status = req.status;
  77. if (status == 200) {
  78. // request status is OK
  79. callback(req.responseXML, req.responseText.trim());
  80. } else if (status >= 400) {
  81. // request status is error (4xx or 5xx)
  82. errorCallback();
  83. } else if (status == 0) {
  84. // request status 0 can indicate a failed cross-domain call
  85. errorCallback();
  86. }
  87. }
  88. };
  89. req.open('GET', url, true);
  90. req.send();
  91. }
  92. }
  93. // Copy attributes from img element to svg element
  94. function copyAttributes(imgElem, svgElem) {
  95. var attribute;
  96. var attributeName;
  97. var attributeValue;
  98. var attributes = imgElem.attributes;
  99. for (var i = 0; i < attributes[_LENGTH_]; i++) {
  100. attribute = attributes[i];
  101. attributeName = attribute.name;
  102. // Only copy attributes not explicitly excluded from copying
  103. if (ATTRIBUTE_EXCLUSION_NAMES.indexOf(attributeName) == -1) {
  104. attributeValue = attribute.value;
  105. // If img attribute is "title", insert a title element into SVG element
  106. if (attributeName == _TITLE_) {
  107. var titleElem;
  108. var firstElementChild = svgElem.firstElementChild;
  109. if (firstElementChild && firstElementChild.localName.toLowerCase() == _TITLE_) {
  110. // If the SVG element's first child is a title element, keep it as the title element
  111. titleElem = firstElementChild;
  112. } else {
  113. // If the SVG element's first child element is not a title element, create a new title
  114. // ele,emt and set it as the first child
  115. titleElem = document[_CREATE_ELEMENT_ + 'NS']('http://www.w3.org/2000/svg', _TITLE_);
  116. svgElem.insertBefore(titleElem, firstElementChild);
  117. }
  118. // Set new title content
  119. titleElem.textContent = attributeValue;
  120. } else {
  121. // Set img attribute to svg element
  122. svgElem[_SET_ATTRIBUTE_](attributeName, attributeValue);
  123. }
  124. }
  125. }
  126. }
  127. // This function appends a suffix to IDs of referenced elements in the <defs> in order to to avoid ID collision
  128. // between multiple injected SVGs. The suffix has the form "--inject-X", where X is a running number which is
  129. // incremented with each injection. References to the IDs are adjusted accordingly.
  130. // We assume tha all IDs within the injected SVG are unique, therefore the same suffix can be used for all IDs of one
  131. // injected SVG.
  132. // If the onlyReferenced argument is set to true, only those IDs will be made unique that are referenced from within the SVG
  133. function makeIdsUnique(svgElem, onlyReferenced) {
  134. var idSuffix = ID_SUFFIX + uniqueIdCounter++;
  135. // Regular expression for functional notations of an IRI references. This will find occurences in the form
  136. // url(#anyId) or url("#anyId") (for Internet Explorer) and capture the referenced ID
  137. var funcIriRegex = /url\("?#([a-zA-Z][\w:.-]*)"?\)/g;
  138. // Get all elements with an ID. The SVG spec recommends to put referenced elements inside <defs> elements, but
  139. // this is not a requirement, therefore we have to search for IDs in the whole SVG.
  140. var idElements = svgElem.querySelectorAll('[id]');
  141. var idElem;
  142. // An object containing referenced IDs as keys is used if only referenced IDs should be uniquified.
  143. // If this object does not exist, all IDs will be uniquified.
  144. var referencedIds = onlyReferenced ? [] : NULL;
  145. var tagName;
  146. var iriTagNames = {};
  147. var iriProperties = [];
  148. var changed = false;
  149. var i, j;
  150. if (idElements[_LENGTH_]) {
  151. // Make all IDs unique by adding the ID suffix and collect all encountered tag names
  152. // that are IRI referenceable from properities.
  153. for (i = 0; i < idElements[_LENGTH_]; i++) {
  154. tagName = idElements[i].localName; // Use non-namespaced tag name
  155. // Make ID unique if tag name is IRI referenceable
  156. if (tagName in IRI_TAG_PROPERTIES_MAP) {
  157. iriTagNames[tagName] = 1;
  158. }
  159. }
  160. // Get all properties that are mapped to the found IRI referenceable tags
  161. for (tagName in iriTagNames) {
  162. (IRI_TAG_PROPERTIES_MAP[tagName] || [tagName]).forEach(function (mappedProperty) {
  163. // Add mapped properties to array of iri referencing properties.
  164. // Use linear search here because the number of possible entries is very small (maximum 11)
  165. if (iriProperties.indexOf(mappedProperty) < 0) {
  166. iriProperties.push(mappedProperty);
  167. }
  168. });
  169. }
  170. if (iriProperties[_LENGTH_]) {
  171. // Add "style" to properties, because it may contain references in the form 'style="fill:url(#myFill)"'
  172. iriProperties.push(_STYLE_);
  173. }
  174. // Run through all elements of the SVG and replace IDs in references.
  175. // To get all descending elements, getElementsByTagName('*') seems to perform faster than querySelectorAll('*').
  176. // Since svgElem.getElementsByTagName('*') does not return the svg element itself, we have to handle it separately.
  177. var descElements = svgElem[_GET_ELEMENTS_BY_TAG_NAME_]('*');
  178. var element = svgElem;
  179. var propertyName;
  180. var value;
  181. var newValue;
  182. for (i = -1; element != NULL;) {
  183. if (element.localName == _STYLE_) {
  184. // If element is a style element, replace IDs in all occurences of "url(#anyId)" in text content
  185. value = element.textContent;
  186. newValue = value && value.replace(funcIriRegex, function(match, id) {
  187. if (referencedIds) {
  188. referencedIds[id] = 1;
  189. }
  190. return 'url(#' + id + idSuffix + ')';
  191. });
  192. if (newValue !== value) {
  193. element.textContent = newValue;
  194. }
  195. } else if (element.hasAttributes()) {
  196. // Run through all property names for which IDs were found
  197. for (j = 0; j < iriProperties[_LENGTH_]; j++) {
  198. propertyName = iriProperties[j];
  199. value = element[_GET_ATTRIBUTE_](propertyName);
  200. newValue = value && value.replace(funcIriRegex, function(match, id) {
  201. if (referencedIds) {
  202. referencedIds[id] = 1;
  203. }
  204. return 'url(#' + id + idSuffix + ')';
  205. });
  206. if (newValue !== value) {
  207. element[_SET_ATTRIBUTE_](propertyName, newValue);
  208. }
  209. }
  210. // Replace IDs in xlink:ref and href attributes
  211. ['xlink:href', 'href'].forEach(function(refAttrName) {
  212. var iri = element[_GET_ATTRIBUTE_](refAttrName);
  213. if (/^\s*#/.test(iri)) { // Check if iri is non-null and internal reference
  214. iri = iri.trim();
  215. element[_SET_ATTRIBUTE_](refAttrName, iri + idSuffix);
  216. if (referencedIds) {
  217. // Add ID to referenced IDs
  218. referencedIds[iri.substring(1)] = 1;
  219. }
  220. }
  221. });
  222. }
  223. element = descElements[++i];
  224. }
  225. for (i = 0; i < idElements[_LENGTH_]; i++) {
  226. idElem = idElements[i];
  227. // If set of referenced IDs exists, make only referenced IDs unique,
  228. // otherwise make all IDs unique.
  229. if (!referencedIds || referencedIds[idElem.id]) {
  230. // Add suffix to element's ID
  231. idElem.id += idSuffix;
  232. changed = true;
  233. }
  234. }
  235. }
  236. // return true if SVG element has changed
  237. return changed;
  238. }
  239. // For cached SVGs the IDs are made unique by simply replacing the already inserted unique IDs with a
  240. // higher ID counter. This is much more performant than a call to makeIdsUnique().
  241. function makeIdsUniqueCached(svgString) {
  242. return svgString.replace(ID_SUFFIX_REGEX, ID_SUFFIX + uniqueIdCounter++);
  243. }
  244. // Inject SVG by replacing the img element with the SVG element in the DOM
  245. function inject(imgElem, svgElem, absUrl, options) {
  246. if (svgElem) {
  247. svgElem[_SET_ATTRIBUTE_]('data-inject-url', absUrl);
  248. var parentNode = imgElem.parentNode;
  249. if (parentNode) {
  250. if (options.copyAttributes) {
  251. copyAttributes(imgElem, svgElem);
  252. }
  253. // Invoke beforeInject hook if set
  254. var beforeInject = options.beforeInject;
  255. var injectElem = (beforeInject && beforeInject(imgElem, svgElem)) || svgElem;
  256. // Replace img element with new element. This is the actual injection.
  257. parentNode.replaceChild(injectElem, imgElem);
  258. // Mark img element as injected
  259. imgElem[__SVGINJECT] = INJECTED;
  260. removeOnLoadAttribute(imgElem);
  261. // Invoke afterInject hook if set
  262. var afterInject = options.afterInject;
  263. if (afterInject) {
  264. afterInject(imgElem, injectElem);
  265. }
  266. }
  267. } else {
  268. svgInvalid(imgElem, options);
  269. }
  270. }
  271. // Merges any number of options objects into a new object
  272. function mergeOptions() {
  273. var mergedOptions = {};
  274. var args = arguments;
  275. // Iterate over all specified options objects and add all properties to the new options object
  276. for (var i = 0; i < args[_LENGTH_]; i++) {
  277. var argument = args[i];
  278. for (var key in argument) {
  279. if (argument.hasOwnProperty(key)) {
  280. mergedOptions[key] = argument[key];
  281. }
  282. }
  283. }
  284. return mergedOptions;
  285. }
  286. // Adds the specified CSS to the document's <head> element
  287. function addStyleToHead(css) {
  288. var head = document[_GET_ELEMENTS_BY_TAG_NAME_]('head')[0];
  289. if (head) {
  290. var style = document[_CREATE_ELEMENT_](_STYLE_);
  291. style.type = 'text/css';
  292. style.appendChild(document.createTextNode(css));
  293. head.appendChild(style);
  294. }
  295. }
  296. // Builds an SVG element from the specified SVG string
  297. function buildSvgElement(svgStr, verify) {
  298. if (verify) {
  299. var svgDoc;
  300. try {
  301. // Parse the SVG string with DOMParser
  302. svgDoc = svgStringToSvgDoc(svgStr);
  303. } catch(e) {
  304. return NULL;
  305. }
  306. if (svgDoc[_GET_ELEMENTS_BY_TAG_NAME_]('parsererror')[_LENGTH_]) {
  307. // DOMParser does not throw an exception, but instead puts parsererror tags in the document
  308. return NULL;
  309. }
  310. return svgDoc.documentElement;
  311. } else {
  312. var div = document.createElement('div');
  313. div.innerHTML = svgStr;
  314. return div.firstElementChild;
  315. }
  316. }
  317. function removeOnLoadAttribute(imgElem) {
  318. // Remove the onload attribute. Should only be used to remove the unstyled image flash protection and
  319. // make the element visible, not for removing the event listener.
  320. imgElem.removeAttribute('onload');
  321. }
  322. function errorMessage(msg) {
  323. console.error('SVGInject: ' + msg);
  324. }
  325. function fail(imgElem, status, options) {
  326. imgElem[__SVGINJECT] = FAIL;
  327. if (options.onFail) {
  328. options.onFail(imgElem, status);
  329. } else {
  330. errorMessage(status);
  331. }
  332. }
  333. function svgInvalid(imgElem, options) {
  334. removeOnLoadAttribute(imgElem);
  335. fail(imgElem, SVG_INVALID, options);
  336. }
  337. function svgNotSupported(imgElem, options) {
  338. removeOnLoadAttribute(imgElem);
  339. fail(imgElem, SVG_NOT_SUPPORTED, options);
  340. }
  341. function loadFail(imgElem, options) {
  342. fail(imgElem, LOAD_FAIL, options);
  343. }
  344. function removeEventListeners(imgElem) {
  345. imgElem.onload = NULL;
  346. imgElem.onerror = NULL;
  347. }
  348. function imgNotSet(msg) {
  349. errorMessage('no img element');
  350. }
  351. function createSVGInject(globalName, options) {
  352. var defaultOptions = mergeOptions(DEFAULT_OPTIONS, options);
  353. var svgLoadCache = {};
  354. if (IS_SVG_SUPPORTED) {
  355. // If the browser supports SVG, add a small stylesheet that hides the <img> elements until
  356. // injection is finished. This avoids showing the unstyled SVGs before style is applied.
  357. addStyleToHead('img[onload^="' + globalName + '("]{visibility:hidden;}');
  358. }
  359. /**
  360. * SVGInject
  361. *
  362. * Injects the SVG specified in the `src` attribute of the specified `img` element or array of `img`
  363. * elements. Returns a Promise object which resolves if all passed in `img` elements have either been
  364. * injected or failed to inject (Only if a global Promise object is available like in all modern browsers
  365. * or through a polyfill).
  366. *
  367. * Options:
  368. * useCache: If set to `true` the SVG will be cached using the absolute URL. Default value is `true`.
  369. * copyAttributes: If set to `true` the attributes will be copied from `img` to `svg`. Dfault value
  370. * is `true`.
  371. * makeIdsUnique: If set to `true` the ID of elements in the `<defs>` element that can be references by
  372. * property values (for example 'clipPath') are made unique by appending "--inject-X", where X is a
  373. * running number which increases with each injection. This is done to avoid duplicate IDs in the DOM.
  374. * beforeLoad: Hook before SVG is loaded. The `img` element is passed as a parameter. If the hook returns
  375. * a string it is used as the URL instead of the `img` element's `src` attribute.
  376. * afterLoad: Hook after SVG is loaded. The loaded `svg` element and `svg` string are passed as a
  377. * parameters. If caching is active this hook will only get called once for injected SVGs with the
  378. * same absolute path. Changes to the `svg` element in this hook will be applied to all injected SVGs
  379. * with the same absolute path. It's also possible to return an `svg` string or `svg` element which
  380. * will then be used for the injection.
  381. * beforeInject: Hook before SVG is injected. The `img` and `svg` elements are passed as parameters. If
  382. * any html element is returned it gets injected instead of applying the default SVG injection.
  383. * afterInject: Hook after SVG is injected. The `img` and `svg` elements are passed as parameters.
  384. * onAllFinish: Hook after all `img` elements passed to an SVGInject() call have either been injected or
  385. * failed to inject.
  386. * onFail: Hook after injection fails. The `img` element and a `status` string are passed as an parameter.
  387. * The `status` can be either `'SVG_NOT_SUPPORTED'` (the browser does not support SVG),
  388. * `'SVG_INVALID'` (the SVG is not in a valid format) or `'LOAD_FAILED'` (loading of the SVG failed).
  389. *
  390. * @param {HTMLImageElement} img - an img element or an array of img elements
  391. * @param {Object} [options] - optional parameter with [options](#options) for this injection.
  392. */
  393. function SVGInject(img, options) {
  394. options = mergeOptions(defaultOptions, options);
  395. var run = function(resolve) {
  396. var allFinish = function() {
  397. var onAllFinish = options.onAllFinish;
  398. if (onAllFinish) {
  399. onAllFinish();
  400. }
  401. resolve && resolve();
  402. };
  403. if (img && typeof img[_LENGTH_] != _UNDEFINED_) {
  404. // an array like structure of img elements
  405. var injectIndex = 0;
  406. var injectCount = img[_LENGTH_];
  407. if (injectCount == 0) {
  408. allFinish();
  409. } else {
  410. var finish = function() {
  411. if (++injectIndex == injectCount) {
  412. allFinish();
  413. }
  414. };
  415. for (var i = 0; i < injectCount; i++) {
  416. SVGInjectElement(img[i], options, finish);
  417. }
  418. }
  419. } else {
  420. // only one img element
  421. SVGInjectElement(img, options, allFinish);
  422. }
  423. };
  424. // return a Promise object if globally available
  425. return typeof Promise == _UNDEFINED_ ? run() : new Promise(run);
  426. }
  427. // Injects a single svg element. Options must be already merged with the default options.
  428. function SVGInjectElement(imgElem, options, callback) {
  429. if (imgElem) {
  430. var svgInjectAttributeValue = imgElem[__SVGINJECT];
  431. if (!svgInjectAttributeValue) {
  432. removeEventListeners(imgElem);
  433. if (!IS_SVG_SUPPORTED) {
  434. svgNotSupported(imgElem, options);
  435. callback();
  436. return;
  437. }
  438. // Invoke beforeLoad hook if set. If the beforeLoad returns a value use it as the src for the load
  439. // URL path. Else use the imgElem's src attribute value.
  440. var beforeLoad = options.beforeLoad;
  441. var src = (beforeLoad && beforeLoad(imgElem)) || imgElem[_GET_ATTRIBUTE_]('src');
  442. if (!src) {
  443. // If no image src attribute is set do no injection. This can only be reached by using javascript
  444. // because if no src attribute is set the onload and onerror events do not get called
  445. if (src === '') {
  446. loadFail(imgElem, options);
  447. }
  448. callback();
  449. return;
  450. }
  451. // set array so later calls can register callbacks
  452. var onFinishCallbacks = [];
  453. imgElem[__SVGINJECT] = onFinishCallbacks;
  454. var onFinish = function() {
  455. callback();
  456. onFinishCallbacks.forEach(function(onFinishCallback) {
  457. onFinishCallback();
  458. });
  459. };
  460. var absUrl = getAbsoluteUrl(src);
  461. var useCacheOption = options.useCache;
  462. var makeIdsUniqueOption = options.makeIdsUnique;
  463. var setSvgLoadCacheValue = function(val) {
  464. if (useCacheOption) {
  465. svgLoadCache[absUrl].forEach(function(svgLoad) {
  466. svgLoad(val);
  467. });
  468. svgLoadCache[absUrl] = val;
  469. }
  470. };
  471. if (useCacheOption) {
  472. var svgLoad = svgLoadCache[absUrl];
  473. var handleLoadValue = function(loadValue) {
  474. if (loadValue === LOAD_FAIL) {
  475. loadFail(imgElem, options);
  476. } else if (loadValue === SVG_INVALID) {
  477. svgInvalid(imgElem, options);
  478. } else {
  479. var hasUniqueIds = loadValue[0];
  480. var svgString = loadValue[1];
  481. var uniqueIdsSvgString = loadValue[2];
  482. var svgElem;
  483. if (makeIdsUniqueOption) {
  484. if (hasUniqueIds === NULL) {
  485. // IDs for the SVG string have not been made unique before. This may happen if previous
  486. // injection of a cached SVG have been run with the option makedIdsUnique set to false
  487. svgElem = buildSvgElement(svgString, false);
  488. hasUniqueIds = makeIdsUnique(svgElem, false);
  489. loadValue[0] = hasUniqueIds;
  490. loadValue[2] = hasUniqueIds && svgElemToSvgString(svgElem);
  491. } else if (hasUniqueIds) {
  492. // Make IDs unique for already cached SVGs with better performance
  493. svgString = makeIdsUniqueCached(uniqueIdsSvgString);
  494. }
  495. }
  496. svgElem = svgElem || buildSvgElement(svgString, false);
  497. inject(imgElem, svgElem, absUrl, options);
  498. }
  499. onFinish();
  500. };
  501. if (typeof svgLoad != _UNDEFINED_) {
  502. // Value for url exists in cache
  503. if (svgLoad.isCallbackQueue) {
  504. // Same url has been cached, but value has not been loaded yet, so add to callbacks
  505. svgLoad.push(handleLoadValue);
  506. } else {
  507. handleLoadValue(svgLoad);
  508. }
  509. return;
  510. } else {
  511. var svgLoad = [];
  512. // set property isCallbackQueue to Array to differentiate from array with cached loaded values
  513. svgLoad.isCallbackQueue = true;
  514. svgLoadCache[absUrl] = svgLoad;
  515. }
  516. }
  517. // Load the SVG because it is not cached or caching is disabled
  518. loadSvg(absUrl, function(svgXml, svgString) {
  519. // Use the XML from the XHR request if it is an instance of Document. Otherwise
  520. // (for example of IE9), create the svg document from the svg string.
  521. var svgElem = svgXml instanceof Document ? svgXml.documentElement : buildSvgElement(svgString, true);
  522. var afterLoad = options.afterLoad;
  523. if (afterLoad) {
  524. // Invoke afterLoad hook which may modify the SVG element. After load may also return a new
  525. // svg element or svg string
  526. var svgElemOrSvgString = afterLoad(svgElem, svgString) || svgElem;
  527. if (svgElemOrSvgString) {
  528. // Update svgElem and svgString because of modifications to the SVG element or SVG string in
  529. // the afterLoad hook, so the modified SVG is also used for all later cached injections
  530. var isString = typeof svgElemOrSvgString == 'string';
  531. svgString = isString ? svgElemOrSvgString : svgElemToSvgString(svgElem);
  532. svgElem = isString ? buildSvgElement(svgElemOrSvgString, true) : svgElemOrSvgString;
  533. }
  534. }
  535. if (svgElem instanceof SVGElement) {
  536. var hasUniqueIds = NULL;
  537. if (makeIdsUniqueOption) {
  538. hasUniqueIds = makeIdsUnique(svgElem, false);
  539. }
  540. if (useCacheOption) {
  541. var uniqueIdsSvgString = hasUniqueIds && svgElemToSvgString(svgElem);
  542. // set an array with three entries to the load cache
  543. setSvgLoadCacheValue([hasUniqueIds, svgString, uniqueIdsSvgString]);
  544. }
  545. inject(imgElem, svgElem, absUrl, options);
  546. } else {
  547. svgInvalid(imgElem, options);
  548. setSvgLoadCacheValue(SVG_INVALID);
  549. }
  550. onFinish();
  551. }, function() {
  552. loadFail(imgElem, options);
  553. setSvgLoadCacheValue(LOAD_FAIL);
  554. onFinish();
  555. });
  556. } else {
  557. if (Array.isArray(svgInjectAttributeValue)) {
  558. // svgInjectAttributeValue is an array. Injection is not complete so register callback
  559. svgInjectAttributeValue.push(callback);
  560. } else {
  561. callback();
  562. }
  563. }
  564. } else {
  565. imgNotSet();
  566. }
  567. }
  568. /**
  569. * Sets the default [options](#options) for SVGInject.
  570. *
  571. * @param {Object} [options] - default [options](#options) for an injection.
  572. */
  573. SVGInject.setOptions = function(options) {
  574. defaultOptions = mergeOptions(defaultOptions, options);
  575. };
  576. // Create a new instance of SVGInject
  577. SVGInject.create = createSVGInject;
  578. /**
  579. * Used in onerror Event of an `<img>` element to handle cases when the loading the original src fails
  580. * (for example if file is not found or if the browser does not support SVG). This triggers a call to the
  581. * options onFail hook if available. The optional second parameter will be set as the new src attribute
  582. * for the img element.
  583. *
  584. * @param {HTMLImageElement} img - an img element
  585. * @param {String} [fallbackSrc] - optional parameter fallback src
  586. */
  587. SVGInject.err = function(img, fallbackSrc) {
  588. if (img) {
  589. if (img[__SVGINJECT] != FAIL) {
  590. removeEventListeners(img);
  591. if (!IS_SVG_SUPPORTED) {
  592. svgNotSupported(img, defaultOptions);
  593. } else {
  594. removeOnLoadAttribute(img);
  595. loadFail(img, defaultOptions);
  596. }
  597. if (fallbackSrc) {
  598. removeOnLoadAttribute(img);
  599. img.src = fallbackSrc;
  600. }
  601. }
  602. } else {
  603. imgNotSet();
  604. }
  605. };
  606. window[globalName] = SVGInject;
  607. return SVGInject;
  608. }
  609. var SVGInjectInstance = createSVGInject('SVGInject');
  610. if (typeof module == 'object' && typeof module.exports == 'object') {
  611. module.exports = SVGInjectInstance;
  612. }
  613. })(window, document);