かもメモ

自分の落ちた落とし穴に何度も落ちる人のメモ帳

JavaScript classListでクラス属性を操作するゾ

Element.classList

classListは配列のように扱えるDOMTokenListオブジェクト

// HTML: <div id="main" class="c1 c2 c3"></div>
const main = document.getElementById('main');
const classList = main.classList;
console.log( classList );
// => DOMTokenList(3) ["c1", "c2", "c3", value: "c1 c2 c3"]

Element.classList.lengthでDOMに付いているクラスの数が返される

main.classList.length; // => 3

配列のようにElement.classList[0]でアクセスするとインデックス番号のクラス名が取得できる

main.classList[0]; // => "c1"
// 存在しないインデックスにアクセスすると undefined
main.classList[100]; // => undefined

Element.classList.valueはクラス属性の値が文字列で取得できる

main.classList.value; // => "c1 c2 c3"
main.classList.value === main.className; // => true

element.classNameで得られる文字列と等しい

classList.value を直接変更するとクラス名が置き換えられる
// HTML: <div id="main" class="c1 c2 c3"></div>
const main = getElementById('main');
main.classList.value = "foo bar";
console.log( main.classList );
// => DOMTokenList(2) ["foo", "bar", value: "foo bar"]
console.log( main.className );
// => "foo bar"
console.log( main )
// => <div id="main" class="foo bar"></div>

同様にclassNameに直接クラス名の値を代入して変更すると、classListの内容変更される
またclassListはオブジェクトなので = で変数に取っておいても元のオブジェクトが変更されると、変数にも変更が反映される反映される

const _classList = main.classList;
main.className = "pizza hotdog";
console.log( main.classList );
// => DOMTokenList(2) ["pizza", "hotdog", value: "pizza hotdog"]
main.classList === _classList; // => true

method

  • add( className ): クラスを追加
  • remove( className ): クラスを削除
  • toggle( className ): クラスがあれば削除・無ければ追加
  • contains( className ): クラス名の有無を true / false で返す
  • replace( oldClass, newClass ): oldClassをnewClassで置き換え

classList.add

add( String [, String [, ...]] )
クラスの追加、,区切りで複数のクラスを一度に追加することもできる
既に存在するクラスは無視される
返り値はundefinedなのでメソッドチェーンはできない

// HTML <div id="main"></div>
const main = document.getElementById('main');
main.classList.add('bold');
// => <div id="main" class="bold"></div>
main.classList.add('text-info', 'small');
// => <div id="main" class="bold text-info small"></div>
console.log( main.classList.add('foo') ); // undefined

,区切りでクラス名を追加できるので、クラス名の配列をスプレッド演算子で展開して渡して追加とかもできる

// HTML <div id="main"></div>
const main = document.getElementById('main');
const classArg = ['pizza', 'hotdog', 'gohan'];
main.classList.add(...classArg);
// => <div id="main" class="pizza hotdog gohan"></div>

classList.remove

remove( String [, String [, ...]] )
クラスの削除、,区切りで複数のクラスを一度に削除もできる
存在しないクラスを削除しようとした場合もエラーにはならない
返り値はundefinedなのでメソッドチェーンはできない

// HTML <div id="main" class="bold text-info small"></div>
const main = document.getElementById('main');
main.classList.remove('text-info');
// => <div id="main" class="bold small"></div>
main.classList.remove('bold', 'small');
// => <div id="main"></div>
main.classList.remove('text-warn');
// => エラーにはならない

classList.contains

contains( String )
クラスが存在しているかどうかをtrue / falseで返す
jQueryで言うところの$elm.hasClass()

// HTML <div id="main" class="error"></div>
const main = document.getElementById('main');
main.classList.contains('error'); // => true
main.classList.contains('success'); // => false

add, removeのように複数のクラス名を渡してもエラーにはならないが、第一引数以外は無視され第一引数での有無の判定結果が返る

main.classList.contains('error', 'foo');
// => 第一引数の error はあるので true
main.classList.contains('foo', 'error');
// => 第一引数の foo はないので false

classList.toggle

toggle( String [, force] )
クラスが存在していれば削除(remove)してfalseを返す・クラスがなければ追加(add)してtrueを返す
第二引数にtrueを渡すと常に追加(add)、falseを渡すと常に削除(remove)
jQuery$elm.toggleClass(name, switch)と同じ感じ

// HTML: <div id="main"></main>
const main = document.getElementById('main');
main.classList.toggle('is_show'); // true
// => <div id="main" class="is_show"></main>
main.classList.toggle('is_show'); // false
// => <div id="main"></main>

main.classList.toggle('required', true); // true
// => <div id="main" class="required"></main>
main.classList.toggle('bold', false); // false
// => <div id="main" class="required"></main>

第二引数はforceなので、add, removeのように複数のクラス名をtoggleさせることはできない

// HTML: <div id="main" class="foo"></main>
const main = document.getElementById('main');
main.classList.toggle('bar', 'foo');
// => <div id="main" class="foo bar"></main>
// * main.classList.toggle('bar', true); と解釈される

スペースが入っているクラス名だとエラーになる

// HTML: <div id="main" class="foo"></main>
const main = document.getElementById('main');
main.classList.toggle("foo bar");
// => Error 
// Uncaught DOMException: Failed to execute 'toggle' on 'DOMTokenList': The token provided ('foo bar') contains HTML space characters, which are not valid in tokens.

\t, \nでも同様にエラーになったが、\s, \d, =, &のようなクラス名としてアレな値が入っていてもエラーにはならなかったので、渡された値がクラス名として成立しているかどうかはチェックしていないっぽい

classList.replace

replace( oldClass, newClass )
第一引数のクラスを、第二引数のクラスに置き換える
置き換えられるoldClassがDOMに無ければ無視される
置き換えるクラス(newClass)が既に存在する場合、置き換えられるoldClassが消えるだけ

// HTML: <div id="main" class="pizza hotdog gohan"></main>
const main = document.getElementById('main');
main.classList.replace('gohan', 'beef');
// main.className => "pizza hotdog beef"
main.classList.replace('fish', 'chiken');
// main.className => "pizza hotdog beef"
main.classList.replace('hotdog', 'pizza');
// main.className => "pizza beef" 

classList.toggleと同様にスペースの入っている値を渡すとエラーになる

// HTML: <div id="main" class="pizza beef"></main>
const main = document.getElementById('main');
main.classList.replace('pizza beef', 'okome');
// => Error
// Uncaught DOMException: Failed to execute 'replace' on 'DOMTokenList': The token provided ('pizza beef') contains HTML space characters, which are not valid in tokens.

// 置き換える側も同様
main.classList.replace('pizza', 'okome misosoup');
// => Error
// Uncaught DOMException: Failed to execute 'replace' on 'DOMTokenList': The token provided ('okome misosoup') contains HTML space characters, which are not valid in tokens.

正規表現はそもそも受け付けていないのでNG

main.classList.replace(\pizza\, 'misosoup');
// => Error
// Uncaught SyntaxError: Invalid or unexpected token

Element.classList - Web API | MDN ここではclassList.replaceSafari未実装と表示表示されているけど、Can I useではグリーンになっていて、Safari 12.0.3 では問題なく動作していたので使えるっぽい

内部的にclassName.replace(oldClass, newClass)で置き換えているのかと思ったのだけれど、スペースを含むとエラーになるのでどうやら違うっぽい。内部でイテレーター回すとかしてるならclassName.replace()の方が高速な気がする...

// HTML: <div id="main" class="pizza beef"></main>
const main = document.getElementById('main');
 main.className = main.className.replace('pizza beef', 'okome misosoup');
// => <div id="main" class="okome misosoup"></main>

classList.item

item( index )
引数で渡したインデックスのクラス名を返す
存在しないインデックスを指定するとnullが返る

// HTML: <div id="main" class="pizza hotdog"></div>
const main = document.getElementById('main');
console.log( main.classList.item(1) ); // => "hotdog"
console.log( main.classList.item(-1) ); // => null

classList[n]で配列ようにアクセスするのとほぼ同じだが、clasList[n] で存在しないインデックスにアクセスした場合undefinedが返るので挙動が異なるので注意が必要

main.classList.item(100) === main.classList[100]; // => false
console.log( main.classList.item(100) ); // => null
console.log( main.classList[100] ); // => undefined

実装状況

classList (DOMTokenList)
Can I use... Support tables for HTML5, CSS3, etc

IEのサポートをしなくて良いなら問題なく使えそうです

感想

jQueryで行っていたクラス名の操作がほぼ同じ感覚でできるって印象です。(jQuery感覚でメソッドチェーンにするとエラーになりますが...
classList.replaceでのクラス名の置き換えはjQueryだと$elm.add().remove()のように追加・削除をメソッドチェーンしなければならなかったのが単一メソッドで書けるので良いです。 jQueryでの複数クラスの追加・削除はclassNameみたいにスペース区切りの1つの文字列を渡さなければならないので、classListでの操作の方が楽かもしれません。

e. g.

jQuery

$('#main').addClass('bold text-info');
$('#main').removeClass('bold text-info');

Element.className

const main = document.getElementById('main');
main.className += " bold text-info";
const classArg = main.className.split(' ').filter((val) => {
  return val !== 'bold' && val !== 'text-info';
});
main.className = classArg.join(' ');

 
ジャパニランドだとまだまだ使われてる、IEはサポートしてないメソッドも多いので、IEをサポート不要で同じDOMでその後jQueryオブジェクトを使う必然性がなければクラス属性の操作はclassListで事足りそうです。

jQueryとの比較で感想を書いているのは、Vue.jsとかReactのプロジェクトだとクラス名の操作はフレームワークで行うでしょうからclassListの出番はjQuery使ってるwebサイト制作くらいな気がしているから…


[参考]

リスト:超絶技巧練習曲

リスト:超絶技巧練習曲

  • アーティスト: ボレット(ホルヘ),リスト
  • 出版社/メーカー: ユニバーサル ミュージック クラシック
  • 発売日: 2009/05/20
  • メディア: CD
  • この商品を含むブログを見る