动态添加SVG渐变

10
我有一个包含路径的SVG容器。我想要编辑它,使路径的填充成为一个图案。这是我失败的尝试:
我添加了一个渐变:
$('svg defs').prepend('<linearGradient id="MyGradient"><stop offset="5%" stop-color="#F60" /><stop offset="95%" stop-color="#FF6" /></linearGradient>');

然后更改路径的填充:
$(base + ' svg path').each(function() {
    this.setAttribute('fill','url(#MyGradient)')
}

这个不起作用。我错过了什么吗?
4个回答

24

你的问题(即你所“缺少”的)在于jQuery会在XHTML命名空间中创建新元素,而SVG元素必须在SVG命名空间中创建。你不能在字符串中使用原始代码来创建SVG元素。

最简单(无需插件)的方法是不要过度依赖jQuery,而是使用简单的DOM方法来创建元素。是的,它比仅使用jQuery魔法般地构造元素更冗长……但在这种情况下,jQuery不起作用。

演示:http://jsfiddle.net/nra29/2/

createGradient($('svg')[0],'MyGradient',[
  {offset:'5%', 'stop-color':'#f60'},
  {offset:'95%','stop-color':'#ff6'}
]);
$('svg path').attr('fill','url(#MyGradient)');

// svg:   the owning <svg> element
// id:    an id="..." attribute for the gradient
// stops: an array of objects with <stop> attributes
function createGradient(svg,id,stops){
  var svgNS = svg.namespaceURI;
  var grad  = document.createElementNS(svgNS,'linearGradient');
  grad.setAttribute('id',id);
  for (var i=0;i<stops.length;i++){
    var attrs = stops[i];
    var stop = document.createElementNS(svgNS,'stop');
    for (var attr in attrs){
      if (attrs.hasOwnProperty(attr)) stop.setAttribute(attr,attrs[attr]);
    }
    grad.appendChild(stop);
  }

  var defs = svg.querySelector('defs') ||
      svg.insertBefore( document.createElementNS(svgNS,'defs'), svg.firstChild);
  return defs.appendChild(grad);
}

使用库

或者,您可以包含Keith Woods的“jQuery SVG”插件,它具有许多方便的方法来执行常见的SVG操作,包括能够创建线性渐变的功能。


+1: 我同意我们应该减少对 jQuery 的依赖,但相对于 OP 片段而言,代码的冗长程度有多少呢?不过感谢你介绍有用代码的 jsFiddle。 - CapelliC
@chac 如果简洁的代码无法正常工作,那么它有多有效呢? :) 这与Keith Woods的jQuery SVG插件类似,通过将DOM代码放置在可重用的函数中,实际创建渐变的代码比jQuery原始XML要 - Phrogz
1
谢谢你的提示,我之前不知道元素有命名空间。最终我找到了更适合jQuery的方法并在我的回答中发布了它。 - Hoffmann
有没有不使用JavaScript的方法?一些网站不允许在SVG图像中使用JavaScript。 - posfan12
非常有用,可以创建动态渐变。 - isuru

3
找到了一个解决方案。虽然有点丑,但不需要使用额外的插件。
显然,在首次创建SVG时必须在标签中包含模式(可能仅在那时读取)。
因此,将SVG标记的包装器内容替换为它们自己就可以起作用(“base”是该包装器)。
$(base).html($(base).html())

我理解的是否正确,你只使用jQuery,在你的脚本中分配(例如) base = $('#embed_container_id')?我正在研究 jQuery.SVG,并且仍然不清楚DOM的通用部分与特定部分在哪里。 - CapelliC
这个方法有效的原因是将元素放置在<svg xmlns="…">内部会使其使用正确的命名空间进行创建。 - Phrogz

3
我只是想来一趟并说我已经找到了一种更优雅的解决方案,它允许你在不使用jQuery SVG库(该库不再更新且在jQuery 1.8或更高版本中存在一些问题)的情况下继续使用jQuery与SVG元素。 只需使用这样的函数:
createSVGElement= function(element) {
    return $(document.createElementNS('http://www.w3.org/2000/svg', element));
}

它在SVG命名空间上创建了一个SVG元素,并使用jQuery对其进行封装。一旦元素在正确的命名空间中创建,您就可以自由地使用jQuery:

然后,您可以以以下方式使用该函数:

var $myGradient= createSVGElement('linearGradient')
    .attr( {
        id:"MyGradient"
    });

//if you dont need `defs`, skip this next line
var $myDefs = createSVGElement('defs');

createSVGElement('stop')
    .attr({
        offset: "5%",
        "stop-color": "#F60"
    })
    .appendTo($myGradient);


createSVGElement('stop')
    .attr({
        offset:"95%",
        "stop-color":"#FF6"
    })
    .appendTo($myGradient);

//Use this if you already have `defs`
$('svg defs').prepend($myGradient);

//Use this if you dont have `defs`
$('svg').prepend($myDefs);
$('svg defs').prepend($myGradient);

由于需要手动创建每个元素,因此它不像你希望的那样紧凑,但比使用DOM方法操纵所有内容要好得多。

一个小提示,jQuery .attr()函数假定所有属性都是小写的,但这对SVG元素(例如<svg>标记中的viewBox属性)并非如此。为了解决这个问题,在设置具有大写字母的属性时,请使用以下方式:

$("svg")[0].setAttribute("viewBox", "0 0 1000 1000");

尝试了很多次,似乎无法使其正常工作。您能否提供一个演示程序(fiddle),以便我们能够看到它的实际运行情况? - Fizzix
嘿 @fizzix,我看到你编辑了我的回答,它有一些错误。你弄好了吗?不管怎样,我只是想补充一下,SVG 元素中的属性是区分大小写的。jQuery 的 .attr() 函数默认它们是不区分大小写的。我会编辑这个回答并添加这个信息。 - Hoffmann
嘿@Hoffmann :) - 不幸的是,最终我无法让它工作,所以我选择了Phrogz的解决方案。 - Fizzix

3

我认为您需要使用jQuery的SVG插件(可以在这里找到)。当使用“普通”jQuery库添加SVG元素时,可能会混淆命名空间。

请尝试以下操作:

svg.linearGradient( $('svg defs'), 
                    'MyGradient', 
                    [ ['5%', '#F60'], ['95%', '#FF6']] );

(不是很确定,你可能需要稍微调整一下那段代码。)
编辑
刚刚创建了这个代码片段,以测试论点(如@Phrogz所建议的)。实际上,它返回插入的

2
@chac 这很容易做到;只需 console.log($('#MyGradient')[0].namespaceURI); 如果它不是 http://www.w3.org/2000/svg,那么这就是问题所在。 Sirko 应该这样做,而不仅仅是猜测。 - Phrogz
Phrogz,这个小技巧救了我的一天。谢谢。 - Liglo App

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