Edit 2016: read the next two answers.
element.classList.add('newclass')
works in modern browsersJQuery (less than 3) can't add a class to an SVG.
.attr()
works with SVG, so if you want to depend on jQuery:
// Instead of .addClass("newclass")
$("#item").attr("class", "oldclass newclass");
// Instead of .removeClass("newclass")
$("#item").attr("class", "oldclass");
And if you don't want to depend on jQuery:
var element = document.getElementById("item");
// Instead of .addClass("newclass")
element.setAttribute("class", "oldclass newclass");
// Instead of .removeClass("newclass")
element.setAttribute("class", "oldclass");
There is element.classList in the DOM API that works for both HTML and SVG elements. No need for jQuery SVG plugin or even jQuery.
$(".jimmy").click(function() {
this.classList.add("clicked");
});
One of the changes listed on the jQuery 3.0 revisions is:
add SVG class manipulation (#2199, 20aaed3)
One solution for this issue would be to upgrade to jQuery 3. It works great:
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>
The reason the jQuery class manipulation functions do not work with the SVG elements is because jQuery versions prior to 3.0 use the className
property for these functions.
attributes/classes.js
:cur = elem.nodeType === 1 && ( elem.className ?
( " " + elem.className + " " ).replace( rclass, " " ) :
" "
);
This behaves as expected for HTML elements, but for SVG elements className
is a little different. For an SVG element, className
is not a string, but an instance of 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>
If you run this code you will see something like the following in your developer console.
test div
SVGAnimatedString { baseVal="test svg", animVal="test svg"}
If we were to cast that SVGAnimatedString
object to a string as jQuery does, we would have [object SVGAnimatedString]
, which is where jQuery fails.
The jQuery SVG plugin works around this by patching the relevant functions to add SVG support.
jquery.svgdom.js
:function getClassNames(elem) {
return (!$.svg.isSVGElem(elem) ? elem.className :
(elem.className ? elem.className.baseVal : elem.getAttribute('class'))) || '';
}
This function will detect if an element is an SVG element, and if it is it will use the baseVal
property of the SVGAnimatedString
object if available, before falling back on the class
attribute.
jQuery currently lists this issue on their Won’t Fix page. Here is the relevant parts.
SVG/VML or Namespaced Elements Bugs
jQuery is primarily a library for the HTML DOM, so most problems related to SVG/VML documents or namespaced elements are out of scope. We do try to address problems that "bleed through" to HTML documents, such as events that bubble out of SVG.
Evidently jQuery considered full SVG support outside the scope of the jQuery core, and better suited for plugins.
If you have dynamic classes or don't know what classes could be already applied then this method I believe is the best approach:
// addClass
$('path').attr('class', function(index, classNames) {
return classNames + ' class-name';
});
// removeClass
$('path').attr('class', function(index, classNames) {
return classNames.replace('class-name', '');
});
Based on above answers I created the following 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;
};
After loading jquery.svg.js
you must load this file: http://keith-wood.name/js/jquery.svgdom.js
.
Source: http://keith-wood.name/svg.html#dom
Working example: http://jsfiddle.net/74RbC/99/
Just add the missing prototype constructor to all SVG nodes:
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);
}
};
You can then use it this way without requiring jQuery:
this.addClass('clicked');
this.removeClass('clicked');
All credit goes to Todd Moto.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With