Как динамически вставить изображение SVG в HTML? - PullRequest
13 голосов
/ 02 ноября 2011

У меня есть некоторый код, который получает сценарий 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>

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

1 Ответ

23 голосов
/ 02 ноября 2011

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

  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>
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...