Element.classList

Element.classList は読み取り専用のプロパティで、生きた DOMTokenList コレクションでその要素の class 属性を返します。これを使用してクラスリストを操作することができます。

classList を使用することは、 element.className から取得した空白区切りの文字列として要素のクラスのリストにアクセスすることの便利な代替手段になります。

構文

const elementClasses = elementNodeReference.classList;

返値

DOMTokenList で、その要素の class 属性を返します。 class 属性が設定されていない場合や空の場合は、空の DOMTokenList を返します。すなわち、 DOMTokenListlength プロパティが 0 になります。

DOMTokenList 自体は読み取り専用ですが、 add()remove() を用いてオブジェクトを変更することはできます。

const div = document.createElement('div');
div.className = 'foo';

// 最初の状態: <div class="foo"></div>
console.log(div.outerHTML);

// classList API を用いてクラスを除去、追加
div.classList.remove("foo");
div.classList.add("anotherclass");

// <div class="anotherclass"></div>
console.log(div.outerHTML);

// visible が設定されていれば除去し、なければ追加
div.classList.toggle("visible");

// i が 10 未満であるかどうかの条件によって visible を追加または除去
div.classList.toggle("visible", i < 10 );

console.log(div.classList.contains("foo"));

// 複数のクラスを追加または除去
div.classList.add("foo", "bar", "baz");
div.classList.remove("foo", "bar", "baz");

// spread syntax を使用したクラスの追加または除去
const cls = ["foo", "bar"];
div.classList.add(...cls); 
div.classList.remove(...cls);

// "foo" クラスを "bar" クラスで置き換え
div.classList.replace("foo", "bar");

Firefox 26 以前のバージョンでは、 add/remove/toggle メソッドでいくつかの引数の使用方法を実装していません。詳しくは https://bugzilla.mozilla.org/show_bug.cgi?id=814014 を参照してください。

ポリフィル

古い onpropertychange イベントは、生きた classList のモックアップを作成することができますが、これは Element.prototype.className プロパティが変更されたときにこのイベントが発生するからです。

以下のポリフィルは classListDOMTokenList の両方のためのもので、 Element.prototype.classList のすべての標準メソッドおよびプロパティを完全に準拠 (カバー) することを保証します。 IE10-IE11 ブラウザーに加えて、 IE 6-9ほぼ同じ動作をします。

// 1. String.prototype.trim polyfill
if (!"".trim) String.prototype.trim = function(){ return this.replace(/^[\s]+|[\s]+$/g, ''); };
(function(window){"use strict"; // prevent global namespace pollution
if(!window.DOMException) (DOMException = function(reason){this.message = reason}).prototype = new Error;
var wsRE = /[\11\12\14\15\40]/, wsIndex = 0, checkIfValidClassListEntry = function(O, V) {
  if (V === "") throw new DOMException(
    "Failed to execute '" + O + "' on 'DOMTokenList': The token provided must not be empty." );
  if((wsIndex=V.search(wsRE))!==-1) throw new DOMException("Failed to execute '"+O+"' on 'DOMTokenList': " +
    "The token provided ('"+V[wsIndex]+"') contains HTML space characters, which are not valid in tokens.");
}
// 2. Implement the barebones DOMTokenList livelyness polyfill
if (typeof DOMTokenList !== "function") (function(window){
    var document = window.document, Object = window.Object, hasOwnProp = Object.prototype.hasOwnProperty;
    var defineProperty = Object.defineProperty, allowTokenListConstruction = 0, skipPropChange = 0;
    function DOMTokenList(){
        if (!allowTokenListConstruction) throw TypeError("Illegal constructor"); // internally let it through
    }
    DOMTokenList.prototype.toString = DOMTokenList.prototype.toLocaleString = function(){return this.value};
    DOMTokenList.prototype.add = function(){
        a: for(var v=0, argLen=arguments.length,val="",ele=this[" uCL"],proto=ele[" uCLp"]; v!==argLen; ++v) {
            val = arguments[v] + "", checkIfValidClassListEntry("add", val);
            for (var i=0, Len=proto.length, resStr=val; i !== Len; ++i)
                if (this[i] === val) continue a; else resStr += " " + this[i];
            this[Len] = val, proto.length += 1, proto.value = resStr;
        }
        skipPropChange = 1, ele.className = proto.value, skipPropChange = 0;
    };
    DOMTokenList.prototype.remove = function(){
        for (var v=0, argLen=arguments.length,val="",ele=this[" uCL"],proto=ele[" uCLp"]; v !== argLen; ++v) {
            val = arguments[v] + "", checkIfValidClassListEntry("remove", val);
            for (var i=0, Len=proto.length, resStr="", is=0; i !== Len; ++i)
                if(is){ this[i-1]=this[i] }else{ if(this[i] !== val){ resStr+=this[i]+" "; }else{ is=1; } }
            if (!is) continue;
            delete this[Len], proto.length -= 1, proto.value = resStr;
        }
        skipPropChange = 1, ele.className = proto.value, skipPropChange = 0;
    };
    window.DOMTokenList = DOMTokenList;
    function whenPropChanges(){
        var evt = window.event, prop = evt.propertyName;
        if ( !skipPropChange && (prop==="className" || (prop==="classList" && !defineProperty)) ) {
            var target = evt.srcElement, protoObjProto = target[" uCLp"], strval = "" + target[prop];
            var tokens=strval.trim().split(wsRE), resTokenList=target[prop==="classList"?" uCL":"classList"];
            var oldLen = protoObjProto.length;
            a: for(var cI = 0, cLen = protoObjProto.length = tokens.length, sub = 0; cI !== cLen; ++cI){
                for(var innerI=0; innerI!==cI; ++innerI) if(tokens[innerI]===tokens[cI]) {sub++; continue a;}
                resTokenList[cI-sub] = tokens[cI];
            }
            for (var i=cLen-sub; i < oldLen; ++i) delete resTokenList[i]; //remove trailing indexs
            if(prop !== "classList") return;
            skipPropChange = 1, target.classList = resTokenList, target.className = strval;
            skipPropChange = 0, resTokenList.length = tokens.length - sub;
        }
    }
    function polyfillClassList(ele){
        if (!ele || !("innerHTML" in ele)) throw TypeError("Illegal invocation");
        ele.detachEvent( "onpropertychange", whenPropChanges ); // prevent duplicate handler infinite loop
        allowTokenListConstruction = 1;
        try{ function protoObj(){} protoObj.prototype = new DOMTokenList(); }
        finally { allowTokenListConstruction = 0 }
        var protoObjProto = protoObj.prototype, resTokenList = new protoObj();
        a: for(var toks=ele.className.trim().split(wsRE), cI=0, cLen=toks.length, sub=0; cI !== cLen; ++cI){
            for (var innerI=0; innerI !== cI; ++innerI) if (toks[innerI] === toks[cI]) { sub++; continue a; }
            this[cI-sub] = toks[cI];
        }
        protoObjProto.length = cLen-sub, protoObjProto.value = ele.className, protoObjProto[" uCL"] = ele;
        if (defineProperty) { defineProperty(ele, "classList", { // IE8 & IE9 allow defineProperty on the DOM
            enumerable:   1, get: function(){return resTokenList},
            configurable: 0, set: function(newVal){
                skipPropChange = 1, ele.className = protoObjProto.value = (newVal += ""), skipPropChange = 0;
                var toks = newVal.trim().split(wsRE), oldLen = protoObjProto.length;
                a: for(var cI = 0, cLen = protoObjProto.length = toks.length, sub = 0; cI !== cLen; ++cI){
                    for(var innerI=0; innerI!==cI; ++innerI) if(toks[innerI]===toks[cI]) {sub++; continue a;}
                    resTokenList[cI-sub] = toks[cI];
                }
                for (var i=cLen-sub; i < oldLen; ++i) delete resTokenList[i]; //remove trailing indexs
            }
        }); defineProperty(ele, " uCLp", { // for accessing the hidden prototype
            enumerable: 0, configurable: 0, writeable: 0, value: protoObj.prototype
        }); defineProperty(protoObjProto, " uCL", {
            enumerable: 0, configurable: 0, writeable: 0, value: ele
        }); } else { ele.classList=resTokenList, ele[" uCL"]=resTokenList, ele[" uCLp"]=protoObj.prototype; }
        ele.attachEvent( "onpropertychange", whenPropChanges );
    }
    try { // Much faster & cleaner version for IE8 & IE9:
        // Should work in IE8 because Element.prototype instanceof Node is true according to the specs
        window.Object.defineProperty(window.Element.prototype, "classList", {
            enumerable: 1,   get: function(val){
                                 if (!hasOwnProp.call(this, "classList")) polyfillClassList(this);
                                 return this.classList;
                             },
            configurable: 0, set: function(val){this.className = val}
        });
    } catch(e) { // Less performant fallback for older browsers (IE 6-8):
        window[" uCL"] = polyfillClassList;
        // the below code ensures polyfillClassList is applied to all current and future elements in the doc.
        document.documentElement.firstChild.appendChild(document.createElement('style')).styleSheet.cssText=(
            '_*{x-uCLp:expression(!this.hasOwnProperty("classList")&&window[" uCL"](this))}' + //  IE6
            '[class]{x-uCLp/**/:expression(!this.hasOwnProperty("classList")&&window[" uCL"](this))}' //IE7-8
        );
    }
})(window);
// 3. Patch in unsupported methods in DOMTokenList
(function(DOMTokenListProto, testClass){
    if (!DOMTokenListProto.item) DOMTokenListProto.item = function(i){
        function NullCheck(n) {return n===void 0 ? null : n} return NullCheck(this[i]);
    };
    if (!DOMTokenListProto.toggle || testClass.toggle("a",0)!==false) DOMTokenListProto.toggle=function(val){
        if (arguments.length > 1) return (this[arguments[1] ? "add" : "remove"](val), !!arguments[1]);
        var oldValue = this.value;
        return (this.remove(oldValue), oldValue === this.value && (this.add(val), true) /*|| false*/);
    };
    if (!DOMTokenListProto.replace || typeof testClass.replace("a", "b") !== "boolean")
        DOMTokenListProto.replace = function(oldToken, newToken){
            checkIfValidClassListEntry("replace", oldToken), checkIfValidClassListEntry("replace", newToken);
            var oldValue = this.value;
            return (this.remove(oldToken), this.value !== oldValue && (this.add(newToken), true));
        };
    if (!DOMTokenListProto.contains) DOMTokenListProto.contains = function(value){
        for (var i=0,Len=this.length; i !== Len; ++i) if (this[i] === value) return true;
        return false;
    };
    if (!DOMTokenListProto.forEach) DOMTokenListProto.forEach = function(f){
        if (arguments.length === 1) for (var i = 0, Len = this.length; i !== Len; ++i) f( this[i], i, this);
        else for (var i=0,Len=this.length,tArg=arguments[1]; i !== Len; ++i) f.call(tArg, this[i], i, this);
    };
    if (!DOMTokenListProto.entries) DOMTokenListProto.entries = function(){
        var nextIndex = 0, that = this;
        return {next: function() {
            return nextIndex<that.length ? {value: [nextIndex, that[nextIndex++]], done: false} : {done: true};
        }};
    };
    if (!DOMTokenListProto.values) DOMTokenListProto.values = function(){
        var nextIndex = 0, that = this;
        return {next: function() {
            return nextIndex<that.length ? {value: that[nextIndex++], done: false} : {done: true};
        }};
    };
    if (!DOMTokenListProto.keys) DOMTokenListProto.keys = function(){
        var nextIndex = 0, that = this;
        return {next: function() {
            return nextIndex<that.length ? {value: nextIndex++, done: false} : {done: true};
        }};
    };
})(window.DOMTokenList.prototype, window.document.createElement("div").classList);
})(window);

注意事項

上記のポリフィルは機能が限定されています。現在、 IE 6-7 では、文書要素の外にある要素 (たとえば、 document.createElement によって作成された後、親ノードに追加される前の要素) を代替することができません。

しかし、 IE9 では正常に動作するはずです。代替バージョンの classList と W3 の仕様書との主な違いは、 IE 6-8 においては不変オブジェクト (プロパティを直接変更できないオブジェクト) を生成する方法がないという点です。しかし IE9 では、プロトタイプを拡張し、可視オブジェクトを凍結し、ネイティブのプロパティメソッドを上書きすることで可能です。しかし、このような操作は IE6-IE8 では機能せず、 IE9 ではウェブページ全体のパフォーマンスを大幅に低下させるため、これらの修正はこのポリフィルでは完全に実行できません。

IE6-7 の軽い注意事項ですが、このポリフィルは window オブジェクトの window[" uCL"] プロパティを使用して CSS の式とコミュニケーションを行い、 CSS の x-uCLp プロパティをすべての要素に付与し、 element[" uCL"] プロパティによってガーベジコレクションを行うことができるようにして性能を改善しています。すべてのポリフィルを使用したブラウザー (IE6-9) では、追加の element[" uCLp"] プロパティによって要素に標準に準拠したプロトタイピングを保証し、それぞれの element["classList"] オブジェクトに DOMTokenList[" uCL"] プロパティを追加することで、 DOMTokenList が自分自身の要素の範囲に収まることを保証します。

仕様書

仕様書 状態 備考
DOM
Element.classList の定義
現行の標準 初回定義
DOM4
Element.classList の定義
廃止された

ブラウザーの互換性

Update compatibility data on GitHub
デスクトップモバイル
ChromeEdgeFirefoxInternet ExplorerOperaSafariAndroid webviewAndroid 版 ChromeAndroid 版 FirefoxAndroid 版 OperaiOSのSafariSamsung Internet
classListChrome 完全対応 8Edge 完全対応 16
完全対応 16
未対応 12 — 16
補足
補足 Not supported for SVG elements.
Firefox 完全対応 3.6IE 部分対応 10
補足
部分対応 10
補足
補足 Not supported for SVG elements.
Opera 完全対応 11.5Safari 完全対応 6WebView Android 完全対応 ≤37Chrome Android 完全対応 18Firefox Android 完全対応 4Opera Android 完全対応 11.5Safari iOS 完全対応 5Samsung Internet Android 完全対応 1.0
Multiple arguments for add() & remove()Chrome 完全対応 24Edge 完全対応 12Firefox 完全対応 26IE 未対応 なしOpera 完全対応 15Safari 完全対応 7WebView Android 完全対応 ≤37Chrome Android 完全対応 25Firefox Android 完全対応 26Opera Android 完全対応 14Safari iOS 完全対応 7Samsung Internet Android 完全対応 1.5
replace()Chrome 完全対応 61Edge 完全対応 17Firefox 完全対応 49IE 未対応 なしOpera ? Safari 未対応 なしWebView Android 完全対応 61Chrome Android 完全対応 61Firefox Android 完全対応 49Opera Android ? Safari iOS 未対応 なしSamsung Internet Android 完全対応 8.0
toggle() method's second argumentChrome 完全対応 24Edge 完全対応 12Firefox 完全対応 24IE 未対応 なしOpera 完全対応 15Safari 完全対応 7WebView Android 完全対応 ありChrome Android 完全対応 ありFirefox Android 完全対応 24Opera Android ? Safari iOS 完全対応 7Samsung Internet Android 完全対応 あり

凡例

完全対応  
完全対応
部分対応  
部分対応
未対応  
未対応
実装状況不明  
実装状況不明
実装ノートを参照してください。
実装ノートを参照してください。

関連情報