[javascript] jQuery SVG, why can't I addClass?

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

This question is related to javascript jquery css svg jquery-svg

The answer is


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:

_x000D_
_x000D_
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);
_x000D_
svg circle {
    fill: red;
    stroke: black;
    stroke-width: 5;
}
svg circle.green {
    fill: green;
}
_x000D_
<script src="https://code.jquery.com/jquery-3.0.0.min.js"></script>

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


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:

_x000D_
_x000D_
var test_div = document.getElementById('test-div');
var test_svg = document.getElementById('test-svg');
console.log(test_div.className);
console.log(test_svg.className);
_x000D_
#test-div {
    width: 200px;
    height: 50px;
    background: blue;
}
_x000D_
<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>
_x000D_
_x000D_
_x000D_

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.


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.

_x000D_
_x000D_
$("#x").click(function() {_x000D_
    if ( $(this).hasClass("clicked") ) {_x000D_
        $(this).removeClass("clicked");_x000D_
    } else {_x000D_
        $(this).addClass("clicked");_x000D_
    }_x000D_
});
_x000D_
.clicked {_x000D_
    fill: red !important;  _x000D_
}
_x000D_
<html>_x000D_
_x000D_
<head>_x000D_
    <script src="https://code.jquery.com/jquery-2.2.0.js"></script>_x000D_
</head>_x000D_
_x000D_
<body>_x000D_
    <svg width="80" height="80">_x000D_
        <rect id="x" width="80" height="80" style="fill:rgb(0,0,255)" />_x000D_
    </svg>_x000D_
</body>_x000D_
_x000D_
</html>
_x000D_
_x000D_
_x000D_


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.


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

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

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/


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

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

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

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

_x000D_
_x000D_
$('.svg-container').addClass('svg-red');
_x000D_
.svg-red svg circle{_x000D_
    fill: #ED3F32;_x000D_
}
_x000D_
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>_x000D_
<div class="svg-container">_x000D_
  <svg height="40" width="40">_x000D_
      <circle cx="20" cy="20" r="20"/>_x000D_
  </svg>_x000D_
</div>
_x000D_
_x000D_
_x000D_


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.


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

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.


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

var jimmy = Snap(" .jimmy ")

jimmy.addClass("exampleClass");

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


Examples related to javascript

need to add a class to an element How to make a variable accessible outside a function? Hide Signs that Meteor.js was Used How to create a showdown.js markdown extension Please help me convert this script to a simple image slider Highlight Anchor Links when user manually scrolls? Summing radio input values How to execute an action before close metro app WinJS javascript, for loop defines a dynamic variable name Getting all files in directory with ajax

Examples related to jquery

How to make a variable accessible outside a function? Jquery assiging class to th in a table Please help me convert this script to a simple image slider Highlight Anchor Links when user manually scrolls? Getting all files in directory with ajax Bootstrap 4 multiselect dropdown Cross-Origin Read Blocking (CORB) bootstrap 4 file input doesn't show the file name Jquery AJAX: No 'Access-Control-Allow-Origin' header is present on the requested resource how to remove json object key and value.?

Examples related to css

need to add a class to an element Using Lato fonts in my css (@font-face) Please help me convert this script to a simple image slider Why there is this "clear" class before footer? How to set width of mat-table column in angular? Center content vertically on Vuetify bootstrap 4 file input doesn't show the file name Bootstrap 4: responsive sidebar menu to top navbar Stylesheet not loaded because of MIME-type Force flex item to span full row width

Examples related to svg

How to extract svg as file from web page How to display svg icons(.svg files) in UI using React Component? Why don’t my SVG images scale using the CSS "width" property? How to show SVG file on React Native? Easiest way to use SVG in Android? IE11 meta element Breaks SVG img src SVG changing the styles with CSS How to use SVG markers in Google Maps API v3 Embedding SVG into ReactJS How to change the color of an svg element?

Examples related to jquery-svg

Create a map with clickable provinces/states using SVG, HTML/CSS, ImageMap jQuery SVG, why can't I addClass?