Hello I am working on an SVG/JS map, which consists of many little SVG graphics (City districts). I put every graphic into an own file so that my main SVG file will still be maintainable and not bloated.
How can I reference an external SVG file from another SVG correctly?
Expected result: Open 1.svg in a browser and see a blue rectangle. How it should work: w3c: use element
So this is what I tried: 1.svg:
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<?xml-stylesheet href="style.css" type="text/css"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.0//EN" "http://www.w3.org/TR/2001/REC-SVG- 20010904/DTD/svg10.dtd">
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="1000" height="1000">
<use xlink:href="another.svg#rectangle"/>
</svg>
another.svg:
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.0//EN" "http://www.w3.org/TR/2001/REC-SVG- 20010904/DTD/svg10.dtd">
<svg id="rectangle" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="1000" height="1000">
<rect class="blue" x="558.5" y="570" width="5" height="5" />
</svg>
style.css
.blue { fill: blue; }
Result:
- Firefox: A blue开发者_开发问答 rectangle (exactly what I wanted)
- Chrome: Nothing
- Opera: Black rectangle
Note: I tried it with the image element but that didn't work with the stylesheets i.e. I got a black rectangle not a blue one.
Important: When you want to reference another SVG and want to have the referenced SVG to be part of the formal document structure, you can use AJAX to do that.
https://bugs.webkit.org/show_bug.cgi?id=12499
This answers the original question, but attempts to answer the matter of referencing external SVG files in SVG in broader terms, too.
Lack of SVG support
Six years later, Chrome and Safari still do not allow for the referencing/loading of external SVG files.
This is why <use xlink:href="another.svg#rectangle" class="blue"/>
works in Firefox, but not in WebKit browsers.
All in one file
If the project can afford it, simply put all of the SVG files in one parent HTML or SVG file. This way, it'll work in all three browsers:
- Example 1 · Both SVGs in one file (SVG)
- Example 2 · Both SVGs in one file (HTML)
But then, it's not really external, granted!
To benefit from caching and avoid repeating oneself, we'd like to keep repeatable SVG content in an external file.
Work around: insert the external SVG file via JavaScript
Keep the styles and definitions in one SVG file, keep the SVG geometry in some other file, and simply load the former from the latter via JavaScript.
In pure SVG and pure JavaScript
Define what we'd like to be able to use. styles-and-defs.svg
:
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<style type="text/css" >
<![CDATA[
.blue { fill: blue; }
]]>
</style>
<defs>
<rect id="rectangle" class="blue" width="50" height="50" />
</defs>
</svg>
Use the geometry created above, and load its definition. parent.svg
:
<svg version="1.1"
baseProfile="full"
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
xmlns:ev="http://www.w3.org/2001/xml-events"
width="420" height="120">
<use xlink:href="#rectangle" x="10" y="10" />
<script><![CDATA[
/** When the document is ready, this self-executing function will be run. **/
(function() {
var ajax = new XMLHttpRequest();
ajax.open("GET", "styles-and-defs.svg", true);
ajax.send();
/**
* Append the external SVG to this very SVG.
*
* Notice the use of an SVG selector on the document derived from the AJAX result.
* This is because the full document cannot be included directly into the SVG.
* Trying to include to do so would result in:
* `HierarchyRequestError: Node cannot be inserted at the specified point in the hierarchy` in Firefox;
* `Nodes of type '#document' may not be inserted inside nodes of type 'svg'.` in Chrome.
*/
ajax.onload = function(e) {
var parser = new DOMParser();
var ajaxdoc = parser.parseFromString( ajax.responseText, "image/svg+xml" );
document.getElementsByTagName('svg')[0].appendChild( ajaxdoc.getElementsByTagName('svg')[0] );
}
})(); /* END (anonymous function) */
]]></script>
</svg>
This answers the OP.
In HTML
Same basic approach as in pure SVG:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>
Load external SVG (HTML)
</title>
<meta name="author" content="Fabien Snauwaert">
</head>
<body>
<svg version="1.1"
baseProfile="full"
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
xmlns:ev="http://www.w3.org/2001/xml-events"
width="420" height="120">
<use xlink:href="#rectangle" x="10" y="10" />
</svg>
<script>
/** When the document is ready, this self-executing function will be run. **/
(function() {
var ajax = new XMLHttpRequest();
ajax.open("GET", "styles-and-defs.svg", true);
ajax.send();
/**
* Append the external SVG to this very SVG.
*
* Notice the use of an SVG selector on the document derived from the AJAX result.
* This is because the full cannot be included directly into the SVG.
* Trying to include to do so would result in:
* `HierarchyRequestError: Node cannot be inserted at the specified point in the hierarchy` in Firefox;
* `Nodes of type '#document' may not be inserted inside nodes of type 'svg'.` in Chrome.
*/
ajax.onload = function(e) {
var parser = new DOMParser();
var ajaxdoc = parser.parseFromString( ajax.responseText, "image/svg+xml" );
document.getElementsByTagName('body')[0].appendChild( ajaxdoc.getElementsByTagName('svg')[0] );
}
})(); /* END (anonymous function) */
</script>
</body>
</html>
You could of course use jQuery (or why not the excellent D3.js) to load the file instead.
Remarks
- Mind the use of
<defs>
. I believe this is the nice thing about having an external SVG, you can keep everything neat and organized. (And without it, we'd be displaying the content twice.) - I got rid of
style.css
and simply put the CSS inside of the styles-and-defs file. - If, in the HTML version, you observe a gap between the parent SVG and the window borders, this is because the "invisible" SVG (with the styles and definition), like any other SVG, is an
inline
element. To get rid of this gap, simply setstyle="display: block;"
on that SVG. - Download all examples here.
SVG is great but can appear to be too little supported, while it does allow for some great things. I hope this helps some folks out there.
Tested OK on OS X 10.12.6 in:
- Firefox 59.0.2
- Chrome 66.0.3359.139
- Safari 11.0.1
From the definition in the SVG spec that you linked to:
CSS2 selectors cannot be applied to the (conceptually) cloned DOM tree because its contents are not part of the formal document structure.
That means that your selector in 1.svg doesn't apply to the cloned DOM tree.
So why not simply reference the stylesheet from another.svg instead? That should work in all browsers, and with both <use>
and <image>
.
Another option is to style the <use>
element in the main svg document (1.svg), since style is cascaded down to the cloned tree from there too.
Try to do it this way:
The square:
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="1000" height="1000">
<rect x="558.5" y="570" width="5" height="5" id="rectangle" />
</svg>
Use it:
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<?xml-stylesheet href="style.css" type="text/css"?>
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="1000" height="1000">
<use xlink:href="another.svg#rectangle" class="blue"/>
</svg>
<svg>
element doesn't have xlink:href
attribute, if you need to include an external image use the <image>
element.
If you want to reference a whole SVG file, SVG 2 (when implemented in browsers) will allow to reference another SVG file without any fragment identifier:
New in SVG 2: An
href
without a fragment allows an entire SVG document to be referenced without having to ensure that it has an ID on its root element.
Before:
<!-- my-vector.svg -->
<svg id="icon" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
<circle r="10" cx="12" cy="12" />
</svg>
<use href="my-vector.svg#icon"></use>
After (there will be no need to define id="..."
on the svg
):
<!-- my-vector.svg -->
<svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
<circle r="10" cx="12" cy="12" />
</svg>
<use href="my-vector.svg"></use>
SVG 2 seems to be in the process of development in major browsers (see this Chrome feature and specifically this Chromium issue: Issue 366545: [SVG2] Allow to reference entire files).
精彩评论