Подтвердить что ты не робот

Как динамически вставить SVG-изображение в HTML?

У меня есть код, который извлекает сценарий svg-изображения с сервера через Ajax. Я могу вернуть текст изображения в браузер, но я не могу найти способ вставить его в 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>

Я пробовал разные вещи. Если я это сделаю:

// 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);
}

Затем Opera и Chrome ничего не делают, и F/F жалуется на "[object XMLDocument]". Если я изменил "responseXML" на "responseText", Opera/Chrome корректно отобразит весь текст svg (а не изображение) в нужном месте, и F/F все еще дает то же предупреждение. Я также попытался назначить ответ на innerHTML, но это ничего не делает. Есть идеи? Спасибо.

ИЗМЕНИТЬ

В ответ на ответ Phrogz'z ниже - я добавил два простых файла svg. Первый - это "стандартный" простой svg, отображающий круг. Второй - это скрипт svg, отображающий прямоугольник. Вы должны иметь возможность просматривать их непосредственно в любом браузере, кроме IE8-. Если я отредактирую код Phrogz'z для использования файла окружности (замените "stirling4.svg" на имя этого файла), то он будет работать, но если я хочу вместо этого скриптовый прямоугольник, это не так. Протестировано на F/F, Opera, Chromium, но все равно не работает (мой) Chromium.

Файл 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>

Файл 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>

Предположительно, ответ заключается в том, чтобы получить script в заголовок

4b9b3361

Ответ 1

В общем случае проблема состоит в следующем: twofold:

  • HTML не является XHTML, а поддержка SVG в HTML является дрянной и плохо определенной на момент написания. Решение заключается в использовании реального документа XHTML, где элементы, помеченные SVG-именами, фактически рассматриваются как SVG.

  • responseXML находится в другом документе DOM, и вы обычно не можете просто перемещать узлы из одного документа в другой. Вы должны использовать document.importNode для импорта node из одного документа в другой.

  • Загрузка SVG файла с обработчиками событий onload не будет иметь эти обработчики, вызванные либо созданием node, либо добавлением его в документ. Однако код внутри блока script будет запущен, поэтому вам нужно будет переписать сценарии так, чтобы они работали автономно, а также с динамической загрузкой.


Вот простой пример, который работает в 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();

Смотрите здесь: http://phrogz.net/SVG/import_svg.xhtml


К сожалению, IE9 не поддерживает document.importNode. Чтобы обойти это, мы пишем нашу собственную функцию cloneToDoc, которая создает эквивалентную структуру для любого заданного node путем рекурсивного обхода иерархии. Вот полный рабочий пример:

<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>

Смотрите здесь: http://phrogz.net/SVG/import_svg_ie9.xhtml


Изменить 2: Как подозревается, проблема в том, что событие onload не срабатывает при динамическом добавлении script. Здесь работает парное решение:

  • Перепишите script, чтобы удалить обработчик события onload. Вместо этого убедитесь, что document существует.
  • Перепишите script запрос глобального svgRoot; если он не существует, используйте document.documentElement.
  • При извлечении SVG установите глобальный svgRoot в новый элемент svg после его импорта в документ.

Здесь код в действии:

И, если мой сайт не работает, вот код для потомков:

script -created.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

<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>