Javascript 如何将 SVG 图像动态插入到 HTML 中?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/7981100/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-24 04:16:21  来源:igfitidea点击:

How do I dynamically insert an SVG image into HTML?

javascripthtmlajaxsvg

提问by EML

I have some code that retrieves a scripted svg image from a server via Ajax. I can get the image text back into the browser, but I can't find a way to insert it into the DOM that will actually display it. Can anyone help with this? The svg looks likes this:

我有一些代码可以通过 Ajax 从服务器检索脚本化的 svg 图像。我可以将图像文本返回到浏览器中,但是我找不到将它插入到实际显示它的 DOM 中的方法。有人能帮忙吗?svg 看起来像这样:

<svg id="chart" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" onload="init(evt)">
<script type="application/ecmascript">
<![CDATA[
...lots of code, changes on each Ajax request
//]]>
</script>
<script type="application/ecmascript" xlink:href="js-on-server-1.js"/>
<script type="application/ecmascript" xlink:href="js-on-server-2.js"/>
</svg>

I've tried various things. If I do this:

我尝试了各种事情。如果我这样做:

// xmlhttp.onreadystatechange:
addImage(xmlhttp.responseXML, "somewhere");
...
function addImage(txt, dst_id) {
   var scr = document.createElement("div");

   if("textContent" in scr)
      scr.textContent = txt;  // everybody else
   else
      scr.text = txt;         // IE

   document.getElementById(dst_id).appendChild(scr);
}

Then Opera and Chrome do nothing, and F/F complains "[object XMLDocument]". If I change 'responseXML' to 'responseText', then Opera/Chrome correctly display the entire svg text (not image) in the right place, and F/F still gives the same warning. I've also tried assigning the response to an innerHTML, but that does nothing. Any ideas? Thanks.

然后 Opera 和 Chrome 什么都不做,F/F 抱怨“[object XMLDocument]”。如果我将“responseXML”更改为“responseText”,则 Opera/Chrome 会在正确的位置正确显示整个 svg 文本(而非图像),并且 F/F 仍会给出相同的警告。我也试过将响应分配给一个innerHTML,但这没有任何作用。有任何想法吗?谢谢。

EDIT

编辑

In response to Phrogz'z answer below - I've added two simple svg files. The first is a 'standard' simple svg, displaying a circle. The second is a scripted svg, displaying a rectangle. You should be able to view both directly in any browser, except IE8-. If I edit Phrogz'z code to use the circle file (replace 'stirling4.svg' with the name of this file), then it works, but if I want the scripted rectangle instead, it doesn't. Tested on F/F, Opera, Chromium, but doesn't work anyway on (my) Chromium.

为了回应下面的 Phrogz'z 回答 - 我添加了两个简单的 svg 文件。第一个是“标准”简单 svg,显示一个圆圈。第二个是脚本化的 svg,显示一个矩形。您应该能够在任何浏览器中直接查看,IE8-除外。如果我编辑 Phrogz'z 代码以使用圆形文件(将 'stirling4.svg' 替换为该文件的名称),则它可以工作,但如果我想要脚本矩形,则不能。在 F/F、Opera、Chromium 上进行了测试,但在(我的)Chromium 上无论如何都不起作用。

File 1, circle:

文件1,圆圈:

<svg xmlns="http://www.w3.org/2000/svg" version="1.1">
  <circle cx="100" cy="50" r="40" stroke="black" stroke-width="2" fill="red" />
</svg>

File 2, rectangle:

文件 2,矩形:

<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" onload="init(evt)">
<script type="application/ecmascript">
<![CDATA[
var svgDocument;
var svgns = "http://www.w3.org/2000/svg";
function init(evt) {
  if(window.svgDocument == null)
    svgDocument = evt.target.ownerDocument;
   var lbox = svgDocument.createElementNS(svgns, "rect");
   lbox.setAttributeNS(null, "x",                10);
   lbox.setAttributeNS(null, "y",                10);
   lbox.setAttributeNS(null, "width",            30);
   lbox.setAttributeNS(null, "height",           30);
   lbox.setAttributeNS(null, "stroke",           "#8080ff");
   lbox.setAttributeNS(null, "stroke-width",     2);
   lbox.setAttributeNS(null, "fill-opacity",     0);
   lbox.setAttributeNS(null, "stroke-opacity",   1);
   lbox.setAttributeNS(null, "stroke-dasharray", 0);
   svgDocument.documentElement.appendChild(lbox);
}
//]]>
</script>
</svg>

Presumably the answer is to get the script into the header??

大概答案是将脚本放入标题中?

回答by Phrogz

In general, the problem is twofoldthreefold:

一般来说,问题是双重的

  1. HTML is not XHTML, and support for SVG in HTML is shoddy and poorly-defined as of this writing. The solution is to use a real XHTML document where SVG-namespaced elements are actually treated as SVG.

  2. The responseXMLis in another DOM document, and you can't normally just move nodes from one document to another. You are supposed to use document.importNodeto import a node from one document to another.

  3. Loading an SVG file with onloadevent handlers will not have those handlers invoked by either creating the node or appending it to the document. Code inside the scriptblock will be run, however, so you need to rewrite your scripts in a manner that works standalone and also with the dynamic loading.

  1. HTML 不是 XHTML,在撰写本文时,HTML 中对 SVG 的支持是粗制滥造且定义不明确。解决方案是使用真正的 XHTML 文档,其中 SVG 命名空间元素实际上被视为 SVG。

  2. responseXML是另一个DOM文档,你不能通常只是从一个文档移动节点到另一个。您应该用于document.importNode将节点从一个文档导入到另一个文档。

  3. 使用onload事件处理程序加载 SVG 文件不会通过创建节点或将其附加到文档来调用这些处理程序。script但是,块内的代码将运行,因此您需要以独立工作和动态加载的方式重写脚本。



Here's a simple example that works in Chrome, Safari, and Firefox...but not IE9:

这是一个适用于 Chrome、Safari 和 Firefox...但不适用于 IE9 的简单示例:

var xhr = new XMLHttpRequest;
xhr.open('get','stirling4.svg',true);
xhr.onreadystatechange = function(){
  if (xhr.readyState != 4) return;
  var svg = xhr.responseXML.documentElement;
  svg = document.importNode(svg,true); // surprisingly optional in these browsers
  document.body.appendChild(svg);
};
xhr.send();

See it in action here: http://phrogz.net/SVG/import_svg.xhtml

在这里查看它的实际效果:http: //phrogz.net/SVG/import_svg.xhtml



Unfortunately IE9 does not properly support document.importNode. To work around this, we write our own cloneToDocfunction that creates an equivalent structure for any given node by recursively crawling the hierarchy. Here's a full working example:

不幸的是 IE9 没有正确支持document.importNode. 为了解决这个问题,我们编写了自己的cloneToDoc函数,通过递归地爬行层次结构为任何给定节点创建等效结构。这是一个完整的工作示例:

<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en"><head> 
  <meta http-equiv="content-type" content="application/xhtml+xml;charset=utf-8"/>
  <title>Fetch and Include SVG in XHTML</title>
  <script type="text/ecmascript"><![CDATA[
    setTimeout(function(){
      var xhr = new XMLHttpRequest;
      xhr.open('get','stirling4.svg',true);
      xhr.onreadystatechange = function(){
        if (xhr.readyState != 4) return;
        var svg = cloneToDoc(xhr.responseXML.documentElement);
        document.body.appendChild(svg);
      };
      xhr.send();
    },1000);
    function cloneToDoc(node,doc){
      if (!doc) doc=document;
      var clone = doc.createElementNS(node.namespaceURI,node.nodeName);
      for (var i=0,len=node.attributes.length;i<len;++i){
        var a = node.attributes[i];
        if (/^xmlns\b/.test(a.nodeName)) continue; // IE can't create these
        clone.setAttributeNS(a.namespaceURI,a.nodeName,a.nodeValue);
      }
      for (var i=0,len=node.childNodes.length;i<len;++i){
        var c = node.childNodes[i];
        clone.insertBefore(
          c.nodeType==1 ? cloneToDoc(c,doc) : doc.createTextNode(c.nodeValue),
          null
        ); }
      return clone;
    }
  ]]></script>
</head><body></body></html>

See it in action here: http://phrogz.net/SVG/import_svg_ie9.xhtml

在这里查看它的实际效果:http: //phrogz.net/SVG/import_svg_ie9.xhtml



Edit 2:As suspected, the problem is that the onloadevent does not fire when dynamically adding script. Here's a paired solution that works:

编辑 2:正如怀疑的那样,问题是onload动态添加脚本时不会触发事件。这是一个有效的配对解决方案:

  1. Rewrite your script to remove the onloadevent handler. Instead, trust that documentexists.
  2. Rewrite your script to ask for a global svgRoot; if it doesn't exist, use document.documentElement.
  3. When fetching the SVG set a global svgRootto the new svgelement after you import it into the document.
  1. 重写您的脚本以删除onload事件处理程序。相反,信任document存在。
  2. 重写您的脚本以请求全局svgRoot;如果它不存在,请使用document.documentElement.
  3. 获取 SVG 时svgRoot,在svg将其导入文档后,将全局设置为新元素。

Here's the code in action:

这是运行中的代码:

And, in case my site is down, here is the code for posterity:

而且,如果我的网站关闭,这里是后代的代码:

script-created.svg

脚本创建.svg

<svg xmlns="http://www.w3.org/2000/svg">
  <script type="text/javascript"><![CDATA[
    function createOn( root, name, a ){
      var el = document.createElementNS(svgNS,name);
      for (var n in a) if (a.hasOwnProperty(n)) el.setAttribute(n,a[n]);
      return root.appendChild(el);
    }
    // Trust someone else for the root, in case we're being
    // imported into another document
    if (!window.svgRoot) svgRoot=document.documentElement;
    var svgNS = svgRoot.namespaceURI;
    createOn(svgRoot,'rect',{
      x:10, y:10, width:30, height:30,
      stroke:'#8080ff', "stroke-width":5,
      fill:"none"
    });
  ]]></script>
</svg>

import_svg_with_script.xhtml

import_svg_with_script.xhtml

<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en"><head> 
  <meta http-equiv="content-type"
        content="application/xhtml+xml;charset=utf-8" />
  <title>Fetch and Include Scripted SVG in XHTML</title>
  <script type="text/ecmascript"><![CDATA[
    setTimeout(function(){
      var xhr = new XMLHttpRequest;
      xhr.open('get','script-created.svg',true);
      xhr.onreadystatechange = function(){
        if (xhr.readyState != 4) return;
        var svg = xhr.responseXML.documentElement;
        svg = cloneToDoc(svg);
        window.svgRoot = svg; // For reference by scripts
        document.body.appendChild(svg);
        delete window.svgRoot;
      };
      xhr.send();
    },1000);
    function cloneToDoc(node,doc){
      if (!doc) doc=document;
      var clone = doc.createElementNS(node.namespaceURI,node.nodeName);
      for (var i=0,len=node.attributes.length;i<len;++i){
        var a = node.attributes[i];
        if (/^xmlns\b/.test(a.nodeName)) continue; // IE can't create these
        clone.setAttributeNS(a.namespaceURI,a.nodeName,a.nodeValue);
      }
      for (var i=0,len=node.childNodes.length;i<len;++i){
        var c = node.childNodes[i];
        clone.insertBefore(
          c.nodeType==1 ? cloneToDoc(c,doc) : doc.createTextNode(c.nodeValue),
          null
        )
      }
      return clone;
    }
  ]]></script>
</head><body></body></html>