jQuery SVG, why can't I addClass?

JavascriptJqueryCssSvgJquery Svg

Javascript Problem Overview


I am using jQuery SVG. I can't add or remove a class to an object. Anyone know my mistake?

The SVG:

<rect class="jimmy" id="p5" x="200" y="200" width="100" height="100" />

The jQuery that won't add the class:

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

I know the SVG and jQuery are working together fine because I can target the object and fire an alert when it's clicked:

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

Javascript Solutions


Solution 1 - Javascript

Edit 2016: read the next two answers.

  • JQuery 3 fixes the underlying issue
  • Vanilla JS: element.classList.add('newclass') works in modern browsers

JQuery (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");

Solution 2 - Javascript

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");
});

Solution 3 - Javascript

jQuery 3 does not have this problem

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 Problem:

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.

Excerpt from jQuery 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.

Consider the following code:

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.

How the jQuery SVG plugin handles this:

The jQuery SVG plugin works around this by patching the relevant functions to add SVG support.

Excerpt from jQuery SVG 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's historical stance on the issue:

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.

Solution 4 - Javascript

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

Solution 5 - Javascript

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;
};

Solution 6 - Javascript

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/

Solution 7 - Javascript

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.

Solution 8 - Javascript

jQuery does not support the classes of SVG elements. You can get the element directly $(el).get(0) and use classList and add / remove. There is a trick with this too in that the topmost SVG element is actually a normal DOM object and can be used like every other element in jQuery. In my project I created this to take care of what I needed but the documentation provided on the Mozilla Developer Network has a shim that can be used as an alternative.

example

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

An alternative all tougher is to use D3.js as your selector engine instead. My projects have charts that are built with it so it's also in my app scope. D3 correctly modifies the class attributes of vanilla DOM elements and SVG elements. Though adding D3 for just this case would likely be overkill.

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

Solution 9 - Javascript

##jQuery 2.2 supports SVG class manipulation

The jQuery 2.2 and 1.12 Released post includes the following quote:

> While jQuery is a HTML library, we agreed that class support for SVG elements could be useful. Users will now be able to call the .addClass(), .removeClass(), .toggleClass(), and .hasClass() methods on SVG. jQuery now changes the class attribute rather than the className property. This also makes the class methods usable in general XML documents. Keep in mind that many other things will not work with SVG, and we still recommend using a library dedicated to SVG if you need anything beyond class manipulation.

Example using jQuery 2.2.0

It tests:

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

If you click on that small square, it will change its color because the class attribute is added / removed.

$("#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>

Solution 10 - Javascript

I use Snap.svg to add a class to and SVG.

var jimmy = Snap(" .jimmy ")

jimmy.addClass("exampleClass");

http://snapsvg.io/docs/#Element.addClass

Solution 11 - Javascript

Here is my rather inelegant but working code that deals with the following issues (without any dependencies):

  • classList not existing on <svg> elements in IE
  • className not representing the class attribute on <svg> elements in IE
  • Old IE's broken getAttribute() and setAttribute() implementations

It uses classList where possible.

Code:

var classNameContainsClass = function(fullClassName, className) {
    return !!fullClassName &&
           new RegExp("(?:^|\\s)" + className + "(?:\\s|$)").test(fullClassName);
};

var hasClass = function(el, className) {
    if (el.nodeType !== 1) {
        return false;
    }
    if (typeof el.classList == "object") {
        return (el.nodeType == 1) && el.classList.contains(className);
    } else {
        var classNameSupported = (typeof el.className == "string");
        var elClass = classNameSupported ? el.className : el.getAttribute("class");
        return classNameContainsClass(elClass, className);
    }
};

var addClass = function(el, className) {
    if (el.nodeType !== 1) {
        return;
    }
    if (typeof el.classList == "object") {
        el.classList.add(className);
    } else {
        var classNameSupported = (typeof el.className == "string");
        var elClass = classNameSupported ?
            el.className : el.getAttribute("class");
        if (elClass) {
            if (!classNameContainsClass(elClass, className)) {
                elClass += " " + className;
            }
        } else {
            elClass = className;
        }
        if (classNameSupported) {
            el.className = elClass;
        } else {
            el.setAttribute("class", elClass);
        }
    }
};

var removeClass = (function() {
    function replacer(matched, whiteSpaceBefore, whiteSpaceAfter) {
        return (whiteSpaceBefore && whiteSpaceAfter) ? " " : "";
    }

    return function(el, className) {
        if (el.nodeType !== 1) {
            return;
        }
        if (typeof el.classList == "object") {
            el.classList.remove(className);
        } else {
            var classNameSupported = (typeof el.className == "string");
            var elClass = classNameSupported ?
                el.className : el.getAttribute("class");
            elClass = elClass.replace(new RegExp("(^|\\s)" + className + "(\\s|$)"), replacer);
            if (classNameSupported) {
                el.className = elClass;
            } else {
                el.setAttribute("class", elClass);
            }
        }
    }; //added semicolon here
})();

Example usage:

var el = document.getElementById("someId");
if (hasClass(el, "someClass")) {
    removeClass(el, "someClass");
}
addClass(el, "someOtherClass");

Solution 12 - Javascript

One workaround could be to addClass to a container of the svg element:

$('.svg-container').addClass('svg-red');

.svg-red svg circle{
    fill: #ED3F32;
}

<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div class="svg-container">
  <svg height="40" width="40">
      <circle cx="20" cy="20" r="20"/>
  </svg>
</div>

Solution 13 - Javascript

I wrote this in my project, and it works... probably;)

$.fn.addSvgClass = function(className) {

    var attr
    this.each(function() {
        attr = $(this).attr('class')
        if(attr.indexOf(className) < 0) {
            $(this).attr('class', attr+' '+className+ ' ')
        }
    })
};    

$.fn.removeSvgClass = function(className) {

    var attr
    this.each(function() {
        attr = $(this).attr('class')
        attr = attr.replace(className , ' ')
        $(this).attr('class' , attr)
    })
};    

examples

$('path').addSvgClass('fillWithOrange')
$('path').removeSvgClass('fillWithOrange')

Solution 14 - Javascript

Inspired by the answers above, especially by Sagar Gala, I've created this API. You may use it if you don't want or can't upgrade your jquery version.

Solution 15 - Javascript

Or just use old-school DOM methods when JQ has a monkey in the middle somewhere.

var myElement = $('#my_element')[0];
var myElClass = myElement.getAttribute('class').split(/\s+/g);
//splits class into an array based on 1+ white space characters

myElClass.push('new_class');

myElement.setAttribute('class', myElClass.join(' '));

//$(myElement) to return to JQ wrapper-land

Learn the DOM people. Even in 2016's framework-palooza it helps quite regularly. Also, if you ever hear someone compare the DOM to assembly, kick them for me.

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionDon PView Question on Stackoverflow
Solution 1 - JavascriptforrestoView Answer on Stackoverflow
Solution 2 - JavascriptTomas MikulaView Answer on Stackoverflow
Solution 3 - JavascriptAlexander O'MaraView Answer on Stackoverflow
Solution 4 - JavascriptnavView Answer on Stackoverflow
Solution 5 - JavascriptSagar GalaView Answer on Stackoverflow
Solution 6 - JavascriptbennedichView Answer on Stackoverflow
Solution 7 - JavascriptZiadView Answer on Stackoverflow
Solution 8 - JavascriptQueueHammerView Answer on Stackoverflow
Solution 9 - JavascriptROMANIA_engineerView Answer on Stackoverflow
Solution 10 - JavascriptcjohndesignView Answer on Stackoverflow
Solution 11 - JavascriptTim DownView Answer on Stackoverflow
Solution 12 - JavascriptClaytroniconView Answer on Stackoverflow
Solution 13 - JavascriptDariusz MajchrzakView Answer on Stackoverflow
Solution 14 - JavascriptStonecrusherView Answer on Stackoverflow
Solution 15 - JavascriptErik ReppenView Answer on Stackoverflow