Приложение jquery не работает с элементом SVG?
Предполагая это:
<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>
Почему я ничего не вижу?
17 ответов
Когда вы передаете строку разметки в $
, он анализируется как 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 по-прежнему будет SVGElements в пространстве имен SVG, а не HTMLElements, поэтому вы не сможете использовать innerHTML
даже если они выглядят как часть документа HTML.
Однако для современных браузеров вы должны использовать X HTML (должным образом application/xhtml+xml
; сохраните с расширением.xhtml для локального тестирования), чтобы заставить SVG работать вообще. (В любом случае это имеет смысл; SVG - это стандарт на основе XML.) Это означает, что вам придется избегать <
символы внутри вашего блока сценария (или заключены в раздел CDATA) и включают XHTML xmlns
декларация. пример:
<!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 решил эти проблемы много лет назад.
Принятый ответ показывает слишком сложный путь. Как утверждает Форресто в своем ответе, "он, кажется, добавляет их в проводнике 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());
не работает при использовании Рафаэля. Это был бы очень короткий взлом.)
Все более популярная библиотека 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");
JQuery не может добавлять элементы в <svg>
(кажется, что они добавляются в DOM Explorer, но не на экране).
Одним из обходных путей является добавление <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
});
});
Я не видел, чтобы кто-то упоминал этот метод, но 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 с правильным пространством имен индивидуально, иначе он не будет работать.
Нашел легкий способ, который работает со всеми браузерами, которые у меня есть (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>
Я могу видеть круг в 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>
Основано на ответе @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);
Если вам нужно добавить строку 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))
Принятый ответ 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');
Я бы предположил, что может быть лучше использовать ajax и загрузить элемент svg с другой страницы.
$('.container').load(href + ' .svg_element');
Где href - это местоположение страницы с svg. Таким образом, вы можете избежать любых нервных эффектов, которые могут возникнуть при замене html-содержимого. Кроме того, не забудьте развернуть svg после загрузки:
$('.svg_element').unwrap();
Для этого я сделал небольшую функцию. Что касается метода добавления jQuery, проблема заключается в необходимости указания пространства имен для SVG, которое
"http://www.w3.org/2000/svg"
Более
Так что, если я подготовлю его для метода добавления? В этом случае единственное, что вам нужно предложить, это некоторые параметры, такие как:
tagName
: это может быть любой элемент SVG, такой как прямоугольник, круг, текст, g и т. д.
text
: Если вы используете что-то вроде текстового тэга, вам нужно указать текст
И другие известные атрибуты элементов SVG.
Таким образом, я собираюсь определить функцию с именем
createSvgElem()
который использует document.createElementNS() внутри.
Вот пример:
$("svg").append(
createSvgElem({tagName: "text", x: 10, y: 10, text: "ABC", style: "fill: red"})
)
И вот функция:
function createSvgElem(options){
var settings = $.extend({
}, options);
if(!$.isEmptyObject(settings.tagName)){
var el = document.createElementNS('http://www.w3.org/2000/svg', settings.tagName);
for (var k in settings)
if(k != "tagName" && k != "text" && settings[k] != "")//If attribute has value
el.setAttribute(k, settings[k]);
if ("text" in settings)
el.textContent = settings.text; //el.innerText; For IE
return el;
}
}
Здесь вы можете попробовать сами:
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.
С jquery вы можете сделать именно это. установка DataType на «текст».
Альтернатива 1: нативный js
При условии, что вы вообще не рассматриваете возможность перехода на собственный JavaScript...
Вы также можете использовать собственный метод javaScriptinsertAdjacentHTML()
для столь же удобных обозначений.
$("#svg")[0].insertAdjacentHTML(
"beforeEnd",
'<circle cx="100" cy="50" r="40" stroke="black" stroke-width="2" fill="red"/>'
);
$("#svg")[0]
делает ваш объект jQuery доступным для выбора в собственном JS.
Альтернатива 2: написать нативный jsDOMParser()
помощник
веб-документы mdn: DOMParser.parseFromString()
function createSvgEl(markup) {
markup = `<svg xmlns="http://www.w3.org/2000/svg">
${markup}</svg>`;
const svgEl = new DOMParser().parseFromString(markup, "image/svg+xml")
.documentElement.children[0];
return svgEl;
}
Использование в jQuery:
$("#svgXML").append(
createSvgEl(
'<circle cx="100" cy="50" r="40" stroke="black" stroke-width="2" fill="red"/>'
)
);
Демо
jquery
after()
или
before()
методы также не смогут добавить элемент SVG DOM(в зависимости от правильного пространства имен).
Использование вышеупомянутых обходных путей также решит эту проблему.
Гораздо более простой способ - просто сгенерировать SVG в строку, создать HTML-элемент-оболочку и вставить строку SVG в HTML-элемент, используя $("#wrapperElement").html(svgString)
, Это прекрасно работает в Chrome и Firefox.
Это работает для меня сегодня с 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");
}
Делает: