89

Since element.classList is not supported in IE 9 and Safari-5, what's an alternative cross-browser solution?

No-frameworks please.

Solution must work in at least IE 9, Safari 5, FireFox 4, Opera 11.5, and Chrome.

Related posts (but does not contain solution):

  1. how to add and remove css class

  2. Add and remove a class with animation

  3. Add remove class?

Community
  • 1
  • 1
Pacerier
  • 86,231
  • 106
  • 366
  • 634
  • 8
    I will never understand the need for cross-browser compatible code and the refusal to use a framework. That's their very purpose. It's like trying to eat spaghetti but refusing to use a fork, sure you can get by but it's messy. – davin Jul 22 '11 at 08:33
  • +1 @davin. But, anyway, some professional projects with less professional project managers or responsibles of some projects are rejecting the use of frameworks because of other non-professional customer decisions. – Matías Fidemraizer Jul 22 '11 at 08:35
  • 2
    @davin there's a difference between frameworks and shims. Shims are also valid. – Raynos Jul 22 '11 at 08:37
  • 2
    @Raynos, most of the time what you think is going to be just one shim ends up being five, which leads to much less maintainable, less tested code that would be better replaced by a framework. – davin Jul 22 '11 at 08:42
  • 5
    @davin You just need one shim. It's called the DOM shim. Then you just write standards compliant code. You do need a framework, it's called the DOM. – Raynos Jul 22 '11 at 08:47
  • 29
    @davin **because eating spaghetti with a fork doesn't mean that you are not allowed to understand how a fork works.** – Pacerier Jul 22 '11 at 08:53
  • 1
    @Pacerier, who said anything about understanding how things work? Your question seems to be one of implementation, not understanding. If your goal is to understand then look at the source code of any of the frameworks that do this stuff (https://github.com/jquery/jquery/blob/master/src/attributes.js#L39). That is very much distinct from whether or not it's a good design decision to implement. – davin Jul 22 '11 at 09:04
  • 1
    my question is one of understanding. you assumed wrongly – Pacerier Jul 22 '11 at 10:06
  • please check this answer [Remove and add css class using javascript](http://stackoverflow.com/a/33434902/2466310) – thejustv Oct 30 '15 at 11:29

13 Answers13

67

Here is solution for addClass, removeClass, hasClass in pure javascript solution.

Actually it's from http://jaketrent.com/post/addremove-classes-raw-javascript/

function hasClass(ele,cls) {
  return !!ele.className.match(new RegExp('(\\s|^)'+cls+'(\\s|$)'));
}

function addClass(ele,cls) {
  if (!hasClass(ele,cls)) ele.className += " "+cls;
}

function removeClass(ele,cls) {
  if (hasClass(ele,cls)) {
    var reg = new RegExp('(\\s|^)'+cls+'(\\s|$)');
    ele.className=ele.className.replace(reg,' ');
  }
}
emil
  • 6,074
  • 4
  • 30
  • 38
61

Look at these oneliners:

  1. Remove class:

    element.classList.remove('hidden');
    
  2. Add class (will not add it twice if already present):

    element.classList.add('hidden');
    
  3. Toggle class (adds the class if it's not already present and removes it if it is)

    element.classList.toggle('hidden');
    

That's all! I made a test - 10000 iterations. 0.8s.

TOPKAT
  • 6,667
  • 2
  • 44
  • 72
Sergio Belevskij
  • 2,478
  • 25
  • 24
  • 1
    Note that classList is not supported in IE 9 which is stated in OP's question – Warren Spencer Jun 08 '16 at 14:06
  • 11
    while it doesn't satisfy a restraint that was extremely particular to OP, I think this question gets looked at by many people hoping for an answer to the general question without those specific restraints. I was very happy to find this, and only noticed it on my second visit. I'm glad I did--much cleaner solution. Thanks! – Kyle Baker Apr 13 '18 at 09:45
  • 2
    Mando likes, "That's the way" – ieselisra Jan 23 '23 at 16:07
33

I just wrote these up:

function addClass(el, classNameToAdd){
    el.className += ' ' + classNameToAdd;   
}

function removeClass(el, classNameToRemove){
    var elClass = ' ' + el.className + ' ';
    while(elClass.indexOf(' ' + classNameToRemove + ' ') !== -1){
         elClass = elClass.replace(' ' + classNameToRemove + ' ', '');
    }
    el.className = elClass;
}

I think they'll work in all browsers.

hugo der hungrige
  • 12,382
  • 9
  • 57
  • 84
Paul
  • 139,544
  • 27
  • 275
  • 264
  • This doesn't work, for example if you try to remove the only class on an element (which doesn't have a space either side of its name) – Gareth Jul 22 '11 at 08:50
  • @Gareth, that will work. It inserts a space on each side in line one of removeClass. – Paul Jul 22 '11 at 09:00
  • 6
    That will not work. If element has class 'a b c' and you try to remove b, it will set new class to ' ac '. So you have to change `''` by `' '` in the `replace` function. Moreover, class is a reserved keyword. – Bali Balo Nov 28 '13 at 21:30
  • 1
    I've fixed your solution to 1. Not use "class", which is a reserved word 2. Your broken `removeClass` method, which creates tons of clutter after repeated use due to whitespace not being trimmed See solution below as an answer. – Drew Apr 15 '14 at 04:56
  • I used this code with the addition of this code just after the replace statement: `while (elClass[0] === " ") elClass = elClass.substr(1); while (elClass[elClass.length - 1] === " ") elClass = elClass.substr(0, elClass.length - 1);` This will delete the leading and trailing space which might still be present – Filip Cornelissen Jul 01 '15 at 08:21
  • fore those people who want's to use this, change class word to theClass like this function(el, theClass){...}; – George Carlin Apr 20 '16 at 09:28
21

The simplest is element.classList which has remove(name), add(name), toggle(name), and contains(name) methods and is now supported by all major browsers.

For older browsers you change element.className. Here are two helper:

function addClass(element, className){
    element.className += ' ' + className;   
}

function removeClass(element, className) {
    element.className = element.className.replace(
        new RegExp('( |^)' + className + '( |$)', 'g'), ' ').trim();
}
Wernight
  • 36,122
  • 25
  • 118
  • 131
16

One way to play around with classes without frameworks/libraries would be using the property Element.className, which "gets and sets the value of the class attribute of the specified element." (from the MDN documentation).
As @matías-fidemraizer already mentioned in his answer, once you get the string of classes for your element you can use any methods associated with strings to modify it.

Here's an example:
Assuming you have a div with the ID "myDiv" and that you want to add to it the class "main__section" when the user clicks on it,

window.onload = init;

function init() {
  document.getElementById("myDiv").onclick = addMyClass;
}

function addMyClass() {
  var classString = this.className; // returns the string of all the classes for myDiv
  var newClass = classString.concat(" main__section"); // Adds the class "main__section" to the string (notice the leading space)
  this.className = newClass; // sets className to the new string
}
Yago DLT
  • 208
  • 2
  • 3
12

Read this Mozilla Developer Network article:

Since element.className property is of type string, you can use regular String object functions found in any JavaScript implementation:

  • If you want to add a class, first use String.indexOf in order to check if class is present in className. If it's not present, just concatenate a blank character and the new class name to this property. If it's present, do nothing.

  • If you want to remove a class, just use String.replace, replacing "[className]" with an empty string. Finally use String.trim to remove blank characters at the start and end of element.className.

Matías Fidemraizer
  • 63,804
  • 18
  • 124
  • 206
5

Fixed the solution from @Paulpro

  1. Do not use "class", as it is a reserved word
  2. removeClass function was broken, as it bugged out after repeated use.

`

function addClass(el, newClassName){
    el.className += ' ' + newClassName;   
}

function removeClass(el, removeClassName){
    var elClass = el.className;
    while(elClass.indexOf(removeClassName) != -1) {
        elClass = elClass.replace(removeClassName, '');
        elClass = elClass.trim();
    }
    el.className = elClass;
}
Drew
  • 328
  • 3
  • 8
4

The solution is to

Shim .classList:

Either use the DOM-shim or use Eli Grey's shim below

Disclaimer: I believe the support is FF3.6+, Opera10+, FF5, Chrome, IE8+

/*
 * classList.js: Cross-browser full element.classList implementation.
 * 2011-06-15
 *
 * By Eli Grey, http://eligrey.com
 * Public Domain.
 * NO WARRANTY EXPRESSED OR IMPLIED. USE AT YOUR OWN RISK.
 */

/*global self, document, DOMException */

/*! @source http://purl.eligrey.com/github/classList.js/blob/master/classList.js*/

if (typeof document !== "undefined" && !("classList" in document.createElement("a"))) {

(function (view) {

"use strict";

var
      classListProp = "classList"
    , protoProp = "prototype"
    , elemCtrProto = (view.HTMLElement || view.Element)[protoProp]
    , objCtr = Object
    , strTrim = String[protoProp].trim || function () {
        return this.replace(/^\s+|\s+$/g, "");
    }
    , arrIndexOf = Array[protoProp].indexOf || function (item) {
        var
              i = 0
            , len = this.length
        ;
        for (; i < len; i++) {
            if (i in this && this[i] === item) {
                return i;
            }
        }
        return -1;
    }
    // Vendors: please allow content code to instantiate DOMExceptions
    , DOMEx = function (type, message) {
        this.name = type;
        this.code = DOMException[type];
        this.message = message;
    }
    , checkTokenAndGetIndex = function (classList, token) {
        if (token === "") {
            throw new DOMEx(
                  "SYNTAX_ERR"
                , "An invalid or illegal string was specified"
            );
        }
        if (/\s/.test(token)) {
            throw new DOMEx(
                  "INVALID_CHARACTER_ERR"
                , "String contains an invalid character"
            );
        }
        return arrIndexOf.call(classList, token);
    }
    , ClassList = function (elem) {
        var
              trimmedClasses = strTrim.call(elem.className)
            , classes = trimmedClasses ? trimmedClasses.split(/\s+/) : []
            , i = 0
            , len = classes.length
        ;
        for (; i < len; i++) {
            this.push(classes[i]);
        }
        this._updateClassName = function () {
            elem.className = this.toString();
        };
    }
    , classListProto = ClassList[protoProp] = []
    , classListGetter = function () {
        return new ClassList(this);
    }
;
// Most DOMException implementations don't allow calling DOMException's toString()
// on non-DOMExceptions. Error's toString() is sufficient here.
DOMEx[protoProp] = Error[protoProp];
classListProto.item = function (i) {
    return this[i] || null;
};
classListProto.contains = function (token) {
    token += "";
    return checkTokenAndGetIndex(this, token) !== -1;
};
classListProto.add = function (token) {
    token += "";
    if (checkTokenAndGetIndex(this, token) === -1) {
        this.push(token);
        this._updateClassName();
    }
};
classListProto.remove = function (token) {
    token += "";
    var index = checkTokenAndGetIndex(this, token);
    if (index !== -1) {
        this.splice(index, 1);
        this._updateClassName();
    }
};
classListProto.toggle = function (token) {
    token += "";
    if (checkTokenAndGetIndex(this, token) === -1) {
        this.add(token);
    } else {
        this.remove(token);
    }
};
classListProto.toString = function () {
    return this.join(" ");
};

if (objCtr.defineProperty) {
    var classListPropDesc = {
          get: classListGetter
        , enumerable: true
        , configurable: true
    };
    try {
        objCtr.defineProperty(elemCtrProto, classListProp, classListPropDesc);
    } catch (ex) { // IE 8 doesn't support enumerable:true
        if (ex.number === -0x7FF5EC54) {
            classListPropDesc.enumerable = false;
            objCtr.defineProperty(elemCtrProto, classListProp, classListPropDesc);
        }
    }
} else if (objCtr[protoProp].__defineGetter__) {
    elemCtrProto.__defineGetter__(classListProp, classListGetter);
}

}(self));

}
Raynos
  • 166,823
  • 56
  • 351
  • 396
  • 1
    Definitely a clever shim. It works in FF at least down to 1.5 (can't test below that). It breaks in IE7 and below. For that reason it is next-to-useless on real sites for anything essential. For real use, go with a framework. – theazureshadow Oct 27 '11 at 18:33
  • @theazureshadow psh, Legacy support is for enterprise solutions, who cares about IE7 outside of the enterprise world. – Raynos Oct 27 '11 at 18:35
  • If you care about the 12 percent of users (about one in 8) who currently use IE7 and below (source: http://marketshare.hitslink.com). I think iOS 5 is the first iOS version to support it, too, so at the time of your posting no iPhone users would be supported. Talk about legacy! – theazureshadow Oct 27 '11 at 19:04
  • 1
    @theazureshadow meh, IE7 doesn't need javascript. And mobiles is a whole different ball game. (The shim should work on mobiles) – Raynos Oct 27 '11 at 19:14
  • You're right about mobile. It will really be up to individual cases whether IE7 support is worth it. Most people will find it easily worth it to use a framework to smooth out browser differences -- far beyond classList availability. "IE7 doesn't need javascript" is an extremely questionable statement. – theazureshadow Oct 27 '11 at 20:11
  • @theazureshadow your HTML/CSS website looks fine without javascript in IE7 and if your making a web application for IE7 then I feel your pain, really ask yourself whether that extra 300% effort to get IE7 to work is worth it. (99% it isnt for me) – Raynos Oct 27 '11 at 20:21
3

Improved version of emil's code (with trim())

function hasClass(ele,cls) {
  return !!ele.className.match(new RegExp('(\\s|^)'+cls+'(\\s|$)'));
}

function addClass(ele,cls) {
  if (!hasClass(ele,cls)) ele.className = ele.className.trim() + " " + cls;
}

function removeClass(ele,cls) {
  if (hasClass(ele,cls)) {
    var reg = new RegExp('(\\s|^)'+cls+'(\\s|$)');
    ele.className = ele.className.replace(reg,' ');
    ele.className = ele.className.trim();
  }
}
vagovszkym
  • 1,432
  • 12
  • 16
2
function addClass(element, classString) {
    element.className = element
        .className
        .split(' ')
        .filter(function (name) { return name !== classString; })
        .concat(classString)
        .join(' ');
}

function removeClass(element, classString) {
    element.className = element
        .className
        .split(' ')
        .filter(function (name) { return name !== classString; })
        .join(' ');
}
scott_trinh
  • 155
  • 1
  • 8
2

Just in case if anyone would like to have prototype functions built for elements, this is what I use when I need to manipulate classes of different objects:

Element.prototype.addClass = function (classToAdd) {
  var classes = this.className.split(' ')
  if (classes.indexOf(classToAdd) === -1) classes.push(classToAdd)
  this.className = classes.join(' ')
}

Element.prototype.removeClass = function (classToRemove) {
  var classes = this.className.split(' ')
  var idx =classes.indexOf(classToRemove)
  if (idx !== -1) classes.splice(idx,1)
  this.className = classes.join(' ')
}

Use them like: document.body.addClass('whatever') or document.body.removeClass('whatever')

Instead of body you can also use any other element (div, span, you name it)

0

A easy to understand way:

// Add class 
DOMElement.className += " one";
// Example:
// var el = document.body;
// el.className += " two"

// Remove class 
function removeDOMClass(element, className) {
    var oldClasses      = element.className,
        oldClassesArray = oldClasses.split(" "),
        newClassesArray = [],
        newClasses;

    // Sort
    var currentClassChecked,
        i;     
    for ( i = 0; i < oldClassesArray.length; i++ ) { 
        // Specified class will not be added in the new array
        currentClassChecked = oldClassesArray[i];
        if( currentClassChecked !== className ) { 
            newClassesArray.push(currentClassChecked);
        }
    }

    // Order 
    newClasses = newClassesArray.join(" ");

    // Apply     
    element.className = newClasses;

    return element;

}
// Example:
// var el = document.body;
// removeDOMClass(el, "two")

https://gist.github.com/sorcamarian/ff8db48c4dbf4f5000982072611955a2

Marian07
  • 2,303
  • 4
  • 27
  • 48
0

add css classes: cssClassesStr += cssClassName;

remove css classes: cssClassStr = cssClassStr.replace(cssClassName,"");

add attribute 'Classes': object.setAttribute("class", ""); //pure addition of this attribute

remove attribute: object.removeAttribute("class");

Reporter
  • 3,897
  • 5
  • 33
  • 47