Приложение jquery не работает с элементом SVG?

199

Предполагая это:

<html>
<head>
 <script type="text/javascript" src="jquery.js"></script>
 <script type="text/javascript">
 $(document).ready(function(){
  $("svg").append('<circle cx="100" cy="50" r="40" stroke="black" stroke-width="2" fill="red"/>');
 });
 </script>
</head>
<body>
 <svg xmlns:svg="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 200 100" width="200px" height="100px">
 </svg>
</body>

Почему я ничего не вижу?


источник

Ответы:

249

Когда вы передаете строку разметки в $нее, она анализируется как HTML с использованием innerHTMLсвойства браузера в <div>(или другом подходящем контейнере для особых случаев, например <tr>). innerHTMLне может проанализировать SVG или другой контент, не относящийся к HTML, и даже если бы он мог, он не смог бы определить, что <circle>должно быть в пространстве имен SVG.

innerHTMLнедоступно в SVGElement - это свойство только HTMLElement. В настоящее время нет ни innerSVGсвойства, ни другого способа (*) для анализа содержимого в SVGElement. По этой причине вам следует использовать методы в стиле DOM. jQuery не дает вам простой доступ к методам пространства имен, необходимым для создания элементов SVG. Действительно, jQuery вообще не предназначен для использования с SVG, и многие операции могут завершиться неудачно.

HTML5 обещает позволить вам использовать в будущем <svg>без xmlnsпростого HTML ( text/html) документа. Но это всего лишь хакер синтаксического анализа (**), содержимое SVG по-прежнему будет SVGElement в пространстве имен SVG, а не HTMLElements, поэтому вы не сможете использовать его, innerHTMLдаже если они выглядят как часть HTML-документа.

Тем не менее, в современных браузерах вы должны использовать X HTML (должным образом application/xhtml+xml; сохраните с расширением .xhtml для локального тестирования), чтобы заставить SVG работать вообще. (Это в любом случае имеет смысл; SVG является стандартом, основанным на XML.) Это означает, что вам нужно экранировать <символы внутри вашего блока скрипта (или заключать в раздел CDATA) и включать xmlnsдекларацию XHTML . пример:

<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"><head>
</head><body>
    <svg id="s" xmlns="http://www.w3.org/2000/svg"/>
    <script type="text/javascript">
        function makeSVG(tag, attrs) {
            var el= document.createElementNS('http://www.w3.org/2000/svg', tag);
            for (var k in attrs)
                el.setAttribute(k, attrs[k]);
            return el;
        }

        var circle= makeSVG('circle', {cx: 100, cy: 50, r:40, stroke: 'black', 'stroke-width': 2, fill: 'red'});
        document.getElementById('s').appendChild(circle);
        circle.onmousedown= function() {
            alert('hello');
        };
    </script>
</body></html>

*: хорошо, есть parseWithContext DOM Level 3 LS , но поддержка браузера очень плохая. Отредактируйте, чтобы добавить: однако, хотя вы не можете внедрить разметку в SVGElement, вы можете добавить новый SVGElement в HTMLElement, используя innerHTML, а затем перенести его в нужную цель. Это, вероятно, будет немного медленнее, хотя:

<script type="text/javascript"><![CDATA[
    function parseSVG(s) {
        var div= document.createElementNS('http://www.w3.org/1999/xhtml', 'div');
        div.innerHTML= '<svg xmlns="http://www.w3.org/2000/svg">'+s+'</svg>';
        var frag= document.createDocumentFragment();
        while (div.firstChild.firstChild)
            frag.appendChild(div.firstChild.firstChild);
        return frag;
    }

    document.getElementById('s').appendChild(parseSVG(
        '<circle cx="100" cy="50" r="40" stroke="black" stroke-width="2" fill="red" onmousedown="alert(\'hello\');"/>'
    ));
]]></script>

**: я ненавижу то, как авторы HTML5, похоже, напуганы XML и полны решимости использовать особенности, основанные на XML, в сложном беспорядке HTML. XHTML решил эти проблемы много лет назад.

bobince
источник
7
Этот ответ по-прежнему актуален! У меня была странная ошибка, когда добавленные элементы показывались в инспекторе элементов Chrome, но не отображались. Если I RMB> edit as htmlв теге html и нажать клавишу ввода, все отображается (но все прослушиватели событий исчезают). После прочтения этого ответа я изменил свои вызовы createElement на createElementNS, и теперь все работает!
kitsu.eb
7
Вы можете манипулировать элементами SVG с помощью jquery после их создания, используя метод DOM createElementNS. Вы можете изменить функцию makeSVG на 'return $ (el)', и теперь у вас есть элемент svg, который может использовать методы jquery.
Хоффманн
6
Ах! Вот почему это не сработает. Я попробовал ту же самую вещь с двумя различными библиотеками, один в JQuery и один в D3.js . Я получил точно такой же исходный вывод в HTML, используя оба, но сгенерированные jQuery элементы не рендерится, а сгенерированные D3! Я рекомендую использовать D3:d3.select('body').append('svg').attr('width','100%');
chharvey
3
@MadsSkjern: DOM Level 3 LS никогда не заходил в браузеры. Первоначально DOMParser, предназначенный только для Mozilla , теперь более широко поддерживается, хотя и имеет jQuery $.parseXML.
бобинце
1
Это все еще актуально. bobince ненавидит беспорядок HTML5, я ненавижу беспорядок всей сети, потому что нигде вы не можете найти хороший код без взломов из-за этого беспорядка. WWW следует переименовать в WWM World Wide Mess.
Оливье Понс
149

В ответ принятые шоу слишком сложным способом. Как утверждает Форресто в своем ответе , « он, кажется, добавляет их в проводнике DOM, но не на экране », и причина этого - разные пространства имен для html и svg.

Самый простой обходной путь - «обновить» весь SVG. После добавления круга (или других элементов) используйте это:

$("body").html($("body").html());

Это делает трюк. Круг на экране.

Или, если хотите, используйте контейнер div:

$("#cont").html($("#cont").html());

И оберните свой svg внутри контейнера div:

<div id="cont">
    <svg xmlns:svg="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 200 100" width="200px" height="100px">
    </svg>
</div>

Функциональный пример:
http://jsbin.com/ejifab/1/edit

Преимущества этой техники:

  • Вы можете редактировать существующий SVG (который уже находится в DOM), например. создан с использованием Raphael или как в вашем примере "жестко запрограммирован" без сценариев.
  • Вы можете добавить сложные структуры элементов в виде строк, например. $('svg').prepend('<defs><marker></marker><mask></mask></defs>');как вы делаете в jQuery.
  • После добавления и отображения элементов на экране $("#cont").html($("#cont").html());их атрибуты можно редактировать с помощью jQuery.

РЕДАКТИРОВАТЬ:

Вышеприведенный метод работает только с SVG «жестко запрограммированным» или DOM-манипулированием (= document.createElementNS и т. Д.). Если для создания элементов используется Raphael, то (согласно моим тестам) связь между объектами Raphael и SVG DOM нарушается, если $("#cont").html($("#cont").html());используется. Обходной путь к этому не должен использовать $("#cont").html($("#cont").html());вообще и вместо этого использовать фиктивный документ SVG.

Этот фиктивный SVG является вначале текстовым представлением документа SVG и содержит только необходимые элементы. Если мы хотим, например. чтобы добавить элемент фильтра к документу Рафаэля, пустышка может выглядеть примерно так <svg id="dummy" style="display:none"><defs><filter><!-- Filter definitons --></filter></defs></svg>. Текстовое представление сначала преобразуется в DOM с помощью метода jQuery $ ("body"). Append (). А когда элемент (filter) находится в DOM, его можно запросить с помощью стандартных методов jQuery и добавить к основному документу SVG, который создан Raphael.

Зачем этот манекен нужен? Почему бы не добавить элемент фильтра строго в документ, созданный Рафаэлем? Если вы попробуете, используя, например. $("svg").append("<circle ... />"), он создается как элемент HTML, и на экране ничего не отображается, как описано в ответах. Но если весь документ SVG добавляется, то браузер автоматически обрабатывает преобразование пространства имен всех элементов в документе SVG.

Пример просветления техники:

// Add Raphael SVG document to container element
var p = Raphael("cont", 200, 200);
// Add id for easy access
$(p.canvas).attr("id","p");
// Textual representation of element(s) to be added
var f = '<filter id="myfilter"><!-- filter definitions --></filter>';

// Create dummy svg with filter definition 
$("body").append('<svg id="dummy" style="display:none"><defs>' + f + '</defs></svg>');
// Append filter definition to Raphael created svg
$("#p defs").append($("#dummy filter"));
// Remove dummy
$("#dummy").remove();

// Now we can create Raphael objects and add filters to them:
var r = p.rect(10,10,100,100);
$(r.node).attr("filter","url(#myfilter)");

Полная рабочая демонстрация этой техники находится здесь: http://jsbin.com/ilinan/1/edit .

(Я не знаю (пока), почему $("#cont").html($("#cont").html());не работает при использовании Рафаэля. Это был бы очень короткий взлом.)

Тимо Кяхконен
источник
Я использую свое решение (Home made) для работы с SVG, и у меня та же проблема, что и у Raphael, когда я использую «трюк с повторным анализом» с $ (# picture) .html ..., но я решил повторно запустить некоторые кэшированные SVG элементы (маркировка прямоугольника, преобразование и т. д.)
полубит
Вы волшебник. Я смотрел на принятый ответ, и я был как человек, это будет боль. Затем я увидел это, и он делает все, что мне нужно, в одну короткую очередь. Спасибо!
Композитор
1
Это заставило меня потерять шрамы ... я подумал, что могу волшебным образом использовать вязы JOM DOM в пространстве имен SVG, как можно подумать ... получил инспектор тегов для распознавания вставок ... но до сих пор не выпал кубик!
Гас Кроуфорд,
Работал отлично для меня, когда добавлял существующие теги полигона и пути к недавно созданному родительскому тегу svg. Спасибо!
Кивак Вольф
1
$("#cont").html($("#cont").html());отлично работал в Chrome, но не работал в IE 11 для меня.
CoderDennis
37

Все более популярная библиотека D3 прекрасно справляется со сложностями добавления / манипулирования svg. Вы можете рассмотреть возможность его использования в отличие от хаков jQuery, упомянутых здесь.

HTML

<svg xmlns="http://www.w3.org/2000/svg"></svg>

Javascript

var circle = d3.select("svg").append("circle")
    .attr("r", "10")
    .attr("style", "fill:white;stroke:black;stroke-width:5");
nategood
источник
хороший ответ. это помогло мне в некоторой проблеме здесь.
Исмаил Байг
JQuery также задыхается от установки / получения классов из SVG. Просто в сторону.
QueueHammer
24

JQuery не может добавлять элементы <svg>(кажется, что они добавляются в проводнике DOM, но не на экране).

Одним из обходных путей является добавление <svg>со всеми необходимыми элементами на страницу, а затем изменение атрибутов элементов с помощью .attr().

$('body')
  .append($('<svg><circle id="c" cx="10" cy="10" r="10" fill="green" /></svg>'))
  .mousemove( function (e) {
      $("#c").attr({
          cx: e.pageX,
          cy: e.pageY
      });
  });

http://jsfiddle.net/8FBjb/1/

forresto
источник
Спасибо, мне очень помогло! Это очень хороший подход. Вместо добавления <circle>или других элементов, индивидуально использующих сложные и медленные DOM-методы (например, createDocumentFragment()или createElementNS()), добавьте весь документ SVG в контейнер. Вместо $ ('body') это может быть также $ ('div'). И после этого документ svg находится в DOM и может быть запрошен обычным способом с помощью, например. $ ( 'с'). атр ( 'заливка', 'красный').
Тимо Кяхконен
Я сделал дополнительный пример этого: jsbin.com/inevaz/1 . Он получает исходный код tiger.svg из Интернета в iframe и может быть скопирован в текстовую область. Содержимое textarea затем используется в качестве источника для встроенного SVG, который затем доступен через DOM (для изменения стиля обводки).
Тимо Кяхконен
Это здорово, я не знаю, почему все голосуют за те ответы, которые просто не работают.
Дастин Пуассан
Это был лучший ответ и для меня. Мне нужен был svg с <use xlink: href = "# icon">, и это был самый короткий рабочий ответ.
Eydamos
17

Я не видел, чтобы кто-то упоминал этот метод, но document.createElementNS()он полезен в этом случае.

Вы можете создавать элементы, используя ванильный Javascript как обычные узлы DOM с правильным пространством имен, а затем jQuery-ify их оттуда. Вот так:

var svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg'),
    circle = document.createElementNS('http://www.w3.org/2000/svg', 'circle');

var $circle = $(circle).attr({ //All your attributes });

$(svg).append($circle);

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

Крис Дельфин
источник
2
Тимо ответ (с наибольшим количеством голосов) работал в Chrome, но не в IE. Этот ответ решил проблему для обоих браузеров!
CoderDennis
1
Ницца! Полезно знать
Крис Дельфин
14

Нашел простой способ, который работает со всеми браузерами (Chrome 49, Edge 25, Firefox 44, IE11, Safari 5 [Win], Safari 8 (MacOS)):

// Clean svg content (if you want to update the svg's objects)
// Note : .html('') doesn't works for svg in some browsers
$('#svgObject').empty();
// add some objects
$('#svgObject').append('<polygon class="svgStyle" points="10,10 50,10 50,50 10,50 10,10" />');
$('#svgObject').append('<circle class="svgStyle" cx="100" cy="30" r="25"/>');

// Magic happens here: refresh DOM (you must refresh svg's parent for Edge/IE and Safari)
$('#svgContainer').html($('#svgContainer').html());
.svgStyle
{
  fill:cornflowerblue;
  fill-opacity:0.2;
  stroke-width:2;
  stroke-dasharray:5,5;
  stroke:black;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>

<div id="svgContainer">
  <svg id="svgObject" height="100" width="200"></svg>
</div>

<span>It works if two shapes (one square and one circle) are displayed above.</span>

Матье Шарбоннье
источник
5
Эта строка обновления - именно то, что я искал. Глупые браузеры. Спасибо!
Сэм Соффс
8

Я могу видеть круг в Firefox, делая 2 вещи:

1) Переименование файла из html в xhtml

2) Изменить скрипт на

<script type="text/javascript">
$(document).ready(function(){
    var obj = document.createElementNS("http://www.w3.org/2000/svg", "circle");
    obj.setAttributeNS(null, "cx", 100);
    obj.setAttributeNS(null, "cy", 50);
    obj.setAttributeNS(null, "r",  40);
    obj.setAttributeNS(null, "stroke", "black");
    obj.setAttributeNS(null, "stroke-width", 2);
    obj.setAttributeNS(null, "fill", "red");
    $("svg")[0].appendChild(obj);
});
</script>
Topera
источник
1
Восемь лет спустя, и это все еще полезно!
Дон 01001100
5

Основано на ответе @ chris-dolphin, но с использованием вспомогательной функции:

// Creates svg element, returned as jQuery object
function $s(elem) {
  return $(document.createElementNS('http://www.w3.org/2000/svg', elem));
}

var $svg = $s("svg");
var $circle = $s("circle").attr({...});
$svg.append($circle);
Йонас Берлин
источник
1
Конечно, вы также можете сделать:$svg.append($s('<circle cx="100" cy="50" r="40" stroke="black" stroke-width="2" fill="red"/>'));
Йонас Берлин
4

Если вам нужно добавить строку SVG и вы добавили правильное пространство имен, вы можете проанализировать строку как XML и добавить к родителю.

var xml = jQuery.parseXML('<circle xmlns="http://www.w3.org/2000/svg" cx="100" cy="50" r="40" stroke="black" stroke-width="2" fill="red"/>');
$("svg").append(xml.documentElement))
jdesjean
источник
3

Принятый ответ Bobince - это короткое, портативное решение. Если вам нужно не только добавить SVG, но и манипулировать им, вы можете попробовать библиотеку JavaScript «Pablo» (я написал). Это будет знакомо пользователям jQuery.

Ваш пример кода будет выглядеть так:

$(document).ready(function(){
    Pablo("svg").append('<circle cx="100" cy="50" r="40" stroke="black" stroke-width="2" fill="red"/>');
});

Вы также можете создавать элементы SVG на лету, не указывая разметку:

var circle = Pablo.circle({
    cx:100,
    cy:50,
    r:40
}).appendTo('svg');
Premasagar
источник
1

Я хотел бы предположить, что может быть лучше использовать ajax и загрузить элемент svg с другой страницы.

$('.container').load(href + ' .svg_element');

Где href - это местоположение страницы с svg. Таким образом, вы можете избежать любых нервных эффектов, которые могут возникнуть при замене HTML-контента. Также не забудьте развернуть svg после загрузки:

$('.svg_element').unwrap();
Dan185
источник
0

Это работает для меня сегодня с FF 57:

function () {
    // JQuery, today, doesn't play well with adding SVG elements - tricks required
    $(selector_to_node_in_svg_doc).parent().prepend($(this).clone().text("Your"));
    $(selector_to_node_in_svg_doc).text("New").attr("x", "340").text("New")
        .attr('stroke', 'blue').attr("style", "text-decoration: line-through");
}

ПРОИЗВОДИТ:

это изображение SVG, как видно в Firefox 57

paul_h
источник
Это не элементы SVG, которые вы добавляете, это текстовое содержимое.
Роберт Лонгсон
Это может быть, но он отображается после начальной загрузки страницы статически встроенного SVG.
paul_h
Так что, это не вопрос, который задают, а об элементах svg, а не о текстовом содержимом.
Роберт Лонгсон
$(this).clone()клонирует элемент SVG (и это дети, если он есть). Смотри мимо использования text(). Я беру один tspan, в котором было "Your New", и в итоге получился два tspan, один с "Your" в нем (черный) и один с "New in it" (синий с проходом через строку). Боже, уменьшено с 0 голосов до -1 :-(
paul_h
0
 var svg; // if you have variable declared and not assigned value.
 // then you make a mistake by appending elements to that before creating element    
 svg.appendChild(document.createElement("g"));
 // at some point you assign to svg
 svg = document.createElementNS('http://www.w3.org/2000/svg', "svg")
 // then you put it in DOM
 document.getElementById("myDiv").appendChild(svg)
 // it wont render unless you manually change myDiv DOM with DevTools

// to fix assign before you append
var svg = createElement("svg", [
    ["version", "1.2"],
    ["xmlns:xlink", "http://www.w3.org/1999/xlink"],
    ["aria-labelledby", "title"],
    ["role", "img"],
    ["class", "graph"]
]);
function createElement(tag, attributeArr) {
      // .createElementNS  NS is must! Does not draw without
      let elem = document.createElementNS('http://www.w3.org/2000/svg', tag);             
      attributeArr.forEach(element => elem.setAttribute(element[0], element[1]));
      return elem;
}
// extra: <circle> for example requires attributes to render. Check if missing.
4baad4
источник
-1

Гораздо более простой способ - просто сгенерировать SVG в строку, создать HTML-элемент-оболочку и вставить строку svg в HTML-элемент, используя $("#wrapperElement").html(svgString). Это прекрасно работает в Chrome и Firefox.

Якоб Дженков
источник