如何在JavaScript中添加/删除类?

92

8
我永远无法理解为什么需要编写跨浏览器兼容的代码,而拒绝使用框架。这正是它们的目的所在。这就像尝试吃意大利面条,但拒绝使用叉子一样,你当然可以通过,但会很凌乱。 - davin
2
@davin,框架和 shim 之间有区别。Shim 也是有效的。 - Raynos
2
@Raynos,大多数情况下,你认为只需要一个 shim 的地方最终需要五个 shim,这导致代码可维护性和测试性都较差,最好用框架替换。 - davin
5
你只需要一个叫做DOM shim的东西,它可以让你写符合标准的代码。你需要使用的框架就是DOM。请注意,我已经尽量保留原文的意思,让翻译更加通俗易懂。 - Raynos
30
因为用叉子吃意大利面并不意味着你不允许理解叉子的使用方法。 - Pacerier
显示剩余4条评论
13个回答

67

这是纯JavaScript实现addClass、removeClass和hasClass的解决方案。

实际上,这个解决方案来自于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,' ');
  }
}

66

看看这些一句话:

  1. 移除类:

element.classList.remove('hidden');
  • 添加类(如果已存在,则不会再次添加):

  • element.classList.add('hidden');
    
  • 切换类(如果该类不存在,则添加它,如果存在,则删除它)

  • element.classList.toggle('hidden');
    

    搞定了!我进行了一次测试——10000次迭代。用时0.8秒。


    1
    请注意,classList在IE 9中不受支持,这在OP的问题中已经说明。 - Warren Spencer
    11
    虽然这个问题没有满足 OP 特别严格的限制条件,但我认为很多人都会看到这个问题,并希望得到一般性的答案而不受特定限制的影响。我很高兴找到了这个问题,在第二次访问时才注意到它。我很高兴发现了这个更干净的解决方案。谢谢! - Kyle Baker
    2
    Mando 喜欢说:“那就是这样”。 - ieselisra

    33
    我刚写了这些:
    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;
    }
    

    我认为它们在所有浏览器中都会起作用。


    这行代码无法正常运行,例如如果您尝试移除一个元素上唯一的类(类名两侧没有空格),它就会出问题。 - Gareth
    @Gareth,那个可以。它在removeClass的第一行两侧插入了一个空格。 - Paul
    6
    那行不通。如果元素有类名为“a b c”,你尝试移除“b”,它会把新的类名设为“ac”。因此,在replace函数中,你必须将''更改为' '。另外,“class”是一个保留关键字。 - Bali Balo
    1
    我已经修复了你的解决方案:
    1. 不使用“class”,这是一个保留字
    2. 修复了你的removeClass方法,因为空格未被修剪而在重复使用后创建了大量混乱
    请查看下面的答案。
    - Drew
    我在替换语句后面加上了以下代码:while (elClass[0] === " ") elClass = elClass.substr(1); while (elClass[elClass.length - 1] === " ") elClass = elClass.substr(0, elClass.length - 1); 这将删除可能仍然存在的前导和尾随空格。 - Filip Cornelissen
    对于那些想要使用这个的人,将class单词更改为theClass,就像这样function(el, theClass){...}; - George Carlin

    21

    最简单的方法是 element.classList,它有remove(name)add(name)toggle(name)contains(name)方法,并且现在被所有主要浏览器支持

    对于旧版浏览器,您可以更改element.className。以下是两个辅助工具:

    function addClass(element, className){
        element.className += ' ' + className;   
    }
    
    function removeClass(element, className) {
        element.className = element.className.replace(
            new RegExp('( |^)' + className + '( |$)', 'g'), ' ').trim();
    }
    

    这对于使用“createElementNS”创建的SVG元素无效。如何实现? - neoexpert

    16

    在没有使用框架/库的情况下,玩弄类别(classes)的一种方法是使用属性Element.className,它“获取并设置指定元素的类别(class)属性的值。”(来自MDN文档)。
    正如@matías-fidemraizer在他的回答中已经提到的那样,一旦您获得了元素的类别字符串,您就可以使用与字符串相关联的任何方法来修改它。

    以下是一个示例:
    假设您有一个ID为“myDiv”的div元素,并且当用户单击它时,您想要将其添加到“main__section”类中:

    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
    }
    

    15
    如果问题中包含“remove”类,但这个答案却没有,那么怎么能把它作为接受的答案呢? - Motin
    7
    .replace(/\bexmaple\b/, "") 翻译为:替换掉所有单词中出现的 "exmaple"。 - leafiy
    这对于使用“createElementNS”创建的SVG元素无效。如何实现? - neoexpert

    12

    请阅读Mozilla开发者网络文章:

    由于 element.className 属性类型为字符串,您可以使用任何 JavaScript 实现中都可找到的常规 String 对象函数:

    • 如果您想要添加一个类,请先使用 String.indexOf 检查 className 中是否存在该类。 如果不存在,只需将一个空字符和新类名连接到此属性。如果已存在,则不做操作。

    • 如果您想要删除一个类,只需使用 String.replace 替换 "[className]" 为空字符串。最后使用 String.trim 移除 element.className 开头和结尾的空字符。


    5

    修正了 @Paulpro 的解决方案

    1. 不要使用“class”,因为它是一个保留字
    2. removeClass 函数已经损坏,重复使用后会出现错误。

    `

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

    在添加类名之前,你不应该先检查一下它是否存在吗? - JoeTidee
    如果将if(el.className.indexOf(' ' + className) != -1) return;替换为addClass会更有帮助。 - keaton

    4
    解决方案是:
    使用Shim .classList: 要么使用DOM-shim,要么使用Eli Grey的下面的shim。
    免责声明:我认为支持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));
    
    }
    

    1
    绝对是一个聪明的垫片。它至少在FF中可以运行到1.5版本(无法测试低于此版本)。它在IE7及以下版本中会出现问题。因此,在真实网站上,它对于任何重要事项都几乎没有用处。对于实际使用,请选择一个框架。 - theazureshadow
    @theazureshadow 哼,遗留支持是为企业解决方案而设的,谁在企业世界之外还关心IE7呢。 - Raynos
    如果你关心现在使用IE7及以下版本(来源:http://marketshare.hitslink.com)的用户中的12%(约为八分之一),我认为iOS 5也是第一个支持它的iOS版本,所以在发布时不会支持任何iPhone用户。谈论遗留问题! - theazureshadow
    1
    @theazureshadow 嗯,IE7不需要JavaScript。而移动设备则是完全不同的游戏规则。(这个 shim 应该可以在移动设备上工作) - Raynos
    你对移动端的看法是正确的。是否支持IE7取决于具体情况。大多数人会发现使用框架来平滑浏览器差异非常值得,远远超出classList的可用性。"IE7不需要JavaScript"这个说法是非常值得质疑的。 - theazureshadow
    @theazureshadow,你的HTML/CSS网站在IE7中没有JavaScript看起来很好,如果你正在为IE7制作Web应用程序,那么我感到你的痛苦,真的要问问自己,为了让IE7正常工作而付出额外的300%努力是否值得。 (对我来说99%不值得) - Raynos

    3

    改进版的 Emil 代码(包括 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();
      }
    }
    

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

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