Как динамически вставить изображение 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 жалуется на "[объект 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>

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

1 ответ

Решение

В общем, проблема двоякая:

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

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

  3. Загрузка файла SVG с onload обработчики событий не будут вызывать эти обработчики, создавая узел или добавляя его в документ. Код внутри 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 функция, которая создает эквивалентную структуру для любого данного узла путем рекурсивного сканирования иерархии. Вот полный рабочий пример:

<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 событие не срабатывает при динамическом добавлении скрипта. Вот парное решение, которое работает:

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

Вот код в действии:

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

скрипт-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>
Другие вопросы по тегам