Доступ к данным SVG через JavaScript

Я работаю над веб-приложением, использующим SVG и JS. Все идет хорошо, но меня немного смущает обработка данных.

Данные для объектов, создаваемых пользователями, динамически добавляются к корню SVG. Где и как хранятся данные SVG и как я могу получить к нему доступ? Причина, по которой я хочу ее прочитать, заключается в том, что я хочу преобразовать эти данные SVG в холст HTML5 (используя Google canvg) и, наконец, превратить это в png-изображение, которое будет храниться в нашем db для справки.

Любое руководство будет высоко оценено.

Ответ 1

Когда веб-браузер загружает SVG файл, он (грубо) анализирует поток байтов и создает структуру DOM в памяти для SVG файла. Именно это представление в памяти изменяется, когда script динамически создает и добавляет новые элементы в документ.

Вы можете увидеть очень простой пример этого:
http://phrogz.net/svg/svg_in_xhtml5.xhtml
Файл SVG загружается с кругом в фоновом режиме для лица, а затем JavaScript динамически создает глаза и нос и добавляет их как дочерние элементы элемента SVG.

Есть три способа узнать, что вы можете получить доступ к этой информации и получить ее в холсте:

  • Вы можете самостоятельно пройти DOM элементов и вызывать команды рисования контекста canvas:

    var svg = document.getElementsByTagName('svg')[0];
    var kids = svg.childNodes;
    for (var i=0,len=kids.length;i<len;++i){
      var kid = kids[i];
      if (kid.nodeType!=1) continue; // skip anything that isn't an element
      switch(kid.nodeName){
        case 'circle':
          // ...
        break;
        case 'path':
          // ...
        break;
        // ...
      }
    }
    

    Обратитесь к ссылке SVG DOM для получения дополнительной информации о доступных вам свойствах и методах или просто используйте DOM 2 Core для извлечения свойств. Для простоты вы можете использовать последнюю.

    Например, вы можете либо получить доступ к текущему (не SMIL анимированный) cx круга, используя SVG DOM с помощью var cx = kid.cx.baseVal.value;, либо просто сделать var cx = kid.getAttribute('cx');.

    Это будет просто, когда SVG и Canvas имеют схожие команды (например, rect или line), небольшая работа, где они не соответствуют точно (например, circle против arcTo, polyline как последовательность команд lineTo) и много работы для элемента path и многих команд рисования.

  • В этот ответ и этот документ, используйте XMLSerializer, чтобы вернуть SVG в качестве разметки, используйте это непосредственно как источник img, а затем drawImage для холста. Используя мой пример:

    var svg_xml = (new XMLSerializer).serializeToString(svg);
    console.log(svg_xml);
    // "<svg xmlns="http://www.w3.org/2000/svg" version="1.1" baseProfile="full" viewBox="-350 -250 700 500">
    // <circle r="200" class="face" fill="red"/>
    // <path fill="none" class="face" transform="translate(-396,-230)" d="M487.41,282.411c-15.07,36.137-50.735,61.537-92.333,61.537         c-41.421,0-76.961-25.185-92.142-61.076"/>
    // <circle cx="-60" cy="-50" r="20" fill="#000"/>
    // <circle cx="60" cy="-50" r="20" fill="#000"/>
    // </svg>"
    var ctx = myCanvas.getContext('2d');
    var img = new Image;
    img.onload = function(){ ctx.drawImage(img,0,0); };
    img.src = "data:image/svg+xml;base64,"+btoa(svg_xml);
    

    Если у вас есть SVG в XHTML (как в моем примере), сериализация не будет записывать стили, определенные за пределами блока SVG (как в моем примере), и поэтому они не будут применяться на изображении.

    Обратите внимание, что за этот вопрос/ответ (мой) вы должны установить атрибут onload, прежде чем устанавливать src на URL-адрес данных, если хотите Совместимость с IE9.

    К сожалению, из-за того, что может быть или не быть ошибка, рисование изображения URL-адреса на холсте приводит к возникновению-очистке флаг должен быть установлен на false, что означает, что вы не сможете вызвать toDataURL() на холсте и вернуть свой PNG. Так что...

  • Я полагаю, что для ваших конкретных потребностей SVG- > Canvas- > PNG-on-сервера, модифицированных клиентом, вам нужно будет использовать первый шаг выше для сериализации svg_xml, а затем передать этот исходный источник в canvg.

В качестве альтернативы вы можете рассмотреть возможность сериализации SVG в соответствии с вышеизложенным и отправить его непосредственно на ваш сервер и сделать преобразование SVG-в PNG на стороне сервера.

Ответ 2

Ошибка webkit, упомянутая в ответе @Phrogz, по-видимому, исправлена ​​в более поздних версиях, поэтому я нашел решение, которое не требует ручного анализа,

// from http://bl.ocks.org/biovisualize/1209499
// via https://groups.google.com/forum/?fromgroups=#!topic/d3-js/RnORDkLeS-Q
var xml = d3.select("svg")
  .attr("title", "test2")
  .attr("version", 1.1)
  .attr("xmlns", "http://www.w3.org/2000/svg")
  .node().parentNode.innerHTML;

// from http://webcache.googleusercontent.com/search?q=cache:http://phpepe.com/2012/07/export-raphael-graphic-to-image.html
// via http://stackoverflow.com/info/4216927/problem-saving-as-png-a-svg-generated-by-raphael-js-in-a-canvas
var canvas = document.getElementById("myCanvas")
canvg(canvas, svgfix(xml));
document.location.href = canvas.toDataURL();

который требует https://code.google.com/p/svgfix/ и https://code.google.com/p/canvg/ и использует d3.js для получения источника svg, который должен выполняться без d3.js(но это позаботится о добавлении необходимых метаданных, которые могут привести к проблемам при отсутствии).