jQuery SVG,为什么我无法添加Class?

304
我使用jQuery SVG,无法向对象添加或删除类。有人知道我的错误在哪里吗?
SVG内容:
<rect class="jimmy" id="p5" x="200" y="200" width="100" height="100" />

不能添加类的 jQuery:

$(".jimmy").click(function() {
    $(this).addClass("clicked");
});

我知道SVG和jQuery可以很好地协同工作,因为我可以定位到对象并在其被单击时触发警报:

$(".jimmy").click(function() {
    alert('Handler for .click() called.');
});

4
这里有一篇好的文章:http://toddmotto.com/hacking-svg-traversing-with-ease-addclass-removeclass-toggleclass-functions/ 文章介绍了如何使用 addClass、removeClass 和 toggleClass 函数轻松遍历 SVG(可缩放矢量图形),并对其进行修改。阅读本文可以帮助您更好地掌握 SVG 的实际应用。 - Kris
8
为什么我不能使用 jQuery 的类函数仍然没有得到解答... 如果我可以通过 jQuery 的 attr 函数访问 SVG 元素的属性,为什么类函数也不能这样做?难道是 jQuery 失败了吗? - Ryan Griggs
2
真的,有人知道为什么吗? - Ben Wilde
一个小型的库,可以为SVG文件添加此功能:https://github.com/toddmotto/lunar - Chuck Le Butt
6
"为什么"是因为jQuery使用了"className"属性,它是HTML元素的字符串属性,但对于SVG元素来说,它是一个SVG动画字符串。这意味着它不能像HTML元素那样被赋值。因此,jQuery代码在尝试分配该值时会崩溃。 - Jon Watte
根据 https://dev59.com/-Goy5IYBdhLWcg3wUcRL#34698009 的回答,正确的做法是:升级到 jQuery 1.12.0、2.2.0 或 3.0 版本或更高版本。 - Ian Kemp
15个回答

439

2016年的更新:请阅读接下来的两个答案。

  • JQuery 3修复了潜在的问题
  • 纯JS:element.classList.add('newclass') 在现代浏览器中可行

JQuery(小于3版本)不能向SVG添加类。

.attr() 可用于SVG,因此如果您想依赖jQuery:

// Instead of .addClass("newclass")
$("#item").attr("class", "oldclass newclass");
// Instead of .removeClass("newclass")
$("#item").attr("class", "oldclass");

如果您不想依赖jQuery:

var element = document.getElementById("item");
// Instead of .addClass("newclass")
element.setAttribute("class", "oldclass newclass");
// Instead of .removeClass("newclass")
element.setAttribute("class", "oldclass");

完美。这应该是答案。尽管真正的答案是让jQuery默认包含这个功能,至少作为一个设置或其他什么东西。 - AwokeKnowing
2
.attr("class", "oldclass")(或更繁琐的.setAttribute("class", "oldclass"))并不等同于删除newclass - Tom
OP问的是“为什么”,而不是“如何”。因此,这个答案根本没有回答问题。 - vsync
1
只是一个补充说明:我尝试了JQuery 3,但问题仍未解决。 - Joao Arruda
关于你的2016年classList编辑 - 请阅读下面Tomas Mikula答案中的评论。IE不在SVG元素中实现classList。 - TimHayes
显示剩余8条评论

76

在 DOM API 中有一个element.classList,它适用于 HTML 和 SVG 元素。不需要使用 jQuery SVG 插件,甚至不需要 jQuery。

$(".jimmy").click(function() {
    this.classList.add("clicked");
});

3
我测试了一下,发现在Chrome浏览器中,对于SVG子元素,.classList属性似乎是未定义的。 - Chris Jaynes
3
在 Chrome 中可以正常运行。我将 SVG 嵌入到 HTML 中,因此我的文档结构看起来像这样:<html><svg><circle class="jimmy" ...></svg></html>。 - Tomas Mikula
1
我使用这个代码在<g> SVG元素中添加类,Chrome浏览器中运行良好。 - Rachelle Uy
20
IE浏览器不支持在SVG元素上使用.classList以及相关API。您可以参考http://caniuse.com/#feat=classlist和“已知问题”列表来了解更多信息,其中也提到WebKit浏览器存在同样的问题。 - Shenme
9
随着时间流逝,这个答案变得更加正确(并且是最佳答案)。 - Gerrat
显示剩余5条评论

69

jQuery 3没有这个问题

jQuery 3.0的修改之一是:

添加SVG类操作(#2199, 20aaed3)

解决此问题的一种方法是升级到jQuery 3。它非常好用:

var flip = true;
setInterval(function() {
    // Could use toggleClass, but demonstrating addClass.
    if (flip) {
        $('svg circle').addClass('green');
    }
    else {
        $('svg circle').removeClass('green');
    }
    flip = !flip;
}, 1000);
svg circle {
    fill: red;
    stroke: black;
    stroke-width: 5;
}
svg circle.green {
    fill: green;
}
<script src="https://code.jquery.com/jquery-3.0.0.min.js"></script>

<svg>
    <circle cx="50" cy="50" r="25" />
</svg>


问题:

jQuery类操作函数无法与SVG元素一起使用的原因是因为jQuery 3.0之前的版本在这些函数中使用className属性。

来自jQuery attributes/classes.js的摘录:

cur = elem.nodeType === 1 && ( elem.className ?
    ( " " + elem.className + " " ).replace( rclass, " " ) :
    " "
);

这在HTML元素中的表现如预期,但对于SVG元素,className有一点不同。对于SVG元素,className不是一个字符串,而是SVGAnimatedString的实例。
考虑以下代码:

var test_div = document.getElementById('test-div');
var test_svg = document.getElementById('test-svg');
console.log(test_div.className);
console.log(test_svg.className);
#test-div {
    width: 200px;
    height: 50px;
    background: blue;
}
<div class="test div" id="test-div"></div>

<svg width="200" height="50" viewBox="0 0 200 50">
  <rect width="200" height="50" fill="green" class="test svg" id="test-svg" />
</svg>

如果您运行此代码,您将在开发者控制台中看到类似以下的内容。
test div
SVGAnimatedString { baseVal="test svg",  animVal="test svg"}

如果我们像jQuery一样将SVGAnimatedString对象转换为字符串,我们会得到[object SVGAnimatedString],这就是jQuery失败的地方。
jQuery SVG插件如何处理这个问题:
jQuery SVG插件通过修补相关函数来添加SVG支持来解决这个问题。
摘自jQuery SVG jquery.svgdom.js
function getClassNames(elem) {
    return (!$.svg.isSVGElem(elem) ? elem.className :
        (elem.className ? elem.className.baseVal : elem.getAttribute('class'))) || '';
}

这个函数将检测一个元素是否为SVG元素,如果是,则会使用SVGAnimatedString对象的baseVal属性(如果可用),然后退回到class属性。

jQuery在这个问题上的历史立场:

目前,jQuery在其Won't Fix页面上列出了这个问题。以下是相关部分。

SVG/VML或命名空间元素错误

jQuery主要是用于HTML DOM的库,因此大多数与SVG/VML文档或命名空间元素相关的问题都不在范围内。 但是,我们确实尝试解决“渗透”到HTML文档中的问题,例如从SVG冒泡出来的事件。

显然,jQuery认为完整的SVG支持超出了jQuery核心的范围,并且更适合插件。


38

如果你有动态类或不知道哪些类已经被应用了,那么我认为这种方法是最好的选择:

// addClass
$('path').attr('class', function(index, classNames) {
    return classNames + ' class-name';
});

// removeClass
$('path').attr('class', function(index, classNames) {
    return classNames.replace('class-name', '');
});

1
这对我来说真是救命稻草,因为我需要改变很多SVG元素才能完成工作。+999分! :) - Karl
很难相信即使过了两年并发布了2.x.x版本,jQuery仍然会坚持这一点。你的修复程序“nav”为我解决了问题。其余的修复程序都是不必要的复杂。谢谢! - unrivaledcreations

18

根据以上答案,我创建了以下API。

/*
 * .addClassSVG(className)
 * Adds the specified class(es) to each of the set of matched SVG elements.
 */
$.fn.addClassSVG = function(className){
    $(this).attr('class', function(index, existingClassNames) {
        return ((existingClassNames !== undefined) ? (existingClassNames + ' ') : '') + className;
    });
    return this;
};

/*
 * .removeClassSVG(className)
 * Removes the specified class to each of the set of matched SVG elements.
 */
$.fn.removeClassSVG = function(className){
    $(this).attr('class', function(index, existingClassNames) {
        var re = new RegExp('\\b' + className + '\\b', 'g');
        return existingClassNames.replace(re, '');
    });
    return this;
};

3
这很有帮助,但算法存在问题:1. 如果没有现有的类字符串,在添加类时会得到“未定义的className”。2. 如果类字符串包含一个是正则表达式超集的类,则在类字符串中会留下垃圾。例如,如果尝试删除类“dog”,而类字符串包含“dogfood”,则类字符串中会剩下“food”。这里提供了一些修复方法:https://jsfiddle.net/hellobrett/c2c2zfto/ - Brett
1
这不是一个完美的解决方案。它会替换掉所有包含您想要删除的类的单词。 - tom10271
该帖子已编辑,解决了上述两个问题。 - oromoiluig
当您删除类时,可能希望在正则表达式开头添加*以避免留下空格。 - oromoiluig

13

已添加 - 似乎仍然无法正常工作。我一直在查看jquery SVG网站上的文档,但没有清晰的解释说明如何利用其额外的功能。 - Don P

8

只需为所有SVG节点添加缺失的原型构造函数:

SVGElement.prototype.hasClass = function (className) {
  return new RegExp('(\\s|^)' + className + '(\\s|$)').test(this.getAttribute('class'));
};

SVGElement.prototype.addClass = function (className) { 
  if (!this.hasClass(className)) {
    this.setAttribute('class', this.getAttribute('class') + ' ' + className);
  }
};

SVGElement.prototype.removeClass = function (className) {
  var removedClass = this.getAttribute('class').replace(new RegExp('(\\s|^)' + className + '(\\s|$)', 'g'), '$2');
  if (this.hasClass(className)) {
    this.setAttribute('class', removedClass);
  }
};

您可以不需要使用jQuery,以以下方式使用它:

this.addClass('clicked');

this.removeClass('clicked');

所有荣誉归Todd Moto


IE11对这段代码支持不好。使用这个polyfill会更好一些:https://github.com/eligrey/classList.js - ryanrain

5

jQuery 2.2支持SVG类操作

jQuery 2.2和1.12发布文章中包含以下引用:

虽然jQuery是一个HTML库,但我们认为对SVG元素的类支持可能会很有用。用户现在可以在SVG上调用.addClass().removeClass().toggleClass().hasClass()方法。jQuery现在更改类属性而不是className属性。这也使得类方法在一般XML文档中可用。请记住,许多其他东西在SVG中将无法使用,如果您需要超出类操作之外的任何内容,我们仍建议使用专门用于SVG的库。

使用jQuery 2.2.0的示例

它测试:

  • .addClass()
  • .removeClass()
  • .hasClass()

如果单击该小正方形,它将更改其颜色,因为添加/删除了class属性。

$("#x").click(function() {
    if ( $(this).hasClass("clicked") ) {
        $(this).removeClass("clicked");
    } else {
        $(this).addClass("clicked");
    }
});
.clicked {
    fill: red !important;  
}
<html>

<head>
    <script src="https://code.jquery.com/jquery-2.2.0.js"></script>
</head>

<body>
    <svg width="80" height="80">
        <rect id="x" width="80" height="80" style="fill:rgb(0,0,255)" />
    </svg>
</body>

</html>


5
jQuery不支持SVG元素的类。您可以直接获取元素$(el).get(0)并使用classListadd / remove。这里有一个技巧,最顶层的SVG元素实际上是一个普通的DOM对象,可以像jQuery中的其他元素一样使用。在我的项目中,我创建了一个来处理我需要的内容,但在Mozilla Developer Network提供的文档中,有一个shim可用作替代方案。

示例

function addRemoveClass(jqEl, className, addOrRemove) 
{
  var classAttr = jqEl.attr('class');
  if (!addOrRemove) {
    classAttr = classAttr.replace(new RegExp('\\s?' + className), '');
    jqEl.attr('class', classAttr);
  } else {
    classAttr = classAttr + (classAttr.length === 0 ? '' : ' ') + className;
    jqEl.attr('class', classAttr);
  }
}

一个更加可行的解决方案是使用 D3.js 作为您的选择器引擎。我的项目中的图表都是用它构建的,所以它也在我的应用程序范围内。D3 可以正确地修改原始 DOM 元素和 SVG 元素的 class 属性。但仅为此情况添加 D3 可能会过度。

d3.select(el).classed('myclass', true);

1
谢谢那个d3的提示...我已经在页面上有d3了,所以决定使用它。非常有用 :) - Muers

4

1
虽然这个链接可能回答了问题,但最好在此处包含答案的基本部分并提供参考链接。仅有链接的答案如果链接页面发生更改可能会变得无效。 - Tristan

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