Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
187 views
in Technique[技术] by (71.8m points)

javascript - Include SVG files with HTML, and still be able to apply styles to them?

You can include SVG files into your HTML file via embed object and svg tags.

  • Using an embed or object tag requires linking the image with a URL. (This is what I prefer, because I don't like all that SVG code in my HTML code, so I’d like to keep it that way.)
  • Using the svg tag (AFAIK) requires having the SVG code inline in your HTML code.

My question is:

How do I include SVG icons, images, and other files into my HTML file, without having to put the entire SVG code in, and still be able to apply styles to them? Applying them through JS is fine too.

Note:

When I include them via object or embed, I can't seem to access them through jQuery, even with $("#my-svg-div").find("svg") (which, by the way, almost every answer on SO says I should). I just get undefined.

Thank you!

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

Short Answer

You can programmatically inline an SVG image. Such an image can be treated essentially identically to an actual inline <svg> element, including being able to have styles applied to it.

If your SVG image is referenced in an <object> or <iframe> element (e), you can inline it as follows:

e.parentElement.replaceChild(e.contentDocument.documentElement.cloneNode(true), e);

If your SVG image is referenced in an <embed> element, replace .contentDocument in the above code with .getSVGDocument().

If your SVG image is referenced in an <img> element, a completely different strategy, involving AJAX and described below, can be used to inline the image.

General Strategy

If your external SVG image files are same-origin (e.g. the images are loaded from the same place as the HTML code), then one approach that allows styling of these images is to programmatically inline them as follows:

  • Retrieve the content of the external SVG file.
  • Add that content directly back to your HTML file in the same HTML DOM location as the original referencing element, i.e. put it "in-line".
  • Delete the element that originally referenced the external SVG file.

Benefits

This inlining strategy gives you the best of two worlds:

  1. You get the benefits of separate image files, including:

    • organizing your image files independently of the HTML,
    • keeping your original HTML file uncluttered from image details, and
    • (potentially) allowing the browser to cache the images (but see below regarding this last point).
  2. Yet you can still do anything to the eventually-inlined SVG images that you could do to <svg> elements that were truly originally inline, including:

    • applying CSS stylings to them,
    • applying event listeners to individual SVG shapes or groups, etc.

Implementation

For <object> or <iframe> elements:

You can inline externally referenced SVG code as follows:

// using vanilla JavaScript (as shown above):
e.parentElement.replaceChild(e.contentDocument.documentElement.cloneNode(true), e);

// using jQuery:
$e.replaceWith($($e[0].contentDocument.documentElement).clone());

...where e or $e are the vanilla or jQuery variables (respectively) in which you have selected an external-SVG-referencing <object> or <iframe> element.

To include <embed> elements:

If, instead, you are using an external-SVG-referencing <embed> element, then you can inline the SVG content by replacing .contentDocument in the above code with .getSVGDocument() (note the additional parentheses). Note that .contentDocument does not work with <embed> elements while .getSVGDocument() actually works with all three element types. However .getSVGDocument() is deprecated and so should only be used if you really need <embed> elements.

To include <img> elements:

Neither of the above strategies works for <img> elements. To inline these, you can retrieve the src attribute of the <img> element, make an AJAX request for that file, create a new <svg> element using the retrieved SVG code and replace the original <img> element with the new <svg> element. If you want this strategy to work for all four element types, then just be aware that the URL of the referenced SVG image is held in the src attribute of <iframe>, <embed> and <img> elements but in the data attribute of an <object> element. This strategy can be implemented as follows:

// using vanilla JavaScript:
var xhr = new XMLHttpRequest();
xhr.open("GET", e.getAttribute(e.nodeName === "OBJECT" ? "data" : "src");
xhr.send();
xhr.onreadystatechange = function() {
  if (xhr.readyState === XMLHttpRequest.DONE && xhr.status === 200) {
    e.outerHTML = xhr.responseText;
  }
};

// using jQuery:
$.get($e.attr($e.prop("nodeName") === "OBJECT" ? "data" : "src"), function(data) {
  $e.replaceWith(data.documentElement);
});

Example

The following example demonstrates where the above strategy does and does not allow external CSS stylings to be applied to originally-external SVG images. (I didn't create a code snippet or jsfiddle because of the need to reference local external files.)

The following two screenshots show the CSS styling (red triangles) or lack thereof (black triangles) before and after inlining. It shows the results for SVG images originally embedded in the HTML (<svg>) or referenced in the indicated elements (<object>, <iframe>, <embed>, and <img>). The three rows show the results of inlining using the three indicated strategies.

Before clicking the button, no inlining has yet been attempted, and the screen looks like this. Only embedded SVG elements (the 1st column) are styled:

svg polygon styling by CSS before svg inlining

After clicking the button, inlining is attempted, and the screen now looks like this. CSS styling has been successfully applied to some of the elements:

svg polygon styling by CSS after svg inlining

The code required for this example follows:

image.svg (externally referenced file, i.e. not embedded in the HTML):

<svg xmlns="http://www.w3.org/2000/svg" width="50px" height="50px">
  <polygon points="25,5 45,45 5,45 25,5"/>
</svg>

index.html (obviously, remove the jQuery script line if not using jQuery):

<!DOCTYPE html>
  <head>
    <link href="styles.css" rel="stylesheet">
    <script src="//code.jquery.com/jquery-1.12.0.min.js"></script>
    <script src="main.js"></script>
  </head>
<body>
  <button>Click to attempt to inline svg images.</button>
  <table>
    <tr>
      <th></th>
      <th>svg   </th>
      <th>object</th>
      <th>iframe</th>
      <th>embed </th>
      <th>img   </th>
    </tr>
    <tr>
      <td>contentDocument</td>
      <td><svg  xmlns="http://www.w3.org/2000/svg" width="50" height="50"><polygon points="25,5 45,45 5,45 25,5"/></svg></td>
      <td><object data="image.svg" type="image/svg+xml"></object></td>
      <td><iframe  src="image.svg" width="50" height="50" style="border: none;"></iframe></td>
      <td><embed   src="image.svg" type="image/svg+xml" /></td>
      <td><img     src="image.svg" /></td>
    </tr>
    <tr>
      <td>getSVGDocument()<br />(deprecated)</td>
      <td><svg  xmlns="http://www.w3.org/2000/svg" width="50" height="50"><polygon points="25,5 45,45 5,45 25,5"/></svg></td>
      <td><object data="image.svg" type="image/svg+xml"></object></td>
      <td><iframe  src="image.svg" width="50" height="50" style="border: none;"></iframe></td>
      <td><embed   src="image.svg" type="image/svg+xml" /></td>
      <td><img     src="image.svg" /></td>
    </tr>
    <tr>
      <td>XMLHttpRequest</td>
      <td><svg  xmlns="http://www.w3.org/2000/svg" width="50" height="50"><polygon points="25,5 45,45 5,45 25,5"/></svg></td>
      <td><object data="image.svg" type="image/svg+xml"></object></td>
      <td><iframe  src="image.svg" width="50" height="50" style="border: none;"></iframe></td>
      <td><embed   src="image.svg" type="image/svg+xml" /></td>
      <td><img     src="image.svg" /></td>
    </tr>
  </table>
</body>
</html>

styles.css (only the polygon line is important for demonstrating the inlining):

polygon {fill: red;}
table {border-collapse: collapse;}
td, th {border: solid black 1px; padding: 0.4em;}

main.js (jQuery version):

$(document).ready(function() {
  $("button").click(function() {
    ["object", "iframe", "embed", "img"].forEach(function(elmtType) {
      var $e, $threeElmts = $(elmtType);
      $e = $($threeElmts[0]);
      if ($e[0].contentDocument) $e.replaceWith($($e[0].contentDocument.documentElement).clone());
      $e = $($threeElmts[1]);
      if ($e[0].getSVGDocument) $e.replaceWith($($e[0].getSVGDocument().documentElement).clone());
      $e = $($threeElmts[2]);
      $.get($e.attr($e.prop("nodeName") === "OBJECT" ? "data" : "src"), function(data) {
        $e.replaceWith(data.documentElement);
      });
    });
  });
});

main.js (vanilla JavaScript version):

document.addEventListener("DOMContentLoaded", function() {
  document.querySelector("button").addEventListener("click", function() {
    ["object", "iframe", "embed", "img"].forEach(function(elmtType) {
      var e, threeElmts = document.querySelectorAll(elmtType);
      e = threeElmts[0];
      if (e.contentDocument) e.parentElement.replaceChild(e.contentDocument.documentElement.cloneNode(true), e);
      e = threeElmts[1];
      if (e.getSVGDocument) e.parentElement.replaceChild(e.getSVGDocument().documentElement.cloneNode(true), e);
      e = threeElmts[2];
      var xhr = new XMLHttpRequest();
      xhr.open("GET", e.getAttribute(e.nodeName === "OBJECT" ? "data" : "src"));
      xhr.send();
      xhr.onreadystatechange = function() {
        if (xhr.readyState === XMLHttpRequest.DONE && xhr.status === 200) e.outerHTML = xhr.responseText;
      };
    });
  });
});

Note the following:

  • This strategy will require you to deal with any characteristics associated with the original referencing elements, e.g. fallback images, id and class (or any other) attributes, event listeners, iframe functionality, etc.
  • Ensure that you only attempt inlining after the image file has actually been loaded.
  • Consider inlining on the server-side to allow serving of a single file, but inlining on the client-side to allow image file caching.
  • I verified this strategy in Firefox 44.0, Chrome 49.0 and Opera 35.0 (Mac and Windows), Safari 9.0 (Mac) and IE 11 (Windows). I did not check Edge or any mobile

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

1.4m articles

1.4m replys

5 comments

57.0k users

...