Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow arbitrarily nested <use> elements #117

Closed
wants to merge 5 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 14 additions & 13 deletions dist/svg4everybody.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,14 @@
"function" == typeof define && define.amd ? // AMD. Register as an anonymous module unless amdModuleId is set
define([], function() {
return root.svg4everybody = factory();
}) : "object" == typeof exports ? // Node. Does not work with strict CommonJS, but
// only CommonJS-like environments that support module.exports,
// like Node.
module.exports = factory() : root.svg4everybody = factory();
}) : "object" == typeof exports ? module.exports = factory() : root.svg4everybody = factory();
}(this, function() {
/*! svg4everybody v2.1.0 | github.com/jonathantneal/svg4everybody */
function embed(svg, target) {
function embed(node, target) {
// if the target exists
if (target) {
// create a document fragment to hold the contents of the target
var fragment = document.createDocumentFragment(), viewBox = !svg.getAttribute("viewBox") && target.getAttribute("viewBox");
var fragment = document.createDocumentFragment(), svg = getSVGAncestor(node), viewBox = !svg.hasAttribute("viewBox") && target.getAttribute("viewBox");
// conditionally set the viewBox on the svg
viewBox && svg.setAttribute("viewBox", viewBox);
// copy the contents of the clone into the fragment
Expand All @@ -21,7 +18,7 @@
fragment.appendChild(clone.firstChild);
}
// append the fragment into the svg
svg.appendChild(fragment);
node.appendChild(fragment);
}
}
function loadreadystatechange(xhr) {
Expand All @@ -40,7 +37,7 @@
// ensure the cached target
target || (target = xhr._cachedTarget[item.id] = cachedDocument.getElementById(item.id)),
// embed the target into the svg
embed(item.svg, target);
embed(item.node, target);
});
}
}, // test the ready state change immediately
Expand All @@ -52,12 +49,12 @@
for (// get the cached <use> index
var index = 0; index < uses.length; ) {
// get the current <use>
var use = uses[index], svg = use.parentNode;
if (svg && /svg/i.test(svg.nodeName)) {
var use = uses[index], parent = use.parentNode, svg = getSVGAncestor(parent);
if (svg) {
var src = use.getAttribute("xlink:href");
if (polyfill && (!opts.validate || opts.validate(src, svg, use))) {
// remove the <use> element
svg.removeChild(use);
parent.removeChild(use);
// parse the src and get the url and id
var srcSplit = src.split("#"), url = srcSplit.shift(), id = srcSplit.join("#");
// if the link is external
Expand All @@ -68,13 +65,13 @@
xhr || (xhr = requests[url] = new XMLHttpRequest(), xhr.open("GET", url), xhr.send(),
xhr._embeds = []), // add the svg and id as an item to the xhr embeds list
xhr._embeds.push({
svg: svg,
node: parent,
id: id
}), // prepare the xhr ready state change event
loadreadystatechange(xhr);
} else {
// embed the local id into the svg
embed(svg, document.getElementById(id));
embed(parent, document.getElementById(id));
}
}
} else {
Expand All @@ -92,5 +89,9 @@
// conditionally start the interval if the polyfill is active
polyfill && oninterval();
}
function getSVGAncestor(node) {
for (var svg = node; "svg" !== svg.nodeName.toLowerCase() && (svg = svg.parentNode); ) {}
return svg;
}
return svg4everybody;
});
34 changes: 16 additions & 18 deletions dist/svg4everybody.legacy.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,14 @@
"function" == typeof define && define.amd ? // AMD. Register as an anonymous module unless amdModuleId is set
define([], function() {
return root.svg4everybody = factory();
}) : "object" == typeof exports ? // Node. Does not work with strict CommonJS, but
// only CommonJS-like environments that support module.exports,
// like Node.
module.exports = factory() : root.svg4everybody = factory();
}) : "object" == typeof exports ? module.exports = factory() : root.svg4everybody = factory();
}(this, function() {
/*! svg4everybody v2.1.0 | github.com/jonathantneal/svg4everybody */
function embed(svg, target) {
function embed(node, target) {
// if the target exists
if (target) {
// create a document fragment to hold the contents of the target
var fragment = document.createDocumentFragment(), viewBox = !svg.getAttribute("viewBox") && target.getAttribute("viewBox");
var fragment = document.createDocumentFragment(), svg = getSVGAncestor(node), viewBox = !svg.hasAttribute("viewBox") && target.getAttribute("viewBox");
// conditionally set the viewBox on the svg
viewBox && svg.setAttribute("viewBox", viewBox);
// copy the contents of the clone into the fragment
Expand All @@ -21,7 +18,7 @@
fragment.appendChild(clone.firstChild);
}
// append the fragment into the svg
svg.appendChild(fragment);
node.appendChild(fragment);
}
}
function loadreadystatechange(xhr) {
Expand All @@ -40,7 +37,7 @@
// ensure the cached target
target || (target = xhr._cachedTarget[item.id] = cachedDocument.getElementById(item.id)),
// embed the target into the svg
embed(item.svg, target);
embed(item.node, target);
});
}
}, // test the ready state change immediately
Expand All @@ -52,8 +49,8 @@
for (// get the cached <use> index
var index = 0; index < uses.length; ) {
// get the current <use>
var use = uses[index], svg = use.parentNode;
if (svg && /svg/i.test(svg.nodeName)) {
var use = uses[index], parent = use.parentNode, svg = getSVGAncestor(parent);
if (svg) {
var src = use.getAttribute("xlink:href");
// if running with legacy support
if (nosvg) {
Expand All @@ -64,11 +61,11 @@
img.setAttribute("width", svg.getAttribute("width") || svg.clientWidth), img.setAttribute("height", svg.getAttribute("height") || svg.clientHeight),
// set the fallback src
img.src = fallback(src, svg, use), // replace the <use> with the fallback image
svg.replaceChild(img, use);
parent.replaceChild(img, use);
} else {
if (polyfill && (!opts.validate || opts.validate(src, svg, use))) {
// remove the <use> element
svg.removeChild(use);
parent.removeChild(use);
// parse the src and get the url and id
var srcSplit = src.split("#"), url = srcSplit.shift(), id = srcSplit.join("#");
// if the link is external
Expand All @@ -79,13 +76,13 @@
xhr || (xhr = requests[url] = new XMLHttpRequest(), xhr.open("GET", url), xhr.send(),
xhr._embeds = []), // add the svg and id as an item to the xhr embeds list
xhr._embeds.push({
svg: svg,
node: parent,
id: id
}), // prepare the xhr ready state change event
loadreadystatechange(xhr);
} else {
// embed the local id into the svg
embed(svg, document.getElementById(id));
embed(parent, document.getElementById(id));
}
}
}
Expand All @@ -98,12 +95,9 @@
requestAnimationFrame(oninterval, 67);
}
var nosvg, fallback, opts = Object(rawopts);
// configure the fallback method
fallback = opts.fallback || function(src) {
return src.replace(/\?[^#]+/, "").replace("#", ".").replace(/^\./, "") + ".png" + (/\?[^#]+/.exec(src) || [ "" ])[0];
}, // set whether to shiv <svg> and <use> elements and use image fallbacks
nosvg = "nosvg" in opts ? opts.nosvg : /\bMSIE [1-8]\b/.test(navigator.userAgent),
// conditionally shiv <svg> and <use>
}, nosvg = "nosvg" in opts ? opts.nosvg : /\bMSIE [1-8]\b/.test(navigator.userAgent),
nosvg && (document.createElement("svg"), document.createElement("use"));
// set whether the polyfill will be activated or not
var polyfill, olderIEUA = /\bMSIE [1-8]\.0\b/, newerIEUA = /\bTrident\/[567]\b|\bMSIE (?:9|10)\.0\b/, webkitUA = /\bAppleWebKit\/(\d+)\b/, olderEdgeUA = /\bEdge\/12\.(\d+)\b/;
Expand All @@ -113,5 +107,9 @@
// conditionally start the interval if the polyfill is active
polyfill && oninterval();
}
function getSVGAncestor(node) {
for (var svg = node; "svg" !== svg.nodeName.toLowerCase() && (svg = svg.parentNode); ) {}
return svg;
}
return svg4everybody;
});
2 changes: 1 addition & 1 deletion dist/svg4everybody.legacy.min.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion dist/svg4everybody.min.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

34 changes: 24 additions & 10 deletions lib/svg4everybody.js
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
/*! svg4everybody v2.1.0 | github.com/jonathantneal/svg4everybody */

function embed(svg, target) {
function embed(node, target) {
// if the target exists
if (target) {
// create a document fragment to hold the contents of the target
var fragment = document.createDocumentFragment();

var svg = getSVGAncestor(node);

// cache the closest matching viewBox
var viewBox = !svg.getAttribute('viewBox') && target.getAttribute('viewBox');
var viewBox = !svg.hasAttribute('viewBox') && target.getAttribute('viewBox');

// conditionally set the viewBox on the svg
if (viewBox) {
Expand All @@ -23,7 +25,7 @@ function embed(svg, target) {
}

// append the fragment into the svg
svg.appendChild(fragment);
node.appendChild(fragment);
}
}

Expand Down Expand Up @@ -55,7 +57,7 @@ function loadreadystatechange(xhr) {
}

// embed the target into the svg
embed(item.svg, target);
embed(item.node, target);
});
}
};
Expand Down Expand Up @@ -122,9 +124,10 @@ function svg4everybody(rawopts) {
var use = uses[index];

// get the current <svg>
var svg = use.parentNode;
var parent = use.parentNode;
var svg = getSVGAncestor(parent);

if (svg && /svg/i.test(svg.nodeName)) {
if (svg) {
var src = use.getAttribute('xlink:href');

// if running with legacy support
Expand All @@ -143,11 +146,11 @@ function svg4everybody(rawopts) {
img.src = fallback(src, svg, use);

// replace the <use> with the fallback image
svg.replaceChild(img, use);
parent.replaceChild(img, use);
} else if (polyfill) {
if (!opts.validate || opts.validate(src, svg, use)) {
// remove the <use> element
svg.removeChild(use);
parent.removeChild(use);

// parse the src and get the url and id
var srcSplit = src.split('#');
Expand All @@ -172,15 +175,15 @@ function svg4everybody(rawopts) {

// add the svg and id as an item to the xhr embeds list
xhr._embeds.push({
svg: svg,
node: parent,
id: id
});

// prepare the xhr ready state change event
loadreadystatechange(xhr);
} else {
// embed the local id into the svg
embed(svg, document.getElementById(id));
embed(parent, document.getElementById(id));
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Minor: would it be better to pass in both the svg and parent so that we don't have to search through the DOM again? Its likely that svg and use are close together, but its would be more efficient this way. You would have to change the obj being passed into xhr._embeds as well.

}
}
}
Expand All @@ -199,3 +202,14 @@ function svg4everybody(rawopts) {
oninterval();
}
}

function getSVGAncestor(node) {
var svg = node;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Very minor: I think leaving it as node is clearer

while (svg.nodeName.toLowerCase() !== 'svg') {
svg = svg.parentNode;
if (!svg) {
break;
}
}
return svg;
}
Loading