jQuery的append方法不能与SVG元素一起使用?

220
假设这样:
<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个回答

268
当你将标记字符串传入 $ 时,它会使用浏览器的 innerHTML 属性在 <div>(或其他适当的容器用于特殊情况如 <tr>)上解析为 HTML。 innerHTML 无法解析 SVG 或其他非 HTML 内容,即使它能够解析也无法识别 <circle> 应该处于 SVG 命名空间。

innerHTML 不适用于 SVGElement——它仅是 HTMLElement 的属性。目前也没有 innerSVG 属性或其他方式(*)可以将内容解析为 SVGElement。因此,您应该使用 DOM 样式方法。jQuery 并未为您提供易于访问所需创建 SVG 元素的命名空间方法。实际上,jQuery 不适用于 SVG,许多操作可能会失败。

HTML5 承诺允许您在未来的普通 HTML (text/html) 文档中使用不带 xmlns<svg>。但这只是一个解析器 hack(**),SVG 内容仍将是 SVG 命名空间中的 SVGElement,而不是 HTMLElement,因此,即使它们看起来像是 HTML 文档的一部分,您也无法使用 innerHTML

然而,对于现代浏览器,您必须使用 XHTML(以正确的方式作为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>

*: 好的,有DOM Level 3 LS的parseWithContext,但浏览器支持非常差。补充编辑:然而,虽然您无法将标记注入SVGElement中,但可以使用innerHTML将新的SVGElement注入HTMLElement中,然后将其传输到所需的目标。不过速度可能会慢一些:

<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已经解决了这些问题多年了。


8
这个回答仍然适用!我遇到了一个奇怪的错误,添加的元素显示在Chrome元素检查器中,但不会渲染出来。如果我在HTML标签上点击“鼠标右键”>“编辑为HTML”,然后按回车键,所有内容就会显示出来(但所有事件监听器都会消失)。阅读了这个答案后,我将我的createElement调用更改为createElementNS,现在一切正常! - kitsu.eb
7
一旦使用DOM的createElementNS方法创建SVG元素,你就可以使用jquery来操作它们。你可以将makeSVG函数改为返回$(el),这样你就可以使用jquery方法来处理SVG元素了。 - Hoffmann
6
啊!原来是这个原因。我尝试使用两个不同的库,一个是jQuery,另一个是D3.js,执行了完全相同的操作,它们生成的HTML代码输出完全相同,但是使用jQuery生成的元素无法呈现,而使用D3生成的却可以呈现!我建议使用D3:d3.select('body').append('svg').attr('width','100%'); - chharvey
3
DOM Level 3 LS从未进入浏览器。最初仅为 Mozilla 开发的 DOMParser 现在得到了更广泛的支持,而 jQuery 则有 $.parseXML 方法。 - bobince
1
这仍然是相关的。Bobince 讨厌 HTML5 的混乱,我讨厌整个网络的混乱,因为你无处可以找到没有 hack 的好代码,因为那个混乱。WWW 应该改名为 WWM(世界范围的混乱)。 - Olivier Pons
显示剩余5条评论

176

被接受的答案显示的方法过于复杂。正如Forresto在他的回答中所说,"它似乎将它们添加到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>

The functional example:
http://jsbin.com/ejifab/1/edit This technique has the following advantages:
- You can edit existing SVG (that is already in DOM), for example, created using Raphael or like in your example "hard coded" without scripting. - You can add complex element structures as strings, for example, $('svg').prepend('<defs><marker></marker><mask></mask></defs>') like you do in jQuery. - After the elements are appended and made visible on the screen using $("#cont").html($("#cont").html()), their attributes can be edited using jQuery.

编辑:

上述技术仅适用于“硬编码”或DOM操作(= document.createElementNS等)的SVG。如果使用Raphael创建元素,则(根据我的测试),如果使用 $(“#cont”).html($(“#cont”).html()); ,则Raphael对象与SVG DOM之间的链接将中断。解决此问题的方法是根本不使用 $(“#cont”).html($(“#cont”).html()); ,而是使用虚拟SVG文档。

这个虚拟SVG首先是SVG文档的文本表示形式,只包含所需的元素。例如,如果我们想向Raphael文档添加一个过滤器元素,那么虚拟文档可能是这样的<svg id="dummy" style="display:none"><defs><filter><!-- Filter definitons --></filter></defs></svg>。首先使用jQuery的$(“body”)。append()方法将文本表示转换为DOM。当(过滤器)元素在DOM中时,可以使用标准的jQuery方法查询并附加到由Raphael创建的主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

(我还不知道为什么在使用Raphael时 $(“#cont”).html($(“#cont”).html()); 不起作用。 这将是非常简短的黑客。)


我正在使用自己制作的解决方案来处理SVG,并且遇到了与Raphael相同的问题,当使用“重新解析技巧”与$(#picture).html ...时,但是我的解决方案是重新初始化一些缓存的SVG元素(标记矩形、变换等)。 - halfbit
1
这让我感到非常沮丧...我以为我可以像使用普通DOM元素一样在SVG命名空间中使用js DOM元素...虽然标签检查器能够识别插入的内容...但是直到现在还没有任何进展! - Gus Crawford
我喜欢这个解决方案,非常直接明了。 - Ji_in_coding
@viggity 我在试图黑掉 Google Chart 的 SVG,但似乎遇到了这个问题。这个解决方案展示了我对 Google Chart 的更改,但破坏了图表的交互功能,感到很失望。 :( - Adam Youngers
2
$("#cont").html($("#cont").html()); 在 Chrome 中运行良好,但在 IE 11 中无法正常工作。 - CoderDennis
显示剩余5条评论

40

越来越受欢迎的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");

好的回答。它帮助了我解决了一些问题。 - ismail baig
1
jQuery在设置/获取SVG类时也会出现问题。这只是一个旁注。 - QueueHammer

27
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/


谢谢,帮了我很多!这是一个非常好的方法。不要使用复杂和缓慢的DOM方法(例如createDocumentFragment()createElementNS())单独添加<circle>或其他元素,而是将整个SVG文档附加到容器中。当然,它可以是一些$('div')而不是$('body')。之后,SVG文档就在DOM上了,可以使用熟悉的方式查询,例如使用$('c').attr('fill','red')。 - Timo Kähkönen
我制作了一个额外的示例:http://jsbin.com/inevaz/1/。它从互联网获取tiger.svg源代码到iframe,可以复制粘贴到文本区域中。然后,文本区域的内容用作内联svg的源,通过DOM可访问该svg(以更改描边样式)。 - Timo Kähkönen
这太棒了,我不知道为什么每个人都在投票支持那些根本行不通的其他答案。 - Dustin Poissant
这对我来说也是最好的答案。 我需要一个带有<use xlink:href="#icon">的svg,这是最短的可行答案。 - Eydamos

22

我没有看到有人提到这种方法,但是document.createElementNS()在这种情况下非常有帮助。

您可以使用纯JavaScript创建具有正确命名空间的元素,然后从那里将它们转换成jQuery对象。如下所示:

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元素,否则它将无法正常工作。

4
Timo 的回答(得到最高票)在 Chrome 中有效,但在 IE 中无效。这个答案解决了两个浏览器的问题! - CoderDennis
2
不错!好知道。 - Chris Dolphin
使用jQuery的.attr({})更简单,谢谢。 - Dee

18

我找到了一种简单的方法,适用于我所使用的所有浏览器(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>


6
那个刷新行就是我要找的。真是愚蠢的浏览器。谢谢! - Sam Soffes
$('#svgObject').empty() 会删除现有的 SVG 元素 - 否则你可以通过 html() 一次性创建整个 SVG 元素。顺便说一句,我已经使用 jQuery 很长时间了,但是 愚蠢的 jQuery。这些 SVG 插入问题本应该在 jQuery 3.x 中得到解决。 - herrstrietzel

10

我在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>

2
八年过去了,这仍然很有帮助! - Becca Dee

7

根据@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"/>')); - Jonas Berlin

3
如果您需要追加的字符串是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)

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');

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接