ネイティブの JavaScript(Vanilla JS) を使う

以下は JavaScript(ECMAScript 5/ES5 までの機能や ES6 の一部)を使った基本的な要素の操作方法(使い方)などについての覚書です。

※ Vanilla JS とはライブラリもフレームワークも使わないただの(純粋な) JavaScript のこと、つまりネイティブの JavaScript のことです。

addEventListener のリスナー関数に引数を渡す方法イベントの移譲、JavaScript の実行のタイミング(DOMContentLoaded や load イベント)、insertAdjacentHTML/Text/Element などを追加しました(2021年7月)。

ブラウザの対応状況

参考サイト:

関連ページ:

作成日:2020年4月10日

要素の取得

要素を操作するには、まず要素を取得する必要があります。

以下のような HTML がある場合、クラスが bar の要素を取得するにはいくつかの方法があります。

<ul id="foo">
  <li class="bar">Blue</li>
  <li>Red</li>
  <li class="boo">Green</li>
</ul>

以下はドキュメント(Document)や要素(Element)のメソッドを使って bar クラスの要素を取得する例です。

// まずは id が foo の要素(オブジェクト)を取得
var elem = document.getElementById('foo');
//取得した elem を起点にクラスが bar の要素の集合を取得し、その最初の要素 [0]
var bar = elem.getElementsByClassName('bar')[0];
alert(bar.textContent); //Blue

//上記は以下のように続けて記述することができます。
var bar = document.getElementById('foo').getElementsByClassName('bar')[0];
alert(bar.textContent);  //Blue

//または、以下のように querySelector() でセレクタを指定して取得することもできます。
var bar = document.querySelector('#foo .bar');
alert(bar.textContent); //Blue

メソッド

ドキュメント(ブラウザーに読み込まれたウェブページ)の要素を取得するには Document オブジェクトや Element オブジェクトのメソッドが使えます。

※ querySelector() 及び querySelectorAll() は他のメソッドに比べて処理速度が遅いです。

メソッド 説明
getElementById() id 属性を指定して要素を取得
getElementsByTagName() タグ名を指定して要素の集まり(HTMLCollection)を取得
getElementsByName() name 属性を指定して要素の集まり(NodeList)を取得
getElementsByClassName() class 属性を指定して要素の集まり(HTMLCollection)を取得
querySelector() セレクタで指定された要素を1つだけ(最初の要素を)取得
querySelectorAll() セレクタで指定された要素の集まり(NodeList)を取得
getElementById

ドキュメント(文書)から指定された ID に一致する要素(Element オブジェク)を取得します。

要素の ID は固有なので、特定の要素にすばやくアクセスすることができます。

var element = document.getElementById(id);

パラメータ
id : 要素の ID(大文字と小文字の区別がある文字列)
戻り値
指定された ID に一致する DOM 要素オブジェクト(Element オブジェクト)。一致する要素がなければ null
// id が foo の要素の文字色を赤に
var elem = document.getElementById('foo');
elem.style.color = 'red';

MDN: getElementById()

getElementsByTagName

指定されたタグ名を持つ要素の集まり(HTMLCollection)を取得します。

但し、WebKit ブラウザーでは NodeList を取得します。

var elements = document.getElementsByTagName(tagName);

var elements = element.getElementsByTagName(tagName)

パラメータ
tagName: タグ名(文字列)。 "*" は全ての要素を表します。
戻り値
見つかった要素の集まり(HTMLCollection、 WebKit ブラウザーでは NodeList)。
//ドキュメント内の p 要素の数をコンソールに出力
var elems = document.getElementsByTagName('p');
//elems(HTMLCollection/NodeList)は配列のようなものなので、length で要素数を取得できます。
console.log(elems.length);

以下は id が foo の要素を取得して、その要素を起点として(Element オブジェクトのメソッド) getElementsByTagName() で li 要素を取得しています。

for 文でそれぞれの要素のテキストの最後に連番を追加しています。item() は引数に指定するインデックスの位置にあるノードを取得します。

また添字 [1] を使って2番目の要素の文字色を赤に指定しています。

<ul id="foo">
  <li>sample</li> <!-- sample 1 に -->
  <li>sample</li> <!-- sample 2 に(赤色) -->
  <li>sample</li> <!-- sample 3 に -->
</ul>

<script>
var elems = document.getElementById("foo").getElementsByTagName("li");
//要素数は length プロパティで取得できます
for(var i = 0; i < elems.length; i++) {
  //各要素のテキストを取得
  var text = elems.item(i).textContent;
  //テキストに番号を追加(例 sample → sample 1 )
  elems.item(i).textContent = text + ' ' + (i + 1);
}
//2番めの要素の色を赤に
elems[1].style.color = 'red';
</script>    

MDN : document.getElementsByTagName()

MDN : element.getElementsByTagName()

getElementsByName

指定した name 属性を持つ要素の集まり(NodeList)を取得します。

var elements = document.getElementsByName(name);

パラメータ
name:要素の name 属性の値
戻り値
指定した name 属性を持つ要素の集まり(NodeList)。 IE10 以前では指定された id 属性を持つ要素も返します。また、IE および Edge では、 NodeList ではなく HTMLCollection を返します。
//name 属性が submit の要素の NodeList を取得
var elems = document.getElementsByName("submit");
//1番めの要素の色を赤に
elems[0].style.color = 'red';
//以下でも同じ
elems.item(0).style.color = 'red';

MDN : document.getElementsByName()

getElementsByClassName

指定されたクラス名を持つすべての要素の集まり(HTMLCollection)を取得します。

document オブジェクトに対して呼び出したときは、ルートノードを含む文書全体が検索され、任意の要素に対して呼び出した場合は、指定されたルート要素下が検索されます。

var elements = document.getElementsByClassName(names);

var elements = element.getElementsByClassName(names);

パラメータ
names:クラス名。複数のクラス名を指定するにはスペースで区切ります。
戻り値
指定した class 属性(クラス名)を持つ要素の集まり(HTMLCollection
//クラス名が bar の要素を取得
var elems = document.getElementsByClassName('bar');
//取得した最初の要素の色を赤に
elems[0].style.color = 'red';

//foo クラスと bar クラス(の両方)を持っている全ての要素を取得
var foo_bars = document.getElementsByClassName('foo bar');

MDN : document.getElementsByClassName()

MDN : element.getElementsByClassName()

querySelector

指定された CSS セレクターに一致する文書内(document)の最初の要素(HTMLElement オブジェク)を取得します。一致するものが見つからない場合は null を返します。

document の代わりに対象の要素(baseElement)のメソッドとして呼び出した場合は、対象要素の子孫の中のマッチする最初の要素を取得します。

var element = document.querySelector(selectors);

var element = baseElement.querySelector(selectors);

パラメータ
selectors:1つまたは複数のセレクターを含む文字列(CSS セレクター)
戻り値
指定された CSS セレクターに最初に一致する要素(HTMLElement オブジェクト)。一致する要素がない場合は null を返します。
//container クラスの div 要素の中の最後の p 要素を赤色に
var elem = document.querySelector('div.container p:last-child');
elem.style.color = 'red'; 

MDN : document.querySelector()

MDN : element.querySelector()

querySelectorAll

指定された CSS セレクターに一致する文書中の要素の集まり(静的な NodeList)を取得します。

document の代わりに対象の要素(baseElement)のメソッドとして呼び出した場合は、対象要素の子孫の内のマッチする要素の集まり(静的な NodeList)を取得します。

var elementList = document.querySelectorAll(selectors);

var elementList = baseElement.querySelectorAll(selectors);

パラメータ
selectors:1つまたは複数のセレクターを含む文字列(CSS セレクター)。複数のセレクターを指定する場合はカンマで区切ります。
戻り値
指定されたセレクターに一致する要素の集まり(静的な NodeList)。一致するものがなければ空の NodeList
//セレクタ(.content h2)にマッチする h2 要素の最初の要素の文字色を赤に
var matches = document.querySelectorAll(".content h2");
matches[0].style.color = 'red';

以下はマッチする li 要素のテキストを調べて、テキストが「Red」の場合は赤色に、「Blue」の場合は青色にする例です。

<ul id="foo">
  <li>Blue</li> <!-- 青色に -->
  <li>Red</li> <!-- 赤色に -->
  <li>Green</li>
</ul>

<script>
var matches = document.querySelectorAll("#foo li");
for(var i = 0; i < matches.length; i++) {
  var text = matches.item(i).textContent
  if( text === 'Red') {
    matches.item(i).style.color = 'red';
  }else if(text === 'Blue') {
    matches.item(i).style.color = 'blue';
  }
}
</script>

MDN: document.querySelectorAll()

MDN: element.querySelectorAll

id 属性

JavaScript では getElementById を使わなくても id 属性を付与した要素をその id 属性の値と同じ変数名で参照することができてしまいます。

但し、この方法での要素へのアクセスは推奨されておらず、この方法に依存したコードには色々と問題があるので使わないほうが良いです(以下は参考まで)。

例えば、以下のような id 属性の値が foo の要素に、JavaScript で変数 foo でアクセスできてしまいます。

<p id="foo">Sample Text</p>
<script>
//id="foo" の要素の文字色を赤に
foo.style.color="red";
</script>

これは、ブラウザは id 属性を持つすべての要素への参照を、id 属性の値を変数名としてグローバル名前空間(window オブジェクト)に追加するためです。

window のプロパティやメソッドを記述する場合、window が省略できるため id 属性から直接要素を参照できるようになっています。前述のコードは以下と同じです。

<p id="foo">Sample Text</p>
<script>
//window を省略しない場合
window.foo.style.color="red";
</script>

もし、例えば以下のように id 属性と同じ値の名前の関数を定義すると、以下のコードの foo は関数を返し、 id 属性が foo の要素にはアクセスできず「Uncaught TypeError: Cannot set property 'color' of undefined」のようなエラーになってしまいます。

<p id="foo">Sample Text</p>
<script>
  function foo() {
    alert('foo');
  }
  foo.style.color="red"; //エラー
</script>

以下も同様にエラーになってしまいます。

<p id="foo">Sample Text</p>
<script>
  //同名の変数を宣言
  const foo = 'FOO!';
  foo.style.color="red"; //エラー
</script>

このように簡単にグローバル名前空間で競合する可能性があるため、 id 属性で直接要素を参照することはせず、document.getElementById(または同様のもの)を使用する必要があります。

name 属性を指定した form 要素

似たようなものに name 属性を指定した form 要素には name 属性の値を使ってアクセスできますが、こちらは問題ありません。

<form action="contact.php" method="post" name="form1"></form>

<script>
  console.log(form1.method);  //post と出力
  console.log(window.form1.action);  // http://xxxx/contact.php と出力
</script> 

プロパティ

html や body、form 要素などは Document のプロパティとして定義されています。

Document インターフェイスのプロパティ(一部抜粋)
プロパティ 説明
body 現在の文書の <body>ノード。document.body
documentElement 文書の直接の子である要素(Element)。 HTML 文書では <html> 要素を表す HTMLElement オブジェクト。document.documentElement
forms 現在の文書の <form> 要素のリスト(HTMLCollection)。document.forms
head 現在の文書の <head> 要素。document.head
images 現在の文書の画像のリスト。document.images
links 文書内のすべてのハイパーリンクのリスト。document.links
scripts 文書内のすべての <script> 要素。document.scripts
selectedStyleSheetSet 現在使用されているスタイルシートセットの名前。document.selectedStyleSheetSet
styleSheetSets 文書で使用できるスタイルシートセットのリスト。document.styleSheetSets
form 要素

forms プロパティは読み取り専用で、文書内に含まれる全ての <form> 要素の集合(HTMLCollection)を返します。

また、 フォーム要素の elements プロパティを使用すると、form 要素に含まれるフォームコントロール(部品)にアクセスすることができます。

<form id="first_form" name="foo">
  <input type="text" value="">
</form>

<form id="second_form" name="bar">
  <input name="email" type="email" placeholder="info@example.com">
  <input name="password" type="password">
  <button type="submit">Submit</button>
</form>

<script>
//インデックスを指定してフォーム要素を取得して、その id の値を表示
console.log(document.forms[0].id);  //first_form
console.log(document.forms[1].id);  //second_form

//name 属性でフォーム要素を取得(アクセス)することもできます。
console.log(document.forms.foo.id);  //first_form
console.log(document.forms['foo'].id); //first_form

//name 属性が bar のフォーム要素を取得
var bar_form = document.forms.bar;

//フォーム要素の elements プロパティで name 属性が email の要素の placeholder 属性を取得
console.log(bar_form.elements.email.placeholder);  //info@example.com

</script>

関連ページ:

HTMLCollection

HTMLCollection は HTML 要素(element オブジェクト)の集まりから成る配列のようなオブジェクトで、各要素にはインデックス番号でアクセスできます。

配列のようなオブジェクトとは length プロパティとインデックス付けされた要素を持つオブジェクトのことです。(参考ページ:Array-likeオブジェクト

また、HTMLCollection は元になった document が変更された時点で自動的に更新される(DOM に対する変更がリアルタイムで反映される)ライブオブジェクトです。

HTMLCollection は以下のプロパティとメソッドを提供します。

HTMLCollection のプロパティとメソッド
プロパティ 説明
length HTMLCollection に含まれる要素数(アイテム数)
メソッド 説明
item() 指定された index (先頭はゼロ) 位置にある特定の要素を返します。index が範囲外なら null を返します。添字 [ i ] を使っても要素にアクセスできます。
namedItem() 指定した文字列(id または name 属性)が一致する要素を返します。指定した要素がない場合は null を返します。

MDN: HTMLCollection

例えば、getElementsByClassName() の戻り値は HTMLCollection です。

<p class="foo">Foo 1</p>  <!-- 赤色に -->
<p class="foo">Foo 2</p>  <!-- 青色に -->
<p class="foo">Foo 3</p>
</body>
<script>
//クラス foo の要素(HTMLCollection)を取得
var elems = document.getElementsByClassName('foo');
//取得した要素(HTMLCollection)の数をコンソールに出力
console.log('length: ' + elems.length);  //length:3
//インデックス(添字)を使って1番目の要素にアクセス
elems[0].style.color = 'red';
//item() メソッドを使って2番めの要素にアクセス
elems.item(1).style.color = 'blue';
</script> 

HTMLCollection の各要素について処理を順次適用するには、以下のように for 文を使うことができます。

<div class="bar">Div</div>
<h3 class="bar">Heading</h3>
<p class="bar">Paragraph</p>

<script>
//クラス bar の要素(HTMLCollection)を取得
var collection = document.getElementsByClassName('bar');

//length プロパティで要素数を取得して各要素について処理
for (var i = 0; i < collection.length; ++i) {
  var item = collection[i];  //または collection.item(i)
  console.log(item);
}
</script>

<!-- 以下が出力結果
<div class="bar">Div</div>
<h3 class="bar">Heading</h3>
<p class="bar">Paragraph</p>
-->

但し、for...in や for each...in は使えません。

HTMLCollection の各要素に加えて他のプロパティやメソッドについても処理が適用されるため、要素のみ処理すべきスクリプトではエラーが発生します。

また、for..in で取得されるプロパティの順番は保証されていません。

<div class="bar">Div</div>
<h3 class="bar">Heading</h3>
<p class="bar">Paragraph</p>

<script>
//クラス bar の要素(HTMLCollection)を取得
var collection = document.getElementsByClassName('bar');

//for..in で各要素について処理(要素以外のメソッドやプロパティも処理が適用される)
for(var key in collection) {
  var node = collection.item(key);
  console.log( key + "番目: nodeType: " + node.nodeType + " nodeName: " + node.nodeName );
}
</script>

<!-- 以下が出力結果
0番目: nodeType: 1 nodeName: DIV
1番目: nodeType: 1 nodeName: H3
2番目: nodeType: 1 nodeName: P
length番目: nodeType: 1 nodeName: DIV   (★ length プロパティ)
item番目: nodeType: 1 nodeName: DIV     (★ item メソッド)
namedItem番目: nodeType: 1 nodeName: DIV(★ namedItem メソッド)
-->

ES6(ECMAScript 2015)以降であれば(IE を対象外にすれば)、for of 文を使って簡潔に記述することもできます。

<div class="bar">Div</div>
<h3 class="bar">Heading</h3>
<p class="bar">Paragraph</p>

<script>
//クラス bar の要素(HTMLCollection)を取得
var collection = document.getElementsByClassName('bar');

//for of 文を使って各要素について処理
for (var elem of collection) {
  console.log( "nodeType: " + elem.nodeType + " nodeName: " + elem.nodeName );
}
</script>

<!-- 以下が出力結果
nodeType: 1 nodeName: DIV
nodeType: 1 nodeName: H3
nodeType: 1 nodeName: P
-->

NodeList

NodeList は配列のような DOM 要素(ノード)の集合を表すオブジェクトです。Node.childNodes などのプロパティや document.querySelectorAll() メソッドの戻り値は NodeList オブジェクトです。

配列のようなオブジェクトとは length プロパティとインデックス付けされた要素を持つオブジェクトのことです。

但し、Node.childNodes のプロパティは元になった document が変更された時点で自動的に更新される(DOM に対する変更がリアルタイムで反映される)ライブオブジェクトですが、querySelectorAll() は静的な(DOM 内の変更が内容に影響を与えない)NodeList オブジェクトを返します。

NodeList は以下のようなプロパティとメソッド(以下は一部)を提供します。

NodeList のプロパティとメソッド(一部抜粋)
プロパティ 説明
length NodeList に含まれる要素数(アイテム数)
メソッド 説明
item() 指定された index (先頭はゼロ) 位置にある特定の要素を返します。index が範囲外なら null を返します。nodeList[i] のアクセスの代替手段です 。
forEach() 指定された関数を NodeList の各要素に対して実行します。関数の引数には以下の三つの引数が与えられます。
  • elem:現在処理中の要素
  • index:現在処理中の要素のインデックス
  • listObj:forEach() メソッドを実行している NodeList

※ IE 未対応(ポリフィル

MDN: NodeList

例えば、querySelectorAll() の戻り値は NodeList です。

<p class="foo">Foo 1</p>  <!-- 赤色に -->
<p class="foo">Foo 2</p>  <!-- 青色に -->
<p class="foo">Foo 3</p>
</body>
<script>
//クラス foo の要素(NodeList)を取得
var nodeList = document.querySelectorAll('.foo');
//取得した要素(NodeList)の数をコンソールに出力
console.log('length: ' + nodeList.length);  //length:3
//インデックス(添字)を使って1番目の要素にアクセス
nodeList[0].style.color = 'red';
//item() メソッドを使って2番めの要素にアクセス
nodeList.item(1).style.color = 'blue';
</script> 

NodeList の各要素について処理を順次適用するには HTMLCollection 同様、以下のように for 文を使うことができます。

<div class="bar">Div</div>
<h3 class="bar">Heading</h3>
<p class="bar">Paragraph</p>

<script>
//クラス bar の要素(NodeList)を取得
var nodeList = document.querySelectorAll('.bar');

//length プロパティで要素数を取得して各要素について処理
for (var i = 0; i < nodeList.length; ++i) {
  var node = nodeList[i];  //または nodeList.item(i)
  console.log(node);
}
</script>

<!-- 以下が出力結果
<div class="bar">Div</div>
<h3 class="bar">Heading</h3>
<p class="bar">Paragraph</p>
-->

但し、for...in や for each...in は使えません。

また 、反復処理を forEach や ES6 (ES2015) 以降であれば(IE を対象外にすれば)for of 文を使って簡潔に記述することもできます。

以下は for of 文(ES6)を使って前述と同じことをしています。HTMLCollection でも可能です。

for of 文
//クラス bar の要素(NodeList)を取得
var nodeList = document.querySelectorAll('.bar');
// for of 文
for (var elem of nodeList) {
  console.log(elem);  //前述と同じ出力結果
}

以下は NodeList のメソッド forEach を使って取得した要素のインデックスとノード名(nodeName)を出力する例です。

//クラス bar の要素(NodeList)を取得
var nodeList = document.querySelectorAll('.bar');
// forEach メソッドで引数に要素とインデックス番号を受け取る場合
nodeList.forEach(function(elem, index) {
  console.log(index + ': ' + elem.nodeName);
});

//出力結果
0: DIV
1: H3
2: P

ノードウォーキング

ある要素を基点にその子要素、親要素、兄弟要素などの相対的な位置関係から取得することもできます。

以下のような HTML がある場合

<body>
<div id="foo">
  <h3>Foo</h3>
  <p id="first">first</p>
  <p id="second">second</p>
  <div id="bar">
    <h4>Bar</h4>
    <p id="third">third</p>
    <p id="fourth">fourth</p>
  </div><!-- end of #bar -->
</div><!-- end of #foo -->
</body>

以下は親要素や親ノードを取得する例です。.id は要素の id 属性を返す Element のプロパティです。

parentNode や parentElement プロパティを使って親の要素を取得することができます。

closest() メソッドは引数で指定した親ノードを取得できますが、IE には対応していません。

//#first の親ノードを取得
var parent_first = document.getElementById('first').parentNode;
console.log(parent_first.id);  //foo

//#third の親要素を取得
var parent_third = document.getElementById('third').parentElement;
console.log(parent_third.id);  //bar

//#third の引数で指定した親ノードを取得(IE 未対応)
var closest =  document.getElementById('third').closest('#foo');
console.log(closest.id);   //foo

//parentNode と parentElement の違い
var html = document.body.parentNode;
//または var html = document.body.parentElement; でも同じ
console.log(html.parentNode); // #document
console.log(html.parentElement); // null

parentNode と parentElement の違いは「ノードを返すか要素を返すか」で、html でこのプロパティを使うと、parentNode の場合は document が返りますが、parentElement の場合は(document は要素ではないので)null が返ります。

以下は子要素や子ノードを取得する例です。

相対的な位置関係でノードを取得する場合に注意する必要があるのは、空白や改行もテキストノードとして扱われる点です。HTML を記述する際は、通常可読性を考えてタグごとに改行を入れますが、その場合改行の箇所にテキストノードが存在することになります。

この例の場合、<div id="foo"> の後に改行が入っているので、firstChild で取得されるのは改行のテキストノードになります。<div id="foo"> と <h3>Foo</h3> の間に改行がなければ結果は異なります。

<body>
<div id="foo">
  <h3>Foo</h3>
  <p id="first">first</p>
  <p id="second">second</p>
  <div id="bar">
    <h4>Bar</h4>
    <p id="third">third</p>
    <p id="fourth">fourth</p>
  </div><!-- end of #bar -->
</div><!-- end of #foo -->
</body>
//最初の子ノードを取得
var foo_fc = document.getElementById('foo').firstChild;
console.log(foo_fc); // #text(改行のテキストノード)
console.log(foo_fc.nodeType); //3(TEXT_NODE)

//最初の子要素を取得
var foo_fec = document.getElementById('foo').firstElementChild;
console.log(foo_fec);  //<h3>Foo</h3>
console.log(foo_fec.nodeType);  //1(ELEMENT_NODE)

var foo = document.getElementById('foo');
//全ての子ノード要素の最初のノード
console.log(foo.childNodes[0]); // #text(改行のテキストノード)
//全ての子要素の最初の要素
console.log(foo.children[0]); //<h3>Foo</h3>

以下は兄弟要素(ノード)などを取得する例です。ノードを取得する場合は空白、改行、コメントもテキストノードとして扱わます。

また、要素(ノード)には meta タグや script タグも含まれます。

<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Samples</title>
</head>
<body>
<div id="foo">
  <h3>Foo</h3>
  <p id="first">first</p>
  <p id="second">second</p>
  <div id="bar">
    <h4>Bar</h4>
    <p id="third">third</p>
    <p id="fourth">fourth</p>
  </div><!-- end of #bar -->
</div><!-- end of #foo -->
</body>
<script>
・・・
</script>
</html>
// #second の兄弟ノードと要素の取得
var elem = document.getElementById('second');
console.log(elem.nextSibling);  //#text(改行)
console.log(elem.nextElementSibling); //<div id="bar">・・・</div>

console.log(elem.previousSibling);    //#text(空白)
console.log(elem.previousElementSibling);  //<p id="first">first</p>

// #foo の兄弟ノードと要素の取得
var foo = document.getElementById('foo');
console.log(foo.nextSibling);  //<!-- end of #bar -->(コメント)
console.log(foo.nextSibling.nodeType);   // 8(COMMENT_NODE)
console.log(foo.nextElementSibling);  //<script>・・・</script>

// head タグの最初の子要素を取得する例
var head_fec = document.getElementsByTagName('head')[0].firstElementChild;
console.log(head_fec); //<meta charset="utf-8">

以下は相対的な位置関係を使ってノードや要素を取得する場合に利用できるプロパティです。

Node プロパティ

ドキュメントを構成する要素や属性、テキストなどのオブジェクトをノード(Node)と呼び、ノードには、要素ノード、テキストノード、属性ノードなどがあります。

ノードには以下のようなプロパティが提供されています。

Node インターフェイスのプロパティ(MDN : Node
プロパティ
(参照)
取得するノードや要素
parentNode このノードの親ノード(存在しない場合は null)
parentElement このノードの親要素 Element(存在しない場合は null)
childNodes このノードの全ての子孫を含む NodeList(ライブオブジェクト)
firstChild ノードの直下の最初の子ノード(存在しない場合は null)
lastChild ノードの直下の最後の最後の子ノード(存在しない場合は null)
previousSibling ツリー構造で1つ前の兄弟ノード(存在しない場合は null)
nextSibling ツリー構造で次の兄弟ノード(存在しない場合は null)
プロパティ
(その他)
説明
nodeName Node の名前(名前の構造はノードの型によって異なります)
nodeValue ノードの値を取得または設定
textContent 要素のテキストコンテンツを取得または設定
nodeType ノードの型を表す値(数値)
  • ELEMENT_NODE : 1
  • ATTRIBUTE_NODE : 2(非推奨)
  • TEXT_NODE : 3
  • PROCESSING_INSTRUCTION_NODE : 7
  • COMMENT_NODE : 8
  • DOCUMENT_NODE : 9
  • DOCUMENT_TYPE_NODE : 10
  • DOCUMENT_FRAGMENT_NODE : 11

また、空白ノードやコメントノードを除外して要素だけを取得するための以下のようなプロパティが提供されています。

ParentNode のプロパティ(MDN : ParentNode
プロパティ 説明
childElementCount 子要素の数を返します。
children 空白ノードやコメントノードを除外した全ての子要素(Element)を含むリスト HTMLCollection(ライブオブジェクト)を返します。
firstElementChild 最初の子要素(Element)を返します。存在しない場合は null
lastElementChild 最後の子要素(Element)を返します。存在しない場合は null

以下の兄弟要素への参照のプロパティも使えます。

NonDocumentTypeChildNode(Element プロパティ)
プロパティ 説明
previousElementSibling 1つ前の兄弟要素を返します。存在しない場合は null
nextElementSibling 次の兄弟要素を返します。存在しない場合は null

Element の closest() メソッドも使うことができます。※但し、IE には対応していません。

Element メソッド(MDN:Element
メソッド 説明
closest() 引数で指定されたセレクターに一致する現在の要素の最も近い祖先要素 (または現在の要素自身) を返します。存在しない場合は null

要素の操作

要素を操作する場合、Element や HTMLElement などのプロパティやメソッドを使うことができます。

Element のプロパティ・メソッド

Element は Document の中にある全ての要素オブジェクトが継承するクラス(インターフェイス)です。

Element プロパティ 抜粋(MDN:Element
プロパティ 説明
attributes 要素に関連したすべての属性のリスト NamedNodeMap(ライブオブジェクト)
classList class 属性のリスト(ライブオブジェクト)を返します。読み取り専用ですがそのリスト(DOMTokenList)のメソッドの add() や remove() で変更できます。
className 要素のクラス
clientHeight 要素の内部の高さ(パディングは含むが、ボーダー、マージン、 垂直スクロールバーは含まない)
clientWidth 要素の内部の幅(パディングは含むが、ボーダー、マージン、 垂直スクロールバーは含まない)
id 要素の id
innerHTML 要素内容のマークアップ
outerHTML その要素を内容に含むマークアップ
scrollTop 文書の上端が垂直方向にスクロールされた量(要素の内容が垂直にスクロールするピクセル数を取得または設定)
tagName 要素のタグ名。タグ名は大文字で表現されます。div 要素なら DIV、p 要素なら P になります。
Element メソッド 抜粋(MDN:Element
メソッド 説明
addEventListener() 要素にイベントハンドラーを登録。書式は target.addEventListener(type, listener[, options]) 。対象(target)は Element, Document, Window, XMLHttpRequest など。type はイベントの種類。listener は関数など。
dispatchEvent() 特定の EventTarget に Event をディスパッチ(送る)
getAttribute() 指定された名前の付いた属性値を取得してそのオブジェクトを返す。指定された属性が存在しない場合は null か "" (空文字列)。指定された属性が存在しない可能性がある場合は、hasAttribute() を使用して属性の存在をチェックしてから getAttribute() を呼び出すのが確実。
getAttributeNames() 要素の属性名の配列を返す
hasAttribute() 指定された指定された属性を持っているか否かを示す真偽値を返す
insertAdjacentElement() 指定した位置に要素ノードを挿入
insertAdjacentHTML() 指定した位置に HTML を挿入
insertAdjacentText() 指定した位置にテキストノードを挿入
removeAttribute() 指定された名前を持つ属性を削除。element.removeAttribute(属性名)
removeEventListener() EventTarget から登録されたイベントリスナーを削除。target.removeEventListener(type, listener[, options]);
setAttribute() 指定された名前を持つ属性値を設定。element.setAttribute(属性名, 値)
scroll() 指定した座標まで要素をスクロールさせます。

HTMLElement のプロパティ・メソッド

HTML 要素は HTMLElement インターフェイスを実装しているので、以下のようなプロパティやメソッドを使うことができます。

HTMLElement のプロパティ 抜粋(MDN:HTMLElement
プロパティ 説明
dataset 要素に設定された全てのカスタムデータ属性 (data-*) へのアクセスを提供
hidden 要素が hidden か否かを示す真偽値
innerText ノードやその子孫のテキストの内容
style 要素のインライン style の値(style 属性を表すオブジェクトである CSSStyleDeclaration)。全ての CSS プロパティの値を取得するには window.getComputedStyle() を使用します。
tabIndex 要素の tabIndex の値
title 要素のタイトル
HTMLElement のメソッド 抜粋(MDN:HTMLElement
メソッド 説明
blur() 現在フォーカスされている要素からキーボードフォーカスを外す
click() 要素にマウスクリックイベントを送信
focus() 要素にキーボードフォーカスを当てる

属性の操作

多くの属性は以下のように、要素の属性名のプロパティとしてアクセス及び設定することができます。

但し、属性名とプロパティ名が一致していない場合もあり、クラス属性は HTML では class ですが、DOM プロパティでは className になります。

<a id="foo" href="http://example.com" target="_blank" rel="noopener" title="Link Title" class="sample external">Sample link</a>

<script>
  var link = document.getElementById('foo');
  console.log(link.id); // foo
  console.log(link.href); // http://example.com/
  console.log(link.target); //_blank
  console.log(link.rel); //noopener
  console.log(link.title); //Link Title
  //class の場合は className
  console.log(link.className); //sample external

  //target を変更
  link.target = '_self';
  console.log(link.target); //_self
</script>

setAttribute() / getAttribute()

Element のメソッド setAttribute() と getAttribute() を使うと、属性名とプロパティ名の違いを意識することなく属性の値を取得・変更することができます。

また、属性を削除するには removeAttribute() を、属性が指定(設定)されているかを確認するには hasAttribute() を使います。

//属性値の取得
element.getAttribute(属性名)
//属性値の設定
element.setAttribute(属性名, 属性値)
//属性の削除
element.removeAttribute(属性名)
//指定された属性が設定されているかを確認
element.hasAttribute(属性名)
<a id="foo" href="http://example.com" target="_blank" rel="noopener" title="Link Title" class="sample external" data-icon="external">Sample link</a>

<img id="bg_img" src="images/bg-img.jpg" alt="" data-bgposition="center center" data-no-retina>

<script>
  //#foo の要素を取得して変数 link に代入
  var link = document.getElementById('foo');
  //クラス名を取得
  var class_name = link.getAttribute('class');
  //クラス名を出力
  console.log(class_name); //sample external

  //クラス名を設定(変更)
  link.setAttribute('class', 'sample internal');

  //target 属性を設定(変更)
  link.setAttribute('target', '_self');

  //data-icon 属性を設定(変更)
  link.setAttribute('data-icon', 'internal');

  //rel 属性を削除
  link.removeAttribute('rel');

  //属性値を出力(確認)
  console.log(link.getAttribute('class')); //sample internal
  console.log(link.getAttribute('target')); //_self
  console.log(link.getAttribute('data-icon')); //internal
  console.log(link.getAttribute('rel')); //null(削除されている)

  //#bg_img の要素を取得して変数 img に代入
  var img = document.getElementById("bg_img");

  //要素に data-no-retina 属性が設定されていれば
  if(img.hasAttribute('data-no-retina')) {
    //data-bgposition を変更(設定)
    img.setAttribute('data-bgposition', 'top center');
  }

  //変更した属性値を出力
  console.log(img.getAttribute('data-bgposition'));

</script>

但し、フォームコントロールの入力値(value 属性)は value プロパティで操作します。

value プロパティ(フォーム)

input 要素(テキスト入力ボックスなど)や textarea 要素に入力された値を取得するには value プロパティの値を参照します。また、単一選択型のセレクトボックスで選択されている項目(option 要素)の値を取得するには、select 要素の value プロパティで取得できます。

input 要素や textarea 要素の value プロパティに値を代入することでテキストボックス内の値を設定できます。

value プロパティと value 属性

value プロパティでは入力された値(テキストボックス内の値)を参照及び設定できます。

getAttribute('value') や setAttribute('value', '値') では HTML に記述された value 属性の初期値(defaultValue プロパティ)を参照及び設定できます。

そのため、入力された値(テキストボックス内の値)は value プロパティで操作し、value 属性の値は getAttribute('value') や setAttribute('value', '値') で操作します。

初期状態から変更されると、value プロパティで参照する値と getAttribute('value') で参照する値は異なってくる可能性があります。

<input type="text" id="my-input" value="初期値">

<script>
  const myInput = document.getElementById('my-input');
  console.log(myInput.value);  // 初期値

  // value プロパティを変更
  myInput.value = 'My Value';
  console.log(myInput.value);  // My Value
  console.log(myInput.getAttribute('value')); // 初期値

  // setAttribute() で変更
  myInput.setAttribute('value', '変更')
  console.log(myInput.value); // My Value
  console.log(myInput.getAttribute('value')); // 変更

  // defaultValue プロパティを変更
  myInput.defaultValue = 'デフォルト'
  console.log(myInput.value); // My Value
  console.log(myInput.getAttribute('value')); // デフォルト

  // change イベント
  myInput.addEventListener('change', ()=> {
    // Foo と入力
    console.log(myInput.value);  // Foo
    console.log(myInput.getAttribute('value')); // デフォルト
    console.log(myInput.defaultValue); // デフォルト
  })

</script>

関連:JavaScript フォームとフォームコントロールの使い方

dataset

data-* 属性(カスタムデータ属性)は通常の属性と同様、前述の getAttribute() や setAttribute() などを使って操作できますが、カスタムデータ属性専用の dataset プロパティを使うことができます。

dataset はその要素に設定された全ての data-* 属性 を含むオブジェクト(DOMStringMap)です。

以下は要素の dataset プロパティを取得してコンソールに出力しています。

<p class="user" data-id="123" data-user-name="foo" data-user-birth-place="usa">Foo</p>

<script>
  const user = document.querySelectorAll('.user')[0];
  //dataset プロパティにアクセス
  const userData = user.dataset;
  //取得した dataset プロパティをコンソールに出力
  console.log(userData);
</script>;

コンソールへの出力結果(キーと値のペアで構成されている)
DOMStringMap {id: "123", userName: "foo", userBirthPlace: "usa"}

DOMStringMap は DOMString のマップ(オブジェクト)で、キーと値のペア(エントリー)で構成されています。

dataset プロパティの場合、各エントリー(キーと値のペア)が個々の data-* 属性に対応していて、キーは data-* 属性の data- 以降の文字列で、その部分にハイフンが含まれればキャメルケースに変換されます。値は data-* 属性の値になります。

※ data-* 属性(カスタムデータ属性)の名前には大文字は使用できません。

以下は for in 文で取得した要素の dataset プロパティ(オブジェクト)の内容を出力しています。

<p class="user" data-id="123" data-user-name="foo" data-user-birth-place="usa">Foo</p>

<script>
  const user = document.querySelectorAll('.user')[0];
  //dataset プロパティの内容をコンソールに出力
  for(let prop in user.dataset){
    console.log(prop + " (キー) : " + user.dataset[prop] + " (値)\n");
  }
</script>

コンソールへの出力結果
id (キー) : 123 (値)
userName (キー) : foo (値)
userBirthPlace (キー) : usa (値)

値へのアクセス

dataset オブジェクトを使って data-* 属性の値を取得するには、属性名の data- 以降の部分をプロパティ名(キー)としてアクセスするか、またはブラケット構文を使用してアクセスすることもできます。

data- 以降の部分(キー)にハイフンが含まれる場合はキャメルケースに変換します。

<p class="user" data-id="123" data-user-name="foo" data-user-birth-place="usa">Foo</p>
<script>
  //要素を取得
  const user = document.querySelectorAll('.user')[0];

  //data-id 属性の値を出力(dataset.key)
  console.log(user.dataset.id); //123
  //ブラケット構文を使用(dataset['key'])
  console.log(user.dataset['id']); //123

  //data-user-name 属性の値を出力
  console.log(user.dataset.userName); //foo
  //ブラケット構文では変数が使用できる
  let key = 'userName';
  console.log(user.dataset[key]); //foo

  //data-user-birth-place 属性の値を出力
  console.log(user.dataset.userBirthPlace); //usa
  //ブラケット構文を使用
  console.log(user.dataset['userBirthPlace']); //usa
</script>  

特定のカスタムデータ属性が存在するか

特定のカスタムデータ(data-*)属性が dataset プロパティに存在するかは in 演算子を使用して「キー in datasetプロパティ」で確認できます。

キーはハイフンが含まれる場合はキャメルケースに変換します。

<p class="user" data-id="123" data-user-name="foo" data-user-birth-place="usa">Foo</p>
<script>
  //要素を取得
  const user = document.querySelector('[data-id="123"]');

  //data-id 属性が存在するかどうか
  console.log('id' in user.dataset);  //true
  //data-name 属性が存在するかどうか(存在しない)
  console.log('name' in user.dataset);  //false
  //data-user-name 属性が存在するかどうか
  console.log('userName' in user.dataset);  //true
</script>

hasAttribute() を使って以下でもほぼ同じです。

<script>
  //要素を取得
  const user = document.querySelector('[data-id="123"]');

  //data-id 属性が存在するかどうか
  console.log(user.hasAttribute('data-id'));  //true
  //data-name 属性が存在するかどうか(存在しない)
  console.log(user.hasAttribute('data-name'));  //false
  //data-user-name 属性が存在するかどうか
  console.log(user.hasAttribute('data-user-name'));  //true
</script>

カスタムデータ属性の更新(設定)

dataset プロパティ自体は読み取り専用ですが、dataset 内の個々のプロパティ(個々の data-* 属性)に対して書き込むことができます。

カスタムデータ属性も通常の属性同様、setAttribute() を使って設定することもできます。

<p class="user" data-id="123" data-user-name="foo" data-user-birth-place="usa">Foo</p>
<script>
  //要素を取得
  const user = document.querySelector('[data-user-name="foo"]');
  //data-user-birth-place 属性の値
  console.log(user.dataset.userBirthPlace);  //usa

  //data-user-birth-place 属性の値を更新
  user.dataset.userBirthPlace = 'Mexico';
  console.log(user.dataset.userBirthPlace);  //Mexico

  //setAttribute() を使って更新
  user.setAttribute('data-user-birth-place', 'Brasil');
  console.log(user.dataset.userBirthPlace);  //Brasil
</script>

値は文字列に変換される

カスタムデータ属性が設定されると、その値は常に文字列に変換されます。例えば、false は文字列 "false" に、undefined は文字列 "undefined" に変換されます。

以下の場合、elem.dataset.count は文字列なので += 1 は文字列として連結されます。

<div id="data1" data-count="99" >...</div>
<script>
  //要素を取得
  const elem = document.getElementById('data1');

  //data-count 属性の値の更新
  elem.dataset.count += 1;
  //更新された値を確認(100 にはならず、文字列として連結される)
  console.log(elem.dataset.count);  //991

  //値を数値(整数)に変換して1を加算
  elem.dataset.count = parseInt(elem.dataset.count) + 1;
  console.log(elem.dataset.count);  //992
</script>

カスタムデータ属性の追加

dataset プロパティの存在しないキーに値を設定すると、カスタムデータ属性が追加されます。

キーをキャメルケースで指定すると、カスタムデータ属性ではハイフンつなぎに変換されます。

<p class="user" data-id="123">Foo</p>
<script>
  //要素を取得
  const user = document.querySelector('[data-id="123"]');
  //data-gender 属性を設定(追加)
  user.dataset.gender = 'male';
  //キーにキャメルケースを指定して data-user-email 属性を設定(追加)
  user.dataset.userEmail = 'foo@example.com';
</script>

<!--上記を実行すると以下のようにカスタムデータ属性が追加される-->

<p class="user" data-id="123" data-gender="male" data-user-email="foo@example.com">Foo</p> 

カスタムデータ属性の削除

カスタムデータ属性を削除する場合は、delete 演算子や removeAttribute() を使用することができます。

<p class="user" data-id="123" data-gender="male" data-user-email="foo@example.com">Foo</p>
<script>
  //要素を取得
  const user = document.querySelector('[data-id="123"]');

  //data-gender 属性を delete 演算子で削除
  delete user.dataset.gender;
  //data-gender 属性が存在するかどうか
  console.log('gender' in  user.dataset);  //false(削除されている)

  //data-user-email 属性を delete 演算子で削除
  delete user.dataset.dataUserEmail;
  //data-gender 属性が存在するかどうか
  console.log('dataUserEmail' in  user.dataset);  //false(削除されている)
  //removeAttribute() で削除する場合
  //user.removeAttribute('data-user-email');
</script>

<!--上記を実行すると以下のようにカスタムデータ属性が削除される-->
<p class="user" data-id="123">Foo</p>

参考ページ:

クラスの操作

class 属性の値を取得したり設定(変更)する場合は、className プロパティを使うことができますが、クラスの追加や削除などは classList プロパティを使うと便利です。

classList はその要素に設定されているクラスのリストを返します。このリストは変更がリアルタイムで反映されるライブオブジェクトで、読み取り専用ですが提供されているメソッドで変更できます。

以下が classList(DOMTokenList)で使用できるプロパティやメソッドです。

classList(DOMTokenList)のプロパティとメソッド(一部抜粋)
プロパティ 説明
length リストに格納されているオブジェクトの数(指定されているクラスの数)
value リスト(クラス)の値の文字列
メソッド 説明
add() クラスの追加
contains() 指定された値がクラスに含まれていれば true を、含まれていなければ false を返す
forEach() それぞれのクラスに対して引数で渡されたコールバックを呼び出します。
remove() クラスの削除。指定したクラスがない場合、エラーはスローされず、何も起こりません。
replace() 指定したクラス(old)を指定した値(new)に置き換え replace(old, new);
toggle() クラスの切り替え。指定されたクラスが存在すれば、そのクラスを削除して false を返し、指定されたクラスが存在しなければ、追加して true を返す。

以下は classList プロパティを使ってクラスの追加や切り替え、削除などを行う例です。

<p id="foo" class="text-black">Foo Bar</p>

<script>
  //#foo の要素のクラスのリスト(ライブオブジェクト)を変数に代入
  var foo_class = document.getElementById('foo').classList;

  //sample クラスを追加
  foo_class.add('sample');

  //クラス(toggle-class)の切り替え(存在しないので追加)
  foo_class.toggle('toggle-class');
  console.log(foo_class.value);  //text-black sample toggle-class

  //クラス(toggle-class)の切り替え(存在するので削除)
  foo_class.toggle('toggle-class');
  console.log(foo_class.value);  //text-black sample

  //sample クラスを production に変更
  foo_class.replace('sample', 'production');

  //text-black クラスを削除
  foo_class.remove('text-black');

  //クラスが指定されているかの確認
  if(foo_class.contains('production')) {
    console.log('production exist!');  //production exist!
  }

  //クラスを出力
  console.log(foo_class.value);  //production
</script>
特定の文字列を含む class を判定

対象の要素を取得して、className でクラスを取得し、match() メソッドで特定の文字列がクラスに含まれているかを判定します。特定の文字列は正規表現を指定します。

match() メソッドは指定されたパターンを検索して一致した結果を格納した配列を戻し、配列の要素 0 にはマッチした文字列全体が入ります。

以下は全ての code 要素で language- を含むクラスを削除する例です。

<code class="language-java foo">Java</code>

<script>
  // 全ての code 要素
  const targets = document.querySelectorAll('code');

  targets.forEach((elem => {
    // クラスを取得
    const elemClassName = elem.className;
    console.log(elemClassName); // language-java foo
    // language- を含むパターン
    const languageClassRegex = /language-\S*/;
    const matched = elemClassName.match(languageClassRegex);
    if (matched && matched[0]) {
      console.log(matched[0]); // language-java
      elem.classList.remove(matched[0]); //
    }
  }));
</script>

スタイルの操作

JavaScript で直接ドキュメントのスタイルを設定する場合、インラインスタイルを設定するのが簡単です。

インラインスタイルが設定できない(@keyframes など)場合は、style 要素を追加したり、CSSStyleSheet インターフェイスを使って style 要素やスタイルシートを操作することもできます。

また、スタイルを操作(設定)する代わりに、スタイル用のクラスを適宜スタイルシートで定義してスクリプトからクラス属性の値を操作することで、スタイルを変更することもできます。

スタイルを設定(インライン)

style プロパティは、HTMLElement のプロパティとして提供されていて、CSSStyleDeclaration オブジェクトを返します。

インラインでスタイルを設定するには、要素の style プロパティの個々のプロパティ名(color など)に対して値を設定します。値は文字列で設定するので引用符で囲みます。

// 要素(element)にスタイルを設定
element.style.スタイルプロパティ名 = "値";

// #foo の要素の文字色を赤に設定する例
document.getElementById('foo').style.color = 'red';

null でリセット

値に null を設定することでリセットすることができます。

<p id="foo" style="color:green;">Foo</p>

<script>
//インラインスタイルをリセット(文字色はデフォルトの色で表示される)
document.getElementById('foo').style.color = null;
</script>
スタイルのプロパティ名

CSS のスタイルのプロパティ名にはハイフンが含まれるものがありますが、JavaScript ではハイフンはマイナス記号と解釈されるため、ハイフンを含む場合はキャメルケース(ハイフンを削除し、次の頭文字を大文字に)に書き換える必要があります。setProperty() メソッドを使えば、CSS のプロパティ名のまま指定できます。

//font-size は fontSize に書き換える
document.getElementById('foo').style.fontSize = '24px';

//setProperty() メソッドを使う場合
document.getElementById('foo').style.setProperty('font-size', '24px');

また float という単語は JavaScript では予約語となっているため、cssFloat というプロパティ名になります。JavaScript でアクセスできる CSS のプロパティ名は以下のリンクで確認することができます。

MDN : CSS プロパティリファレンス

CSSStyleDeclaration インターフェース

style プロパティで返される値はその要素に設定されているインラインのスタイルを表す CSSStyleDeclaration オブジェクトです。

CSSStyleDeclaration インターフェースには以下のようなプロパティとメソッドがあります。

CSSStyleDeclaration
プロパティ 説明
cssText CSS 設定(インライン)のテキスト(例 color: red;)
length インラインプロパティの数(読み取り専用)
個々のプロパティ
例:fontSize
個々の CSS プロパティ名(ハイフンを含む場合はキャメルケース)。style.color や style.fontSize でアクセスできます(前述の例)
メソッド 説明
item() 指定された index (先頭はゼロ) 位置にあるプロパティ名を返します。index が範囲外なら空文字を返します。添字 [ i ] を使ってもアクセスできます(i が範囲外なら undefined を返します)。
getPropertyValue() CSS プロパティ名を指定してインラインで設定されている値を取得。CSS でのプロパティ名を指定。例: getPropertyValue('font-size')
setProperty() CSS プロパティ名を指定してインラインで値を設定。CSS でのプロパティ名を指定。例:setProperty('font-size', '24px')
removeProperty() CSS プロパティ名を指定してインラインのプロパティを削除。CSS でのプロパティ名を指定。例:removeProperty('font-weight')
getPropertyPriority() 指定したプロパティに「!important」が指定されている場合は文字列「important」を返し、指定されていない場合は空文字列を返す。例: getPropertyPriority('color')

上記のプロパティやメソッドを使ってもスタイルを設定することができます。

以下は cssText プロパティを使って設定を変更する例です。cssText プロパティを使う場合は、一部のみを変更することはできず設定を全て記述する必要があるので注意が必要です。

<p id="foo" style="color:green; font-size:30px;">Foo</p>

<script>
//style プロパティを取得
var fooStyle = document.getElementById('foo').style;
// CSS の設定(テキスト)を確認
console.log(fooStyle.cssText);  //color: green; font-size: 30px;
// CSS を設定(※ font-size を記述していないので、font-size の設定は消える)
fooStyle.cssText = 'color : orange;'
// CSS の設定(テキスト)を確認
console.log(fooStyle.cssText);  //color : orange;
</script>
  

以下は setProperty() を使って特定の CSS を設定したり、removeProperty() を使って特定の CSS の設定を削除する例です。この場合、CSS のプロパティ名は、CSS の記述方法で指定し、キャメルケースに変換する必要はありません。

<p id="foo" style="color:green; font-size:30px;">Foo</p>

<script>
//style プロパティを取得
var fooStyle = document.getElementById('foo').style;
//font-size を設定(変更)
fooStyle.setProperty('font-size', '24px');
//font-size の設定を確認
console.log(fooStyle.getPropertyValue('font-size')); //24px
//font-weight を !important を指定して設定(追加)
fooStyle.setProperty('font-weight', 'bold', 'important');
//color を削除
fooStyle.removeProperty('color');
// CSS の設定(テキスト)を確認
console.log(fooStyle.cssText); //font-size: 24px; font-weight: bold !important;
</script>

以下は setProperty() の書式です。priority はオプションで、「important」を指定することができます。

style.setProperty(CSS プロパティ名, 値, priority);

インラインスタイルの削除

インラインスタイルを削除するには、removeProperty() を使うか、値に null を設定することでリセットすることができます。

<p id="foo" style="color:green; font-size:30px;">Foo</p>

<script>
//style プロパティを取得
var fooStyle = document.getElementById('foo').style;

// color の削除
fooStyle.removeProperty('color');
fooStyle.color = null;

// font-size の削除
fooStyle.removeProperty('font-size');
fooStyle.fontSize = null;
</script>

複数のプロパティをまとめて設定

複数のプロパティをまとめて設定する場合は、以下のような関数を定義しておくと便利かもしれません。

Object.keys() メソッドを使って指定したスタイルオブジェクト(styles)が持つ各プロパティを対象の要素のスタイルプロパティ(targetStyle)に設定しています。

//複数のプロパティをまとめて設定する関数
const setMyInlineStyles = (targetStyle, styles) => {
  Object.keys(styles).forEach( (key) => {
    targetStyle.setProperty(key, styles[key]);
  });
}

//function 文で定義する場合
/*function setMyInlineStyles(targetStyle, styles) {
  Object.keys(styles).forEach( function (key) {
    targetStyle.setProperty(key, styles[key]);
  });
}*/

//スタイルを設定する対象の要素
const targetElm = document.getElementById('styleTarget');
//対象の要素のスタイルプロパティ
const targetElmStyle = targetElm.style;
//複数のスタイルを設定(引数には対象の要素のスタイルプロパティとスタイルオブジェクトを指定)
setMyInlineStyles(targetElmStyle, {'color':'red', 'font-weight':'bold', 'background-color':'lightgreen'});

以下は Change ボタンをクリックすると複数のインラインスタイルを設定し、Remove ボタンをクリックすると設定したインラインスタイルを削除して元に戻す例です。

この例では設定するスタイルと削除するスタイルが同じなので、複数のプロパティをまとめて削除する関数も同じ値を受け取るようにしています。

但し、このような場合、可能であればクラスでスタイルを設定して、クラスの追加・削除を行うほうが簡単です。

<p id="styleTarget">This is the target element.</p>
<button type="button" id="changeStyle">Change</button>
<button type="button" id="removeStyle">Remove</button>

<script>
//複数のプロパティをまとめて設定する関数
const setMyInlineStyles = (targetStyle, styles) => {
  Object.keys(styles).forEach( (key) => {
    targetStyle.setProperty(key, styles[key]);
  });
}
//複数のプロパティをまとめて削除する関数
const removeMyInlineStyles = (targetStyle, styles) => {
  Object.keys(styles).forEach( (key) => {
    targetStyle.removeProperty(key);
  });
}

//スタイルを設定する対象の要素
const targetElm = document.getElementById('styleTarget');
//対象の要素のスタイルプロパティ
const targetElmStyle = targetElm.style;
//設定(削除)するスタイル
const myInlineStyle =  {'color':'red', 'font-weight':'bold', 'background-color':'lightgreen'};

document.getElementById('changeStyle').addEventListener('click', () => {
  setMyInlineStyles(targetElmStyle, myInlineStyle);
});

document.getElementById('removeStyle').addEventListener('click', () => {
  removeMyInlineStyles(targetElmStyle, myInlineStyle);
});
</script>

上記と同じことをクラスの追加・削除で行う場合の例(クラスの操作

<style>
.myStyle {
  color:red;
  font-weight:bold;
  background-color:lightgreen;
}
</style>

<p id="styleTarget">This is the target element.</p>
<div class="margin_top20">
  <button type="button" id="changeStyle">Change</button>
  <button type="button" id="removeStyle">Remove</button>
</div>

<script>
//要素のクラスリスト
const targetElmClass = document.getElementById('styleTarget').classList;

document.getElementById('changeStyle').addEventListener('click', () => {
  //myStyle クラスを追加
  targetElmClass.add('myStyle');
});

document.getElementById('removeStyle').addEventListener('click', () => {
  //myStyle クラスを削除
  targetElmClass.remove('myStyle');
});
</script>

This is the target element.

スタイルを取得

設定されているインラインスタイルは「style.スタイルプロパティ名」や「style.cssText」、メソッド style.getPropertyValue('プロパティ名') を使って取得することができます。

<p id="foo" style="color:green; font-size:18px; font-weight:bold;">Foo</p>

<script>
//style プロパティを取得
var fooStyle = document.getElementById('foo').style;

//style.スタイルプロパティ名
console.log(fooStyle.color);  //green
console.log(fooStyle.fontSize);  //18px
console.log(fooStyle.fontWeight);  //bold

// style.getPropertyValue()
console.log(fooStyle.getPropertyValue('color'));  //green
console.log(fooStyle.getPropertyValue('font-size'));  //18px
console.log(fooStyle.getPropertyValue('font-weight'));  //bold

//style.cssText
console.log(fooStyle.cssText);  //color: green; font-size: 18px; font-weight: bold;

//プロパティ名を style[i] または style.item(i) で取得
for (var i = 0; i < fooStyle.length; ++i) {
  var prop = fooStyle[i]; //または  fooStyle.item(i);
  console.log(prop + ':' + fooStyle.getPropertyValue(prop));
}

/* 上記 for 文の出力
color:green
font-size:18px
font-weight:bold
*/
</script>

但し、要素の style プロパティは、要素のインラインスタイル(style 属性)で設定された内容しか表していないため、適用されているススタイルシートや継承されたスタイルなどの情報は含まれていません。

要素に適用されている全ての CSS プロパティの値を取得するには、代わりに getComputedStyle() メソッドを使うことができます。

getComputedStyle() メソッドは、要素の算出スタイル(Computed Style)を取得するための API で Window オブジェクトのメソッドです。

getComputedStyle

window.getComputedStyle() はその要素のアクティブなスタイルを適用して算出したすべての CSS プロパティの値を含む(読み取り専用の)オブジェクトを返します。

var style = window.getComputedStyle(element,pseudoElt);

パラメータ
  • element:スタイルを調べる要素を指定
  • pseudoElt:疑似要素を調べる場合に指定。通常の要素には空文字列(または null)を指定。(省略可能)
戻り値
指定された要素の算出済みの CSS プロパティの値を含む読み取り専用の CSSStyleDeclaration オブジェクト。要素のスタイル(外部スタイルシート及びインライン等)が変更されると自動的に更新されます。
//以下は id 属性が foo の要素のフォントサイズの算出スタイルを取得する例
//#foo の要素を取得
var foo = document.getElementById('foo');
//#foo の算出スタイルを取得
var fooStyle = window.getComputedStyle(foo);
//#foo の算出スタイルの font-siz を出力
console.log(fooStyle.fontSize);
//以下でも同じ
console.log(fooStyle.getPropertyValue('font-size'));

MDN: window.getComputedStyle()

以下は id 属性が foo の p 要素の算出スタイルを取得する例です。

取得した算出スタイルは CSSStyleDeclaration のプロパティやメソッドが使えます。

以下の例では外部スタイルシートは使っていませんが、読み込んでいればそれらの値も含めて算出されたスタイルが取得できます。

<html>
<head>
<style>
p {
  font-size: 16px !important;
  color: #333;
}
</style>
</head>
<body>
<div style="background-color: #efefef;">
  <p id="foo" style="color:green; font-size:18px;">Foo</p>
</div>
</body>
<script>
// id 属性が foo の p 要素の算出スタイルを取得
var fooStyle = window.getComputedStyle(document.getElementById('foo'));

// 文字色を取得して出力
console.log(fooStyle.color); //rgb(0, 128, 0) → green(インラインの値)

// フォントサイズを取得(5行目の 16px !important が適用されている)
console.log(fooStyle.fontSize); //16px

// 背景色を取得(設定していないのでブラウザのデフォルト値)
console.log(fooStyle.backgroundColor);  //rgba(0, 0, 0, 0) 透過(ブラウザのデフォルト値)

// getPropertyValue() で font-weight を取得(ブラウザのデフォルト値)
console.log(fooStyle.getPropertyValue('font-weight')); //400

// マージンを取得(ブラウザのデフォルト値)
console.log(fooStyle.margin); //16px 0px
</script>
</html>

算出スタイルはブラウザのデベロッパーツールの「Computed」タブで表示される値です。

以下は id 属性が foo の要素の算出スタイルを全て出力する例です。

各プロパティ名には CSSStyleDeclaration の item() メソッドまたは添字にインデックスを指定してアクセスすることができます。

//id 属性が foo の要素の算出スタイルを取得
var fooStyle = window.getComputedStyle(document.getElementById('foo'));

//取得した算出スタイルの全てのプロパティ名を for 文で出力(総数は .length で取得)
for (var i = 0; i < fooStyle.length; ++i) {
  //プロパティ名を prop に代入
  var prop = fooStyle[i]; //または fooStyle.item(i);
  console.log(prop + ':' + fooStyle.getPropertyValue(prop));
}

/*以下は出力例です。
animation-delay:0s
animation-direction:normal
animation-duration:0s
animation-fill-mode:none
animation-iteration-count:1
animation-name:none
animation-play-state:running
animation-timing-function:ease
background-attachment:scroll
background-blend-mode:normal
background-clip:border-box
background-color:rgba(0, 0, 0, 0)
background-image:none
background-origin:padding-box
background-position:0% 0%
background-repeat:repeat
background-size:auto
border-bottom-color:rgb(0, 128, 0)
border-bottom-left-radius:0px
border-bottom-right-radius:0px
border-bottom-style:none
border-bottom-width:0px
border-collapse:separate
・・・以下省略・・・
*/
style タグを追加

createElement() メソッドを使って <style> タグ(要素)を作成することもできます。

以下は style タグ(要素)を作成して、スタイルを記述したテキストノードを style タグに追加し、 head 要素内の末尾に追加する例です。

//style タグ(要素)を作成して変数に代入
const styleElem = document.createElement('style');

//スタイルを記述したテキストノードを作成
const styles = document.createTextNode('p {color: red;}');
//テキストノードを style タグに追加
styleElem.appendChild(styles);

//style タグを head 要素内の末尾に追加
document.getElementsByTagName('head')[0].appendChild(styleElem);

上記により、head 要素内に <style>p {color: red;}</style> が追加されます。

上記ではテキストノードを作成してそれを style タグに追加していますが、以下のように innerText や innerHTML、textContent などを使っても同じです。

const styleElem = document.createElement('style');

//style タグにスタイルを設定
styleElem.innerText = 'p {color: red;} ';

//または、以下でも同じ
//styleElem.innerHTML = 'p {color: red;} ';
//styleElem.textContent = 'p {color: red;} ';

document.getElementsByTagName('head')[0].appendChild(styleElem);

また、innerHTML や textContent でバッククォート(テンプレートリテラル)を使って改行して記述することもできます。※但し、innerText の場合は出力される style タグの改行部分に <br> タグが挿入されてしまいます(スタイルとしては機能するようですが)。

const styleElem = document.createElement('style');

//バッククォートを使って改行して記述
styleElem.textContent = `
p {
  color: purple;
  font-size: 20px;
}
`;

//または、以下でも同じ
/*styleElem.innerHTML = `
p {
  color: purple;
  font-size: 20px;
}
`;*/

document.getElementsByTagName('head')[0].appendChild(styleElem);

関連項目:スタイルシートが存在すればルールを追加し、存在しなければ style 要素を追加

style 要素の属性

style 要素(タグ)には必要に応じて属性を指定することもできます。但し、現在 type 属性を含める理由はほとんどないようです(省略した場合の既定値は text/css)。

以下は style 要素に media 属性(メディアクエリ)を指定する例です。この場合、画面幅が 600px 以上で文字色が赤で表示されます。media 属性を省略した場合の既定値は all です。

const styleElem = document.createElement('style');

//style タグ(要素)に media 属性(メディアクエリ)を指定
styleElem.media = "screen and (min-width: 600px)"

styleElem.textContent = `
p {
  color: red;
}
`;

document.getElementsByTagName('head')[0].appendChild(styleElem);
CSSStyleSheet

CSS を構成する要素は DOM ではオブジェクトで表され、CCS スタイルシートは CSSStyleSheet インターフェースを実装します。

CSSStyleSheet オブジェクトのリスト(StyleSheetList)は、document.styleSheets プロパティを使用して取得でき、document.styleSheets で返されるリストは次の順序で並べられます。

  • <link> から取得したスタイルシートが最初に配置され、<head> 内の順に並べ替えられます。
  • DOM から取得したスタイルシート(style 要素)は、ツリー順にソートされた後に配置されます。

例えば、以下のような head 内で link タグで読み込むスタイルシートと style 要素で記述したスタイル、body 内で style 要素で記述したスタイルがある場合、

HTML
<!doctype html>
<html lang="ja">
<head>
  <meta charset="utf-8">
  <link rel="stylesheet" href="reset.css">
  <title>Sample</title>
  <style>
  body {
    max-width: 1200px;
    margin: 30px auto;
  }
  </style>
</head>
<body>
  <style>
    p {
      color: #999;
    }
  </style>
  <div>
    <p>Lorem ipsum dolor sit amet, consectetur adipisicing elit.</p>
  </div>
</body>
</html>
link タグで読み込んでいる reset.css
* {
  margin: 0 auto;
  padding: 0;
  box-sizing: border-box;
}
*:before, *:after {
  box-sizing: border-box;
}

document.styleSheets で返されるリストをコンソールに出力すると以下のように3つのスタイルシートが取得されています。

//CSSStyleSheet オブジェクトのリスト
const myStyleSheets = document.styleSheets;
console.log(myStyleSheets);

//以下がコンソールへの出力
StyleSheetList
0: CSSStyleSheet {ownerRule: null, cssRules: CSSRuleList, rules: CSSRuleList, type: 'text/css', href: 'http://localhost/.../reset.css', …}
1: CSSStyleSheet {ownerRule: null, cssRules: CSSRuleList, rules: CSSRuleList, type: 'text/css', href: null, …}
2: CSSStyleSheet {ownerRule: null, cssRules: CSSRuleList, rules: CSSRuleList, type: 'text/css', href: null, …}
length: 3

それぞれのスタイルシートには添字や item() でアクセスすることができます。

上記の場合、head 内の link タグで読み込んでいる reset.css には myStyleSheets[0] または myStyleSheets.item(0) でアクセスできます。

CSSStyleSheet オブジェクトには様々なプロパティがあります。以下はその一部をコンソールに出力する例です。

const myStyleSheets = document.styleSheets;

//reset.css のルール(cssRules)の数
console.log(myStyleSheets[0].cssRules.length);
//2

//reset.css の最初のルール(cssRules)の cssText
console.log(myStyleSheets[0].cssRules[0].cssText);
//* { margin: 0px auto; padding: 0px; box-sizing: border-box; }

//reset.css の2番目のルール(cssRules)の cssText
console.log(myStyleSheets[0].cssRules[1].cssText);
//::before, ::after { box-sizing: border-box; }

//reset.css が無効化されているかどうか
console.log(myStyleSheets[0] .disabled);
//false

//reset.css の URI
console.log(myStyleSheets[0] .href);
//http://localhost/...省略.../reset.css

//reset.css の owner 要素(document と関連付けているノード)
console.log(myStyleSheets[0] .ownerNode);
//<link rel="stylesheet" href="reset.css">

//head 内 style のルール(cssRules)の数
console.log(myStyleSheets[1].cssRules.length);
//1

//head 内 style の最初のルール(cssRules)の cssText
console.log(myStyleSheets[1].cssRules.item(0).cssText);
//body { max-width: 1200px; margin: 30px auto; }

CSSStyleSheet は cssRules という CSS ルールのリストを表すプロパティを持ち、このプロパティには length プロパティ と item() メソッドがあります。

例えば、reset.css の最初のルール(cssRules)は myStyleSheets[0].cssRules[0]、2番目のルールは myStyleSheets[0].cssRules[1] でアクセスできます。

この例の link タグで読み込んでいる reset.css の場合、以下のように2つのルールで構成されています。

/* myStyleSheets[0].cssRules[0] または myStyleSheets[0].cssRules.item(0)*/
* {
  margin: 0 auto;
  padding: 0;
  box-sizing: border-box;
}

/* myStyleSheets[0].cssRules[1]  または myStyleSheets[0].cssRules.item(1)*/
*:before, *:after {
  box-sizing: border-box;
}

また、CSSStyleSheet にはスタイルシート内の指定した位置にルールを挿入する insertRule() やスタイルシートから指定した位置のルールを削除する deleteRule() メソッドなどがあります。

sheet プロパティ

style 要素を表すインタフェース(HTMLStyleElement)は HTMLElement 及び LinkStyle のプロパティとメソッドを継承し、LinkStyle には要素が読み込んでいるスタイルシート(CSSStyleSheet)を返す sheet プロパティがあります。

また、link 要素が実装するインターフェイス HTMLLinkElement にも LinkStyle.sheet プロパティがあり、スタイルシート(CSSStyleSheet)を返します。

このため、document.styleSheets で返されるリストからスタイルシートにアクセスする代わりに、style 要素や link 要素の sheet プロパティでスタイルシートにアクセスすることもできます。

例えば、以下のように link 要素 や style 要素に id 属性を指定すればそれらを使ってスタイルシートにアクセスすることもできます。

<!doctype html>
<html lang="ja">
<head>
<link id="reset_css" rel="stylesheet" href="reset.css">
<title>Sample</title>
<style id="body_style">
body {
  max-width: 1200px;
  margin: 30px auto;
}
</style>
</head>
<body>
<style id="p_style">
  p {
    color: #999;
  }
</style>
<div>
  <p>Lorem ipsum dolor sit amet, consectetur adipisicing elit. Ipsa necessitatibus amet aliquid..</p>
</div>
</body>
</html>

以下のように要素にアクセスして sheet プロパティから CSSStyleSheet を取得することができます。

//id が reset_css の link 要素
const restCSS = document.getElementById('reset_css');
console.log(restCSS.sheet); //sheetプロパティ
//CSSStyleSheet {ownerRule: null, cssRules: CSSRuleList, rules: CSSRuleList, type: 'text/css', href: 'http://localhost/.../reset.css', …}

console.log(restCSS.sheet.cssRules.length);
//2

console.log(restCSS.sheet.cssRules[0].cssText);
//* { margin: 0px auto; padding: 0px; box-sizing: border-box; }

//id が body_style の style 要素
const bodyStyle = document.getElementById('body_style');
console.log(bodyStyle.sheet);
//CSSStyleSheet {ownerRule: null, cssRules: CSSRuleList, rules: CSSRuleList, type: 'text/css', href: null, …}

//id が p_style の style 要素
const pStyle = document.getElementById('p_style');
console.log(pStyle.sheet);
//CSSStyleSheet {ownerRule: null, cssRules: CSSRuleList, rules: CSSRuleList, type: 'text/css', href: null, …}
ルール(cssRules)を変更

CSS のルール(cssRules)を変更することができます。

それぞれのルールに対して、インラインスタイル同様、style プロパティを使ってルールを設定(変更)することができます。

style プロパティの個々のプロパティ名(color など)に対して値を設定するか、setProperty() を使って設定します。

//CSSStyleSheet オブジェクトのリストを取得
const myStyleSheets = document.styleSheets;

//最初のスタイルシート(添字が0)を取得
const resetCSS = myStyleSheets[0];
//最初のルール cssRules[0] に background-color を設定
myStyleSheets[0].cssRules[0].style.setProperty('background-color','aqua');


//id が p_style の style 要素を取得
const pStyle = document.getElementById('p_style');
//style 要素の sheet プロパティの最初のルール cssRules[0] に color を設定(変更)
pStyle.sheet.cssRules[0].style.color = 'red';
スタイルシートに CSS ルールを追加

CSSStyleSheet のメソッド insertRule() を使って CSS ルールをスタイルシートの指定した位置に挿入(追加)することができます。

insertRule()

以下が insertRule() の書式です。

stylesheet は CSS スタイルシートを表わす CSSStyleSheet インターフェイスで、document.styleSheets で取得したリストからインデックスでアクセスするか、style 要素や link 要素の sheet プロパティでスタイルシートにアクセスできます。

stylesheet.insertRule( rule[, index] )
  • rule:追加する CSS ルールの文字列。※一度に追加できるルール selector {...} は1つのみです。
  • index:追加されるルールの位置(stylesheet.cssRules.length の値以下の正の数)。既定値は 0(先頭に追加)。

例えば以下のようなスタイルシート(style.css)を読み込んでいる HTML がある場合、

HTML
<!doctype html>
<html lang="ja">
<head>
  <meta charset="utf-8">
  <link rel="stylesheet" href="style.css"> <!-- link 要素(CSS の読み込み) -->
  <title>Sample</title>
  <style> /* style 要素 */
  .foo {
    color: #999;
  }
  </style>
</head>
<body>
  <div>
    <p class="foo">Lorem ipsum dolor sit amet, consectetur adipisicing elit.</p>
  </div>
</body>
</html>

style.css
* {
  margin: 0 auto;
  padding: 0;
  box-sizing: border-box;
}

body {
  max-width: 1200px;
  margin: 30px auto;
  font-size: 14px;
}

以下のように document.styleSheets で CSS のリストを取得してインデックス(添字)でそれぞれのスタイルシートにアクセスできます。

また、link 要素 や style 要素に id 属性が指定されていれば、sheet プロパティでスタイルシートにアクセスすることもできます。

//CSS(CSSStyleSheet)のリストを取得
const myStyleSheets = document.styleSheets;

//style.css の CSSStyleSheet
const styleCss = myStyleSheets[0];

console.log(styleCss);
//CSSStyleSheet {ownerRule: null, cssRules: CSSRuleList, rules: CSSRuleList, type: 'text/css', href: 'http://localhost/.../style.css', …}

console.log(styleCss.cssRules.length);
//2(ルールは2つ)

//head 要素内の style 要素の CSSStyleSheet
const styleElem = myStyleSheets[1];

console.log(styleElem);
//CSSStyleSheet {ownerRule: null, cssRules: CSSRuleList, rules: CSSRuleList, type: 'text/css', href: null, …}

console.log(styleElem.cssRules.length);
//1 (ルールは1つ)

insertRule() でルールを追加

以下は前述の例の HTML と CSS で、insertRule() を使ってスタイルシートにルールを追加する例です。

//CSS のリストを取得
const myStyleSheets = document.styleSheets;

//style.css の CSS
const styleCss = myStyleSheets[0];

//style.css の最後(styleCss.cssRules.length)にルールを追加
styleCss.insertRule('div { background-color: #eee; }', styleCss.cssRules.length);

//head 要素内の style 要素の CSS
const styleElem = myStyleSheets[1];

//head 要素内の style 要素の先頭にルールを追加(第2引数を省略 → 0)
styleElem.insertRule('p { padding: 10px; }');

スタイルシートが存在すればルールを追加し、存在しなければ style 要素を追加

以下はドキュメントにスタイルシートが存在すれば、スタイルシートに @keyframes を使ったルールを追加し、スタイルシートが存在しない場合は、style 要素を作成してルールを追加する例です。

以下の例ではキーフレームのアニメーションのルールを末尾に追加し、アニメーションの対象となる要素のルールを先頭に追加しています。

insertRule() を使って挿入できるルールは1つだけなので、以下の場合、変数 keyframes にキーフレームのルールと .foo {...} のルールを一緒に記述するとエラーになります。

const keyframes = '@keyframes rotate { '+
                    'from {transform:rotate( 0deg ) }'+
                    'to {transform:rotate( 360deg ) }'+
                  '}';

/* または以下のようにバッククォートを使って記述することもできます
const keyframes = `@keyframes rotate {
  from {
    transform:rotate( 0deg );
  }
  to {
    transform:rotate( 360deg );
  }
}`;
*/

//スタイルシートが存在すれば、スタイルシートにルールを追加
if( document.styleSheets && document.styleSheets.length ) {
  //最初(インデックスが0)のスタイルシートを取得
  const s = document.styleSheets[0];
  //変数 keyframes に代入したルールを末尾に追加
  s.insertRule( keyframes, document.styleSheets[0].cssRules.length );
  //第1引数に指定したルールを先頭に追加
  s.insertRule( '.foo {animation: rotate 3s;}', 0 );

} else { //スタイルシートが存在しなければ、style 要素を作成してルールを追加
  //style 要素を作成
  const s = document.createElement( 'style' );
  //変数 keyframes に代入したルールを style 要素のコンテンツとして設定
  s.innerHTML = keyframes;
  //style 要素を head 要素内の末尾に追加
  document.getElementsByTagName( 'head' )[ 0 ].appendChild( s );
  //第1引数に指定したルールを style 要素の先頭に追加
  s.sheet.insertRule( '.foo {animation: rotate 3s;}', 0 );
}

上記の例の場合、ドキュメントを開くと一度だけ rotate アニメーションが実行されます。

以下はボタンをクリックするとアニメーションを実行する例です。クリックイベントでアニメーションを行う要素にアニメーションのクラスを追加することで、アニメーションを実行します。

CSS アニメーションの場合、2回目以降のクリックでもアニメーションを実行するには animationend イベントを使ってアニメーション終了時に classList から一度実行したアニメーションのクラスを削除する必要があります。

<div>
  <p id="foo">Lorem ipsum dolor sit amet.</p>
</div>
<button id="btn" type="button">Click</button>

<script>
  //アニメーションのルール(バッククォートを使って記述)
  const keyframes = `@keyframes rotate {
    from {
      transform:rotate( 0deg );
    }
    to {
      transform:rotate( 360deg );
    }
  }`;

  //スタイルシートが存在すれば、スタイルシートにルールを追加
  if( document.styleSheets && document.styleSheets.length ) {
    //最初(インデックスが0)のスタイルシートを取得
    const s = document.styleSheets[0];
    //変数 keyframes に代入したルールを末尾に追加
    s.insertRule( keyframes, document.styleSheets[0].cssRules.length );
    //第1引数に指定したルールを先頭に追加
    s.insertRule( '.rotate {animation: rotate 3s;}', 0 );

  } else { //スタイルシートが存在しなければ、style 要素を作成してルールを追加
    //style 要素を作成
    const s = document.createElement( 'style' );
    //変数 keyframes に代入したルールを style 要素のコンテンツとして設定
    s.innerHTML = keyframes;
    //style 要素を head 要素内の末尾に追加
    document.getElementsByTagName( 'head' )[ 0 ].appendChild( s );
    //第1引数に指定したルールを style 要素の先頭に追加
    s.sheet.insertRule( '.rotate {animation: rotate 3s;}', 0 );
  }
  //id が btn のボタン要素を取得
  const btn = document.getElementById('btn');
  //id が foo の div 要素を取得
  const foo = document.getElementById('foo');

  //ボタンにクリックイベントを設定
  btn.addEventListener('click', function() {
    //クリックされたら rotate クラスを追加(アニメーションを実行)
    foo.classList.add('rotate');
  })

  //アニメーション終了時のイベントを設定
  foo.addEventListener('animationend', function() {
    //実行したアニメーションのクラスを削除
    this.classList.remove('rotate');
  });

</script>

参考:CSS アニメーション対応の検出

要素のサイズと位置(座標)

サイズを取得

要素のサイズは offsetWidth/Height や clientWidth/Height などを使って取得することができます(他にも方法はあります)。

プロパティ 説明
offsetWidth 要素の幅を整数として返します。ボーダー、パディング、垂直スクロールバー(表示されていれば)を含む要素の CSS width のピクセル単位の測定値。
offsetHeight 要素の高さを整数として返します。ボーダー、パディング、水平スクロールバー(表示されていれば)を含む要素の CSS height のピクセル単位の測定値。
clientWidth 要素の内側の幅を整数として返します。パディングは含みますが、ボーダー、マージン、 垂直スクロールバーは含みません。
clientHeight 要素の内側の高さを整数として返します。パディングは含みますが、ボーダー、マージン、 水平スクロールバーは含みません。

以下は box-sizing プロパティ に content-box を指定した場合の例です(現在はあまり使われない設定だと思いますが)。

#sampleBox1 {
  width: 200px;
  height: 100px;
  border: 5px solid #98D799;
  padding: 10px;
  margin: 40px 20px;
  box-sizing: content-box;
}
/*content-box では width と height にはパディングとボーダーは含まれません。*/
<div id="sampleBox1"></div>

<script>
const sampleBox1 = document.getElementById('sampleBox1');

//offsetWidth = CSS width + 左右 border + 左右 padding
console.log('offsetWidth : ' + sampleBox1.offsetWidth);  //230

//offsetHeight = CSS height + 上下 border + 上下 padding
console.log('offsetHeight : ' + sampleBox1.offsetHeight);  //130

//offsetWidth = CSS width + 左右 padding
console.log('clientWidth : ' + sampleBox1.clientWidth);  //220

//offsetWidth = CSS width + 上下 padding
console.log('clientHeight : ' + sampleBox1.clientHeight); //120
</script>

以下は box-sizing プロパティ に border-box を指定した場合の例です。

#sampleBox2 {
  width: 200px;
  height: 100px;
  border: 5px solid #98D799;
  padding: 10px;
  margin: 40px 20px;
  box-sizing: border-box;
}
/*border-box では width と height にパディングとボーダーの大きさが含められます*/
<div id="sampleBox2"></div>
<script>
const sampleBox2 = document.getElementById('sampleBox2');

//offsetWidth = CSS width
console.log('offsetWidth : ' + sampleBox2.offsetWidth);  //200

//offsetHeight = CSS height
console.log('offsetHeight : ' + sampleBox2.offsetHeight);  //100

//clientWidth = CSS width - 左右 border
console.log('clientWidth : ' + sampleBox2.clientWidth);  //190

//clientHeight = CSS height - 上下 border
console.log('clientHeight : ' + sampleBox2.clientHeight); //90
</script>

小数値を取得

小数値が必要な場合は、要素の getBoundingClientRect() メソッドを使って返される値の各プロパティ(left, top, right, bottom, x, y, width, height)から取得することができます。

#sampleBox3 {
  width: 16.73%;
  height: 100px;
  border: 5px solid #98D799;
  padding: 10px;
  margin: 40px 20px;
  box-sizing: border-box;
}

以下は getBoundingClientRect() メソッドで取得したオブジェクトの width プロパティで幅を取得する例です。offsetWidth や clientWidth では値は整数に丸められますが、getBoundingClientRect() で取得した値は丸められていません。

<div id="sampleBox3"></div>
<script>
const sampleBox3 = document.getElementById('sampleBox3');

//整数値
console.log(sampleBox3.offsetWidth);  //134
console.log(sampleBox3.clientWidth);  //124 (左右ボーダーの分 10px 小さい)

//小数値
console.log(sampleBox3.getBoundingClientRect().width); //133.828125
</script>
パディングを取得

offsetWidth や offsetHeight、及び clientWidth と clientHeight はいずれもパディングを含みます。

要素のパディングを含まないサイズを取得するには、getComputedStyle メソッドを使用して、コンピューテッドスタイルからパディングを引きます。

以下は要素のパディングを含まない高さを取得する例です。

#box1 {
  width: 200px;
  height: 100px;
  border: 5px solid #999;
  padding: 10px 5px 20px;
  margin: 20px;
  box-sizing: border-box;
  background-color: #c9f9ca;
}
box1

要素を取得して、そのコンピューテッドスタイルから上下のパディングを取得し、要素の高さからパディングを差し引きます。

要素の高さは clientHeight はボーダーを含みませんが、offsetHeight はボーダーを含みます。

コンピューテッドスタイルにプロパティ名を指定して取得できる値は CSS の文字列(px などを含む)ので計算する際は、parseFloat() を使って数値に変換します。

// 要素を取得
const box1 = document.getElementById('box1');

// 要素のコンピューテッドスタイルを取得
const box1CS = window.getComputedStyle(box1);

// パディングトップ
const box1PT = parseFloat(box1CS.paddingTop);
console.log(box1PT); // 10

// パディングボトム
const box1PB = parseFloat(box1CS.paddingBottom);
console.log(box1PB); // 20

// 上下のパディング、ボーダーを含まない高さ
const heightWithoutPaddingBorder = box1.clientHeight - box1PT - box1PB;
console.log(heightWithoutPaddingBorder); // 60

// 上下のパディングを含まない高さ(ボーダーを含む)
const heightWithoutPadding = box1.offsetHeight - box1PT - box1PB;
console.log(heightWithoutPadding); // 70

// ボーダートップ
console.log(box1CS.borderTop); // 5px solid rgb(153, 153, 153)

// ボーダートップの幅
console.log(parseFloat(box1CS.borderTopWidth))  // 5
サイズを設定

要素の幅と高さは style プロパティで設定・変更することができます。

element.style.width(height) に値を設定するか、setProperty() メソッドを使います。

以下は「Change Size」をクリックするとサイズを変更し、「Reset Size」をクリックすると元のサイズに戻す例です。

#sampleBox4 {
  width: 100px;
  height: 100px;
  border: 5px solid #5764D5;
  padding: 10px;
  margin: 40px 0px 20px;
  box-sizing: border-box;
}
<div id="sampleBox4"></div>
<button type="button" id="changeSize">Change Size</button>
<button type="button" id="resetSize">Reset Size</button>

<script>
  const sampleBox4 = document.getElementById('sampleBox4');
  const box4Width = sampleBox4.offsetWidth;
  const box4Height = sampleBox4.offsetHeight;

  document.getElementById('changeSize').addEventListener('click', () => {
    //setProperty() メソッドを使ってサイズを設定
    sampleBox4.style.setProperty('width', '200px');
    sampleBox4.style.setProperty('height', '150px');
   });

  document.getElementById('resetSize').addEventListener('click', () => {
    //element.style.width(height)でサイズを設定
    sampleBox4.style.width = box4Width + 'px';
    sampleBox4.style.height = box4Height + 'px';
   });
</script>
位置(座標)

要素の位置は offsetParent となる要素に対するオフセット座標やページ左上に対する(document を起点とする文書の原点からの)ドキュメント座標を取得することができます。

offsetParent

offsetParent プロパティは position 属性に relative、absolute、fixed のいづれかをもつ直近の親(祖先)要素を返す HTMLElement(HTML 要素)のプロパティです。

但し、その要素が table 要素内にある場合は、直近の td 要素や table 要素を返し、static 以外の position 属性が指定された要素も table 要素もない場合は body を返します。

また、以下の場合、offsetParent は null を返します。

  • 表示されていない要素(display:none)またはドキュメント上にない表示の場合
  • 要素の position が fixed に設定されている場合 (firefox は <body> を返します)。
  • 要素が <body> または <html> の場合

オフセット座標の取得

要素のオフセット座標は offsetLeft 及び offsetTop で取得できます。

Lorem ipsum dolor sit amet

Harum, ipsam! Alias aut

<style>
  #op1 {
    position: relative;
    padding: 20px;
    border: 1px solid #ccc;
    max-width: 300px;
  }
  #pos1 {
    margin-left: 10px;
    background-color: #C6D9F7;
  }
</style>
<div id="op1">
  <p>Lorem ipsum dolor sit amet</p>
  <p id="pos1">Harum, ipsam! Alias aut</p>
</div>
<script>
  const pos1 = document.getElementById('pos1');
  //pos1 の offsetParent の id
  console.log(pos1.offsetParent.id);  //op1 (直近の position: relative の要素)
  //pos1 の offsetLeft
  console.log(pos1.offsetLeft);  //30
  //pos1 の offsetTop
  console.log(pos1.offsetTop);  //50
</script> 

getBoundingClientRect()

Element.getBoundingClientRect() メソッドは、要素のウィンドウ座標(ビューポートに対する位置)とサイズのプロパティを持つ DOMRect オブジェクトを返します。

DOMRect オブジェクトのプロパティ
プロパティ 説明
top 要素の上端の Y 座標(ウィンドウの左上からの座標)
left 要素の左端の X 座標(ウィンドウの左上からの座標)
right 要素の右端の X 座標(ウィンドウの左上からの座標)。right = left + width
bottom 要素の下端の Y 座標(ウィンドウの左上からの座標)。bottom = top + height
x X 座標(ウィンドウの左上からの座標)。left と同じ
y Y 座標(ウィンドウの左上からの座標)。top と同じ
width 要素の幅。width = right - left
height 要素の高さ。height = bottom - top

ウィンドウ座標はウィンドウの左上端から計算されたもので、ページをスクロールしたり、幅を変更するとそのウィンドウ座標は変わります。

以下はボタンをクリックすると、id 属性が box1 の div 要素に getBoundingClientRect() メソッドを適用して取得したオブジェクトのプロパティをアラートで表示する例です。

ページを上下にスクロールすると、top や bottom、y の値が変わり、ブラウザの幅を変更すると left や right、x の値が変わります。

box1
<style>
  #box1 {
    width: 30%;
    height: 5vw;
  }
</style>

<div id="box1">box1</div>
<button type="button" id="showRectProps">box1.getBoundingClientRect()</button>

<script>
  //対象の div 要素
  const box1 = document.getElementById('box1');

  //ボタンに click イベントを設定
  document.getElementById('showRectProps').addEventListener('click', ()=> {
    //対象の div 要素の getBoundingClientRect() メソッドを実行してプロパティをアラート表示
    const box1Rect = box1.getBoundingClientRect();
      alert(`top: ${box1Rect.top}
left: ${box1Rect.left}
right: ${box1Rect.right}
bottom: ${box1Rect.bottom}
x: ${box1Rect.x}
y: ${box1Rect.y}
width: ${box1Rect.width}
height ${box1Rect.height}`);
    });
</script> 

ドキュメント座標の取得

要素のドキュメント座標を取得するには、getBoundingClientRect() でウィンドウ座標を取得し、それらに現在のスクロール量を加えます。

  • top = elem.getBoundingClientRect().top + pageYOffset
  • left = elem.getBoundingClientRect().left + pageXOffset

以下はボタンをクリックすると要素のドキュメント座標を取得してアラート表示する例です。

box2
<div id="box2">box2</div>
<button type="button" id="getDocOffset">ドキュメント座標を取得</button>

<script>
// 対象の div 要素
const box2 = document.getElementById('box2');

document.getElementById('getDocOffset').addEventListener('click', () => {
  //対象の div 要素に getBoundingClientRect() を適用して DOMRect を取得して変数に代入
  const box2Rect = box2.getBoundingClientRect();
  //ドキュメント上端からの位置
  const top = box2Rect.top + pageYOffset;
  //ドキュメント左端からの位置
  const left = box2Rect.left + pageXOffset;
  alert('top: ' + top + "\n" + 'left: ' + left );
});
</script>

以下のように要素のドキュメント座標を取得する関数を作成しておくと便利かも知れません。

const box2 = document.getElementById('box2');

// 要素のドキュメント座標を取得する関数
function getDocOffset(elem) {
  let box = elem.getBoundingClientRect();
  return {
    top: box.top + pageYOffset,
    left: box.left + pageXOffset
  };
}

document.getElementById('getDocOffset').addEventListener('click', () => {
  const rect = getDocOffset(box2);
  alert('top: ' + rect.top + "\n" + 'left: ' + rect.left );
});

ウィンドウのサイズと位置

Window オブジェクトや Screen オブジェクトのプロパティからウインドウ(表示領域)のサイズやディスプレイのサイズ、位置などを取得することができます。

画面(スクリーン)サイズ

ディスプレイ画面のサイズは Screen オブジェクトのプロパティから取得することができます。

以下はモニタのスクリーンサイズを取得して表示する例です。

<p>お使いのモニタの画面サイズ: <span id="screen_size"></span></p>

<script>
// モニタの画面の横幅(window を省略して screen.width でも可)
const sw = screen.width;
// モニタの画面の高さ(window を省略して screen.height でも可)
const sh = screen.height;

document.getElementById('screen_size').textContent = sw + ' x ' + sh + ' ピクセル';
</script>

お使いのモニタの画面サイズ:

Screen オブジェクトのプロパティ(一部抜粋)
プロパティ 説明
screen.width モニタ画面の横幅
screen.height モニタ画面の高さ
screen.availWidth モニタ画面の利用可能な横幅
screen.availHeight モニタ画面の利用可能な高さ。デスクトップのタスクバーなどのスペースを除いた高さ。

ウィンドウ(表示領域)のサイズ

ほとんどのブラウザでは、Window オブジェクトの innerWidth/innerHeight や outerWidth/outerHeight プロパティからウィンドウのサイズを取得できます。但し、IE8 以下では取得できません。

以下は window.innerWidth と window.innerHeight を使って表示領域のサイズを取得して表示する例です。また、resize イベントを設定してブラウザのサイズを変更すると表示も更新するようにしています(イベント終了時に処理を実行)。

<p>ブラウザの表示領域のサイズ: <span id="window_size"></span></p>

<script>
  // 表示領域の横幅
  let wiw = window.innerWidth;
  // 表示領域の高さ
  let wih = window.innerHeight;

  const window_size = document.getElementById('window_size');
  window_size.textContent = wiw + ' x ' + wih + ' ピクセル';

  let timer = false;
  window.addEventListener('resize', () => {
    if (timer !== false) {
      clearTimeout(timer);
    }
    //リサイズ操作が終了した時点で処理を実行
    timer = setTimeout(function() {
      window_size.textContent = window.innerWidth + ' x ' + window.innerHeight + ' ピクセル';
    }, 200);
  });
</script>

ブラウザの表示領域のサイズ:

Window オブジェクトのプロパティ(一部抜粋)
プロパティ 説明 値(※)
window.outerWidth ブラウザウィンドウの外枠の幅
window.outerHeight ブラウザウィンドウの外枠の高さ
window.innerWidth 表示領域の幅
window.innerHeight 表示領域の高さ(外枠の大きさからメニューバー、ツールバーなどの大きさを引いた値)

※このページを開いた時点での値です。リサイズしても更新されません(再読み込みが必要です)。

clientWidth / clientHeight プロパティ

Element.clientWidth は要素の内側の幅を表し、Element.clientHeight は要素の内側の高さを表します。

パディングは含みますが、ボーダーやマージン、スクロールバーは含みません。また、インライン要素や CSS のない要素ではゼロになります。

document.body は body 要素を、document.documentElement は document のルート要素 (html 要素) を返します。

プロパティ 説明 値(※)
document.body.clientWidth body 要素の内側のサイズ(幅)
document.body.clientHeight body 要素の内側のサイズ(高さ)
document.documentElement.clientWidth ウィンドウの幅
document.documentElement.clientHeight ウィンドウの高さ

※このページを開いた時点での値です。リサイズしても更新されません(再読み込みが必要です)。

ウィンドウの位置

デスクトップ上でのブラウザウィンドウの位置は以下のプロパティで取得することができます。

プロパティ 説明 値(※)
window.screenX デスクトップ上でのブラウザウィンドウの水平方向の位置(ビューポートの左端から画面の左端までの水平距離)
window.screenY デスクトップ上でのブラウザウィンドウの垂直方向の位置(ビューポートの上端から画面の上端までの垂直距離)
window.screenLeft screenX の別名(もともとは IE のみが対応。)
window.screenTop screenY の別名(もともとは IE のみが対応)

※このページを開いた時点での値です。ブラウザを移動しても更新されません(再読み込みが必要です)。

スクロール量の取得

ウィンドウの現在のスクロール位置は以下のプロパティを使って取得することができます。

プロパティ 説明
window.scrollX 文書が水平方向にスクロールされている量(現在のビューポートの左端の X 座標)を浮動小数点値(ピクセル数)で返します。文書が左にも右にもスクロールされていない場合の scrollX は 0
window.scrollY 文書が垂直方向にスクロールされている量(現在のビューポートの上端の Y 座標)を浮動小数点値(ピクセル数)で返します。文書が上にも下にもスクロールしていない場合の scrollY は 0。
window.pageXOffset scrollX の別名(クロスブラウザー互換用。但し、IE9 以前は未対応)
window.pageYOffset ScrollY の別名(クロスブラウザー互換用。但し、IE9 以前は未対応)

要素のスクロール量

要素のスクロールの位置(量)を取得(設定)するには、以下の Element のプロパティを使用します。

要素をスクロールさせるには、Element の scroll() メソッドを使用することができます。

プロパティ 説明
element.scrollTop 要素の垂直方向のスクロールするピクセル数(スクロール量)を取得または設定します。
element.scrollLeft 要素の水平方向のスクロールするピクセル数(スクロール量)を取得または設定します。

以下のボックス(iframe で読み込んでいるコンテンツ)内で水平・垂直方向にスクロールすると値が更新されます。

上記ボックスは以下の内容のファイルを iframe で読み込んでいます。

<style>
body {
  margin: auto;
  max-width: 900px;
}
.content {
  /*スクロールが発生するように大きなサイズを指定*/
  width: 2000px;
  height: 2000px;
}
.output {
  /*iframe で見やすいように固定配置*/
  position: fixed;
}
</style>
</head>
<body>
<div class="output">
  <table style="width:300px;">
    <tr>
      <td>window.scrollX</td>
      <td id="scrollX"></td>
    </tr>
    <tr>
      <td>window.scrollY</td>
      <td id="scrollY"></td>
    </tr>
    <tr>
      <td>window.pageXOffset</td>
      <td id="pageXOffset"></td>
    </tr>
    <tr>
      <td>window.pageYOffset</td>
      <td id="pageYOffset"></td>
    </tr>
  </table>
</div>
<div class="content"></div>

<script>
  const scrollX = window.scrollX;
  const scrollY = window.scrollY;
  const pageXOffset = window.pageXOffset;
  const pageYOffset = window.pageYOffset;

  const sxOut = document.getElementById('scrollX');
  const syOut = document.getElementById('scrollY');
  const pxOut = document.getElementById('pageXOffset');
  const pyOut = document.getElementById('pageYOffset');

  //初期状態のスクロール量(0)を表示
  sxOut.textContent = window.scrollX;
  syOut.textContent = window.scrollY;
  pxOut.textContent = window.pageXOffset;
  pyOut.textContent = window.pageYOffset;

  //スクロールイベントを登録して、スクロールしたらその量を表示
  window.addEventListener('scroll', () => {
    sxOut.textContent = window.scrollX;
    syOut.textContent = window.scrollY;
    pxOut.textContent = window.pageXOffset;
    pyOut.textContent = window.pageYOffset;
  });
</script>
</body>

以下はボタンをクリックすると、現在の垂直方向のスクロール位置を取得して表示する例です。

scrollY:

<p>scrollY: <span id="get_scrollY_out"></span></p>
<button id="get_scrollY">scrollY</button>

<script>
document.getElementById('get_scrollY').addEventListener('click', () => {
  document.getElementById('get_scrollY_out').textContent = window.scrollY;
});
</script>

特定の位置までスクロール

以下のメソッドを使ってウィンドウを文書内の特定の位置までスクロールすることができます。

メソッド 説明
window.scroll() window.scroll(x, y)

x, y で指定した座標までスクロール(文書の絶対座標へのスクロール)。

  • x : 左上を基準とした、表示させたい文書の水平軸上のピクセル
  • y : 左上を基準とした、表示させたい文書の垂直軸上のピクセル
window.scroll(options)

以下のプロパティを持つオブジェクト(ScrollToOptions)を指定可能。

  • top :上記書式の y と同じ
  • left :上記書式の x と同じ
  • behavior :smooth、 instant、 auto のいずれか。初期値は auto

※ IE 未対応

window.scrollTo() window.scroll() と同じ(文書の絶対座標へのスクロール)
window.scrollBy(X, Y) window.scrollBy()

現在の位置から指定された量だけスクロール(指定量のスクロール)

  • X:現在の位置から水平方向にスクロールする量(ピクセル)
  • Y:現在の位置から垂直方向にスクロールする量(ピクセル)
scroll() 同様、ScrollToOptions オブジェクトを指定可能。

以下はボタンをクリックすると現在の位置から垂直方向下に 100px スクロールする例です。

<button id="scroll1">スクロール1</button>

<script>
document.getElementById('scroll1').addEventListener('click', () => {
  //現在の位置から下に 100px スクロール
  window.scroll(0, window.scrollY - 100);
  //以下でもほぼ同じ
  //window.scrollBy(0, -100);
});
</script>

以下はボタンをクリックすると現在の位置から垂直方向上に300pxスムーススクロールする例です。

<button class="margin_top40" id="scroll2">スクロール2</button>
<script>
document.getElementById('scroll2').addEventListener('click', () => {
  //現在の位置から上に300pxスクロール
  window.scrollBy({
    top: 300,
    //スムーススクロール
    behavior: 'smooth'
  });
  //以下でも同じ
  /*window.scrollTo({
    top: window.scrollY + 300,
    behavior: 'smooth'
  });*/
});
</script>

指定した要素の位置までスクロール

以下は対象の要素のオフセット座標を取得して、その位置までスクロールする例です。

<button id="scroll2">スクロール2</button>
・・・
<button id="scroll3">スクロール3</button>

<script>
document.getElementById('scroll3').addEventListener('click', () => {
  //対象の要素を取得
  const targetBtn = document.getElementById('scroll2');
  //対象の要素のオフセット座標を取得
  const targetBtnOffsetTop = targetBtn.offsetTop;
  //対象の要素の位置へスクロール
  window.scrollTo({
    top: targetBtnOffsetTop,
    //スムーススクロール
    behavior: 'smooth'
  });
});
</script>

以下は対象の要素のウィンドウ座標を取得して、その位置までスクロールする例です。

以下では対象の要素のウィンドウ座標を取得するために DOMRect オブジェクトを取得し、その top プロパティに pageYOffset を加えてドキュメント座標を指定しています。

<button id="scroll2">スクロール2</button>
・・・
<button id="scroll4">スクロール4</button>

<script>
document.getElementById('scroll4').addEventListener('click', () => {
  //対象の要素を取得
  const targetBtn = document.getElementById('scroll2');
  //対象の要素のウィンドウ座標を取得するために DOMRect オブジェクトを取得
  const targetBtnRect = targetBtn.getBoundingClientRect();
  //対象の要素の位置へスクロール
  window.scrollTo({
    //DOMRect オブジェクトの top プロパティ + pageYOffset
    top: targetBtnRect.top + pageYOffset,
    //スムーススクロール
    behavior: 'smooth'
  });
});
</script>

テキストの操作

textContent

要素のテキストを取得・設定するには Node インターフェイスのプロパティ textContent が使えます。

textContent プロパティは子要素も含めたテキスト部分を取得します。以下のようにインラインスタイル(display: none)で非表示にしている部分も取得されます。

<div id="foo">
  <h3>Title</h3>
  <div><p>This is a sample text.</p></div>
  <p style="display: none;">No Display</p>
</div>

<script>
console.log(document.getElementById('foo').textContent);
/*
以下が取得したテキストの出力結果

Title

    This is a sample text.

  No Display
*/
</script>

textContent プロパティを設定すると、子要素は全て削除され、テキストノードに置き換わります。

<div id="foo">
  <h3>Title</h3>
  <div><p>This is a sample text.</p></div>
  <p style="display: none;">No Display</p>
</div>
<p id="bar">ID: <strong>bar</strong> text</p>

<script>
document.getElementById('foo').textContent = 'New Text Content!';
document.getElementById('bar').textContent = 'New Bar!';
</script>

<!-- 上記 HTML は以下に置き換わります  -->
<div id="foo">New Text Content!</div>
<p id="bar">New Bar!</p>

値に HTML 文字列を指定してもそれらはエスケープされてタグは作成されません。外部からの入力などを出力する場合、XSS 攻撃を防ぐことができます。

<div id="foo"></div>

<script>
document.getElementById('foo').textContent = '<p>Sample</p>';
</script>
<!-- HTML 文字列は以下のようにエスケープされます  -->
<div id="foo">&lt;p&gt;Sample&lt;/p&gt;</div>

但し、引用符(" や ')はエスケープされません。以下はボタンをクリックするとテキストフィールドに入力した値を div 要素に設定しますが、その際に「&」「<」「>」はエスケープされますが、「"」や「'」はエスケープされずそのまま出力されます。

そのため、外部からの入力などを出力する場合は、必要に応じて独自にサニタイズするための関数を作成するなどして引用符もエスケープすることもできす。

<input type="text" id="foo" size="20">
<input type="button" id="bar" value="Click">
<div id="baz"></div>

<script>
//ボタン要素
const bar = document.getElementById('bar');
//div 要素
const baz = document.getElementById('baz');
//input 要素(テキストフィールド)
const foo = document.getElementById('foo');

//ボタンにクリックイベントを登録
bar.addEventListener('click', ()=>{
  //div 要素にテキストフィールドに入力された値を設定
  baz.textContent =  foo.value;
})
</script>

HTMLElement インターフェイスのプロパティ innerText でも要素のテキストを取得(及び設定)することができますが、innerText はスタイルを反映するので「非表示」の要素のテキストを取得しません。

innerText

innerText は HTMLElement のプロパティで、要素内の子要素も含めた「レンダリングされた」テキストを取得(または設定)することができます。

innerText は子要素も含めたテキスト部分を取得しますが、textContent とは異なり、以下のようにスタイル(display: none)で非表示にしている部分は取得されません。

<div id="foo">
  <h3>Title</h3>
  <div>
    <p>This is a sample text.</p>
  </div>
  <p style="display: none;">No Display</p>
</div>
<script>
console.log(document.getElementById('foo').innerText);
/*
以下が取得したテキストの出力結果(display: none の p 要素は取得されない)
Title

This is a sample text.
*/
</script> 

innerText を設定すると、textContent 同様、子要素は全て削除され、テキストノードに置き換わります。

<div id="foo">
  <h3>Title</h3>
  <div><p>This is a sample text.</p></div>
</div>
<p id="bar">ID: <strong>bar</strong> text</p>

<script>
document.getElementById('foo').innerText = 'New Text Content!';
document.getElementById('bar').innerText = 'New Bar!';
</script>

<!-- 上記 HTML は以下に置き換わります  -->
<div id="foo">New Text Content!</div>
<p id="bar">New Bar!</p>

また、textContent 同様、値に HTML 文字列を指定してもそれらはエスケープされてタグは作成されません。

<div id="foo"></div>

<script>
document.getElementById('foo').innerText = '<p>Sample</p>';
</script>

<!-- HTML 文字列は以下のようにエスケープされます  -->
<div id="foo">&lt;p&gt;Sample&lt;/p&gt;</div>
textContent と innerText の違い

取得

以下は textContent と innerText を使ってテキストを取得する場合の違いです。但し、以下の例は div 要素の子要素に style 要素を入れているので、マークアップ的には正しくありません。Nu Html Checker(Markup Validation Service) でチェックすると「Element style not allowed as child of element div in this context.」のようなエラーが表示されます。

id="original" の div 要素を textContent と innerText で取得してテキストエリアの値に設定しています。

textContent は記述されている HTML(改行を含むエディタ上のタグの位置)を反映しますが、innerText はブラウザに表示(レンダリング)される状態を反映します。

innerText はスタイルシートの設定などで非表示になっていたり、エディタ上のホワイトスペースや style タグのように元々ブラウザに表示されない要素のテキストは取得しませんが、br タグがある場合は改行された状態でテキストを取得します。

          <div id="original"><!--  左側にはタブによるスペースあり  -->
            <h3>textContent と innerText</h3>
            <style>
.baz { color: red; } /* エディタの自動整形で左側に寄せられている */
</style>
            <p>Sample text <br><br><!--  br タグを2つ  -->
              with line break.</p>
            <p class="baz">Class baz!</p>
          </div>

          <div>
            <textarea id="foo" rows="7" cols="50" readonly></textarea>
          </div>
          <div>
            <textarea id="bar" rows="7" cols="50" readonly></textarea>
          </div>

<script>
const original = document.getElementById('original');
const foo = document.getElementById('foo');
const bar = document.getElementById('bar');

// id="foo" のテキストエリアの値(value)に textContent で取得した値を設定
foo.value = original.textContent;

// id="bar" のテキストエリアの値(value)に innerText で取得した値を設定
bar.value = original.innerText;
</script>

以下が id="original" の div 要素です。

textContent と innerText

Sample text

with line break.

Class baz!

以下は textContent で取得した値を設定したテキストエリアです。

スタイル要素(<style>)の内容も出力されています。<br> タグの改行は反映されていませんが、HTML の記述の改行やタブなどのホワイトスペースを反映します。

以下は innerText で取得した値を設定したテキストエリアです。

スタイル要素は削除されていて、<br> の改行は反映されています。innerText は、<br> タグによる改行などテキストがレンダリングされる状態を取得しますが、レンダリングには表れない HTML の記述自体の改行やスペースは取得されません。

設定(出力)

以下は textContent と innerText を使って値を設定(出力)する場合の違いを確認するサンプルです。

ボタンをクリックするとテキストエリアに入力された文字を textContent と innerText を使って出力します。textContent を使った出力ではテキストエリアの改行は反映されませんが、innerText を使った出力では改行が反映されます。

textContent

innerText

<textarea id="textArea" cols="30" rows="5"></textarea>
<button type="button" id="btn">Click</button>

<div>
  <p>textContent</p>
  <div id="out1"></div>
</div>
<div>
  <p>innerText</p>
  <div id="out2"></div>
</div>

<script>
  document.getElementById('btn').addEventListener('click', () => {
    //テキストエリアに入力された値
    const textAreaValue = document.getElementById('textArea').value;
    //textContent で出力
    document.getElementById('out1').textContent = textAreaValue;
    //innerText で出力
    document.getElementById('out2').innerText = textAreaValue;
  }, false);
</script> 
innerHTML

Element クラスのプロパティ innerHTML を使うと、その要素の子要素を表す HTML テキスト文字列を取得できます。

また、outerHTML は、要素とその子孫を含む部分の HTML テキスト文字列を取得します。

関連項目:要素をラップ

<div id="foo">
  <h3>Title</h3>
  <p>This is a sample text.</p>
</div>

<script>
console.log(document.getElementById('foo').innerHTML);
console.log(document.getElementById('foo').outerHTML);
/*
//innerHTML(7行目)の出力結果
<h3>Title</h3>
<p>This is a sample text.</p>

//outerHTML(8行目)の出力結果
<div id="foo">
  <h3>Title</h3>
  <p>This is a sample text.</p>
</div>
*/
</script>

innerHTML プロパティに値を設定すると、指定された文字列をパースし、解析結果をその要素の子要素に設定します。insertAdjacentHTML() を使うと指定した文字列(を解析した結果のノード)を指定した位置に挿入することができます。

<div id="foo"></div>

<script>
document.getElementById('foo').innerHTML = '<p><strong>sample</strong></p>';
</script>

<!-- 上記 HTML は以下のようになります  -->
<div id="foo"><p><strong>sample</strong></p></div>

※外部からの入力など制御できない文字列を innerHTML を使って挿入するのはセキュリティリスクが発生するので注意が必要です。外部からの入力などを挿入する場合は innerHTML を使用せず、代わりに textContent を使用するか、適切にエスケープ(サニタイジング)する必要があります。

また、+= 演算子を使って innerHTML プロパティに少しずつテキストを追加していくのは、あまり効率的ではないようです。

HTML を挿入する場合は insertAdjacentHTML() メソッドを使用

要素の内容を置き換えるのではなく、文書に HTML を挿入するという場合には、insertAdjacentHTML() メソッドを使用します。

以下はクリックイベントを設定したボタン要素を持つ div 要素に p 要素の HTML を挿入する例です。

この場合、10行目の右辺の divFoo.innerHTML は新たな HTML 文字列として解釈され、設定したイベントリスナーが取り除かれてしまいます。

<div id="div-foo">
  <button id="hello-btn">Click</button>
</div>

<script>
  const divFoo = document.getElementById('div-foo');
  document.getElementById('hello-btn').addEventListener('click', ()=> {
    console.log('hello');
  });
  // 右辺のもとの HTML(divFoo.innerHTML)で設定したイベントリスナーは取り除かれる
  divFoo.innerHTML = '<p>Greeting</p>' + divFoo.innerHTML;
</script>

以下のように、insertAdjacentHTML() を使って HTML を挿入すれば、ボタンのクリックイベントはそのまま残ります。

const divFoo = document.getElementById('div-foo');
document.getElementById('hello-btn').addEventListener('click', ()=> {
  console.log('hello');
});
// insertAdjacentHTML で HTML を挿入
divFoo.insertAdjacentHTML('afterbegin', '<p>Greeting</p>');

または、以下のように要素を作成して挿入すれば、ボタンのクリックイベントはそのまま残ります。。

const divFoo = document.getElementById('div-foo');
document.getElementById('hello-btn').addEventListener('click', ()=> {
  console.log('hello');
});
// 要素を作成
const greeting = document.createElement('p');
greeting.textContent = 'Greeting';
// 要素を挿入
divFoo.prepend(greeting);
サニタイジング(エスケープ処理)

ユーザなどの外部からの入力を出力する場合、値をそのまま出力してしまうと不正なスクリプトが実行されてしまう(クロスサイトスクリプティング)などセキュリティが脆弱になってしまいます。

そのため外部からの入力を出力する場合は、それらの値をサニタイズ(無害化)する必要があります。

textContentinnerText プロパティ、insertAdjacentHTML()insertAdjacentText()、 createTextNode() を使って文字列をエスケープすることができます。

JavaScript には PHP の htmlspecialchars() のようなサニタイジングを行う組み込み関数が用意されていないので、例えば以下のような独自の関数を定義して必要に応じてサニタイズを行うことができます。

以下の関数 h() は「&」「" 」「'」「<」「>」をエスケープします。

//引数に文字列を受け取り特殊文字を変換する関数 h()
function h(str){
  return str.replace(/&/g, '&amp;')
    .replace(/"/g, '&quot;')
    .replace(/'/g, '&#39;')
    .replace(/</g, '&lt;')
    .replace(/>/g, '&gt;')
}

または、以下でも同様にエスケープします。こちらは replace() を1回呼び出すので少し効率的です。

//引数に文字列を受け取り特殊文字を変換する関数 h()
function h(str){
  return str.replace(/[<>&'"]/g, (match) => {
    const specialChars = {
      "<": "&lt;",
      ">": "&gt;",
      "&": "&amp;",
      "'": "&#39;",
      '"': "&quot;",
    };
    return specialChars[match];
  });
}

以下はテキストフィールドに文字を入力してボタンをクリックすると、入力された文字をエスケープしてテキストエリアと div 要素に表示する例です。

<form>
  <input type="text" id="foo" size="40" placeholder="文字列を入力">
  <input type="button" id="bar" value="Click">
  <div>
    <textarea id="baz" rows="2" cols="50" readonly></textarea>
    <textarea id="baz2" rows="2" cols="50" readonly></textarea>
  </div>
  <div id="qux"></div>
  <input type="reset" value="Reset">
</form>

<script>
  //サニタイジング用関数(上記と同じ)
  function h(str){
    return str.replace(/&/g, '&amp;')
      .replace(/"/g, '&quot;')
      .replace(/'/g, '&#39;')
      .replace(/</g, '&lt;')
      .replace(/>/g, '&gt;')
  }

  //input 要素(テキストフィールド)
  const foo = document.getElementById('foo');
  //ボタン要素
  const bar = document.getElementById('bar');
  //textarea 要素
  const baz = document.getElementById('baz');
  //textarea 要素(2つ目)
  const baz2 = document.getElementById('baz2');
  //div 要素
  const qux = document.getElementById('qux');

  //ボタンにクリックイベントを登録
  bar.addEventListener('click', ()=>{
    //エスケープしてテキストエリアの値(value プロパティ)に設定
    baz.value =  h(foo.value);
    //エスケープしてテキストエリアの innerHTML プロパティに設定
    baz2.innerHTML =  h(foo.value);
    //div 要素のコンテンツに入力された値をエスケープして innerHTML プロパティに設定
    qux.innerHTML =  h(foo.value);
  })
</script>

例えば、以下のテキストフィールドに <form> と入力するとエスケープした値がテキストエリアや div 要素に出力されます。

エスケープされた値は innerHTML プロパティに設定した場合は HTML のエスケープ文字として扱われるので見た目は「<form>」と表示されますが、テキストエリアの value プロパティに設定したものは「&lt;form&gt;」のように表示されます

また、スタイル(padding)の設定により div 要素とテキストエリアでは出力される文字の左側の余白の大きさが異なっています。


実際のフォームの処理などでは、JavaScript でサニタイズや検証を実行しても、ブラウザで JavaScript を無効にすることができるため、必ずサーバー側でもサニタイズや検証を行う必要があります。

要素の作成・追加

要素は createElement() メソッドを使って作成し、必要に応じて属性を設定することができます。

表示するテキストやコンテンツは createTextNode() メソッドや textContent、innerHTML などを使って要素に追加することができます。

要素は生成しただけでは、どこにも関連付けされていない(表示されない)ので appendChild()insertBefore()insertAdjacentElement() などのメソッドを使ってドキュメントに追加します。

以下は a 要素を作成して指定した要素に追加する例です。

<p id="foo"></p>

<script>
//a 要素を生成
var link = document.createElement('a');

//生成した a 要素(link)に属性を設定
link.href = 'http://example.com';
link.target = '_blank';

//テキストノードを生成(表示するテキスト)
var text = document.createTextNode('Link Text');

//要素ノード(a 要素)の直下にテキストノードを追加
link.appendChild(text);

//追加先の対象ノード(id が foo の要素)を取得
var foo = document.getElementById('foo');

//追加先のノードに生成したノードを追加
foo.appendChild(link);
</script>

作成したリンクが指定した要素(id が foo の要素)に追加されます。

<p id="foo"><a href="http://example.com" target="_blank">Link Text</a></p>

関連項目:ノードの作成・追加

要素を作成して追加するには以下のようなメソッドを使うことができます。また、属性の設定は「属性の操作」を参照ください。

Document のメソッド(一部抜粋)
メソッド 説明
createElement() 引数に指定したタグ名で新しい要素を生成して、生成した要素を返します
createTextNode() 引数に指定した文字列のテキストノードを生成します。このメソッドは HTML 文字をエスケープするのに利用できます
Node のメソッド(一部抜粋)
メソッド 説明
appendChild() 対象ノードの最後の子として、指定したノードを追加します
insertBefore() 第1引数で指定したノードを、第2引数で指定したノードの直前に挿入します
cloneNode() ノードを複製します

以下は作成した要素を insertBefore() を使って指定した要素の前に挿入する例です。また、以下の例ではテキストの作成は textContent を使用しています。

<p id="foo">foo</p>

<script>
//p 要素を生成
var p_elem = document.createElement('p');
//p 要素に id を設定
p_elem.id= 'bar';
//p 要素のテキストを設定
p_elem.textContent = 'new paragraph';
//対象となる要素を取得
var target = document.getElementById('foo');
//対象となる要素(target)の前に作成した要素を追加
target.parentNode.insertBefore(p_elem, target);
</script>

以下のように作成した要素が挿入されます。

<p id="bar">new paragraph</p>
<p id="foo">foo</p>

以下は生成した要素に innerHTML を使って子要素を追加する例です。

※ 但し、外部からの入力など制御できない文字列を innerHTML を使って挿入するのはセキュリティリスクが発生するので注意が必要です。

<div id="foo"></div>

<script>
//div 要素の生成
var div = document.createElement('div');
//生成した div 要素にクラス属性を設定
div.className = 'sample';
//innerHTML で子要素を追加
div.innerHTML = '<p class="bar"><a href="#">top</a></p>';
//追加先のノードに生成した要素を追加
document.getElementById('foo').appendChild(div);
</script>

以下のように作成した要素が追加されます。

<div id="foo">
  <div class="sample">
    <p class="bar"><a href="#">top</a></p>
  </div>
</div>
appendChild

appendChild() を使うと指定した要素をその要素の子要素(の末尾)に追加することができます。

追加する要素が既に存在している場合、その要素は現在の親ノードから削除され、新しい親ノードに自動的に追加(移動)されます。既存の要素を複数箇所に追加するには cloneNode() メソッドを使って要素を複製することができます。

以下が appendChild() の書式です。

let aChild = element.appendChild(aChild);
element
対象の要素(追加する要素の親要素)
aChild
追加する要素(親要素に追加する子要素)
戻り値
追加した子要素を返します

以下では新たに p 要素とテキストノードを生成し、appendChild() で p 要素にテキストノードを追加しています。appendChild() は親要素の最後の子要素として追加しますが、親要素が子要素を持たない場合は、最初の子要素として追加されます。

その後、作成した p 要素を appendChild() で id="foo" の div 要素内の末尾に追加しています。

<div class="content">
  <div id="foo">
    <p>foo の先輩</p>
  </div>
</div>

<script>
//p 要素を生成
const newElem = document.createElement('p');
//テキストノードを生成
var newText = document.createTextNode("新しい p 要素");
//p 要素にテキストノードを追加
newElem.appendChild(newText);
//p 要素に id を設定
newElem.setAttribute("id","new");

//作成した p 要素の追加先の要素を取得
const foo = document.getElementById('foo');
//取得した要素に p 要素を追加
foo.appendChild(newElem);

</script>

上記の結果は以下のようになります。

<div class="content">
  <div id="foo">
    <p>foo の先輩</p>
    <p id="new">新しい p 要素</p>
  </div>
</div>

以下のように parentNode を使用すると、id="foo" の div 要素の後に追加することができます。

const newElem = document.createElement('p');
var newText = document.createTextNode("新しい p 要素");
newElem.appendChild(newText);
newElem.setAttribute("id","new");

const foo = document.getElementById('foo');
// foo の親要素の末尾に追加
foo.parentNode.appendChild(newElem);

上記の結果は以下のようになります。

<div class="content">
  <div id="foo">
    <p>foo の先輩</p>
  </div>
  <p id="new">新しい p 要素</p>
</div>
insertBefore

insertBefore() はある要素の前に新たな要素を追加するメソッドで、親となる要素で実行してその直下の子要素のいずれかを指定し、その直前に新たな要素を追加します。

appendChild() 同様、追加する要素が既に存在している場合、その要素は現在の親ノードから削除され、新しい親ノードに自動的に追加(移動)されます。既存の要素を複数箇所に追加するには cloneNode() メソッドを使って要素を複製することができます。

以下が insertBefore() の書式です。

第1パラメータで指定した要素(newNode)を現在の要素(parentNode)の子要素として、第2パラメータで指定した要素(referenceNode:現在の要素の子要素)の前に挿入します。

let insertedNode = parentNode.insertBefore(newNode, referenceNode)
parentNode
現在の要素(挿入する要素の親要素)
newNode
挿入される要素
referenceNode
基準となる要素。newNode がこの要素の直前に挿入されます。null を指定した場合は、newNode は parentNode の子要素の末尾に挿入されます。
戻り値
挿入された子要素を返します

以下は新しい p 要素(newElem)を作成して、id="parent" の要素の子要素(id="child1" の p 要素)の前に挿入する例です。insertAdjacentElement() を使っても同様のことができます。

<div class="content">
  <div id="parent">
    <p id="child1">child 1</p>
    <p id="child2">child 2</p>
  </div>
</div>

<script>
//挿入する p 要素を作成
const newElem = document.createElement('p');
var newText = document.createTextNode("新しい p 要素");
newElem.appendChild(newText);
newElem.setAttribute("id","new");

//insertBefore() を実行する要素(挿入する要素の親要素)
const parent = document.getElementById('parent');
//parent の最初の子要素
const child1 = document.getElementById('child1');
//parent の2番目の子要素
const child2 = document.getElementById('child2');

//child1 の前に作成した要素を挿入
parent.insertBefore(newElem, child1);

/*insertAdjacentElement() を使う場合
parent.insertAdjacentElement('afterbegin', newElem);
child1.insertAdjacentElement('beforebegin', newElem);*/
</script>

以下のような結果になります。

<div class="content">
  <div id="parent">
    <p id="new">新しい p 要素</p><!-- 挿入された要素 -->
    <p id="child1">child 1</p>
    <p id="child2">child 2</p>
  </div>
</div>

id="child2" の p 要素の前に挿入するには以下のようになります。

parent.insertBefore(newElem, child2);

/*insertAdjacentElement() を使う場合
child1.insertAdjacentElement('afterend', newElem);
child2.insertAdjacentElement('beforebegin', newElem);*/
結果
<div class="content">
  <div id="parent">
    <p id="child1">child 1</p>
    <p id="new">新しい p 要素</p><!-- 挿入された要素 -->
    <p id="child2">child 2</p>
  </div>
</div>

parent の子要素の末尾(この例では id="child2" の p 要素 の後)に挿入するには以下のように基準となる要素に null を指定します。

parent.insertBefore(newElem, null);

/*insertAdjacentElement() を使う場合
parent.insertAdjacentElement('beforeend', newElem);*/
結果
<div class="content">
  <div id="parent">
    <p id="child1">child 1</p>
    <p id="child2">child 2</p>
    <p id="new">新しい p 要素</p><!-- 挿入された要素 -->
  </div>
</div>

現在の要素(自分)の前に挿入

現在の要素の前に挿入するには、現在の要素の親要素を参照するように parentNode を使い、基準の要素に自身を指定します。

<div class="content">
  <div id="parent">
    <p id="child1">child 1</p>
    <p id="child2">child 2</p>
  </div>
</div>

<script>
//挿入する p 要素を作成
const newElem = document.createElement('p');
var newText = document.createTextNode("新しい p 要素");
newElem.appendChild(newText);
newElem.setAttribute("id","new");

const parent = document.getElementById('parent');
//parentNode で現在の要素の親要素を参照して、自分を基準に指定
parent.parentNode.insertBefore(newElem, parent);

//この例の場合、以下でも同じ
document.querySelector('.content').insertBefore(newElem, parent);

/*insertAdjacentElement() を使う場合
parent.insertAdjacentElement('beforebegin', newElem);*/
</script> 
結果
<div class="content">
  <p id="new">新しい p 要素</p><!-- 挿入された要素 -->
  <div id="parent">
    <p id="child1">child 1</p>
    <p id="child2">child 2</p>
  </div>
</div>

指定した要素の後に挿入(nextSibling)

JavaScript には insertAfter() というメソッドはないので、指定した要素の後に挿入するには、基準となる要素で Node インターフェイスのプロパティ nextSibling を使います。

以下は、id="child1" の要素の後に新しい要素を挿入する例です。この場合、child1.nextSibling は id="child2" の p 要素になり、その前に挿入するので、結果的に child1 の後に挿入することになります。

<div class="content">
  <div id="parent">
    <p id="child1">child 1</p>
    <p id="child2">child 2</p>
  </div>
</div>

<script>
//挿入する p 要素を作成
const newElem = document.createElement('p');
var newText = document.createTextNode("新しい p 要素");
newElem.appendChild(newText);
newElem.setAttribute("id","new");

const parent = document.getElementById('parent');
const child1 = document.getElementById('child1');

//child1 の後に作成した要素を挿入
parent.insertBefore(newElem, child1.nextSibling);

/*insertAdjacentElement() を使う場合
child1.insertAdjacentElement('afterend', newElem);*/
</script> 
結果
<div class="content">
  <div id="parent">
    <p id="child1">child 1</p>
    <p id="new">新しい p 要素</p><!-- 挿入された要素 -->
    <p id="child2">child 2</p><!-- child1.nextSibling -->
  </div>
</div>

基準となる要素を child2.nextSibling とすると child2 には次の兄弟要素はありませんが child2.nextSibling は null を返すので、newElem は子要素の末尾 (child2 の直後) に挿入されます。

最初の子要素として挿入(firstChild)

要素を最初の子要素の前に挿入するには、 Node インターフェイスのプロパティ firstChild を利用します。基準となる要素を「親要素.firstChild」とすることで、親要素の最初の子要素の前に(最初の子要素として)挿入することができます。

以下は基準となる要素を parent.firstChild として、現在の要素の最初の子要素として挿入する例です。

<div class="content">
  <div id="parent">
    <p id="child1">child 1</p>
    <p id="child2">child 2</p>
  </div>
</div>

<script>
//挿入する p 要素を作成
const newElem = document.createElement('p');
var newText = document.createTextNode("新しい p 要素");
newElem.appendChild(newText);
newElem.setAttribute("id","new");

//insertBefore() を実行する要素(挿入する要素の親要素)
const parent = document.getElementById('parent');

//親要素の最初の子要素の前に挿入
parent.insertBefore(newElem, parent.firstChild);

/*insertAdjacentElement() を使う場合
parent.insertAdjacentElement('afterbegin', newElem);*/
</script> 
結果
<div class="content">
  <div id="parent">
    <p id="new">新しい p 要素</p>
    <p id="child1">child 1</p>
    <p id="child2">child 2</p>
  </div>
</div>

この例の場合、最初の例のように、基準となる要素を id="child1" の要素を指定しても同じ結果になりますが、「親要素.firstChild」とすることで親要素に子要素がない場合など、動的に構成が変化する場合でも機能します。

親要素に最初の子要素がない場合、 firstChild は null になり、null の場合は親の最後の子要素の後に追加されます。実際は親要素が最初の子要素を持っていないということは最後の子要素もないので、挿入された要素は唯一の要素になります。

append、prepend、after、before

最新のブラウザでは jQuery のようなメソッド append() や prepend()、after()、before() が使えます。

https://caniuse.com/?search=prepend

メソッド 説明
append() その要素の子要素の末尾に指定した要素(ノード)またはテキストを挿入します。 テキストはテキストノードとして挿入されます。
prepend() その要素の子要素の先頭に指定した要素(ノード)またはテキストを挿入します。 テキストはテキストノードとして挿入されます。
after() その要素の後(親要素から見た子要素の末尾)に指定した要素(ノード)またはテキストを挿入します。 テキストはテキストノードとして挿入されます。
before() その要素の前(親要素から見た子要素の先頭)に指定した要素(ノード)またはテキストを挿入します。 テキストはテキストノードとして挿入されます。

appendChild() と append() の違い

  • append() はテキスト(DOMString オブジェクト)を追加することもできますが、appendChild() は要素(Node オブジェクト)のみを追加します。
  • append() には戻り値がありませんが、appendChild() は追加された子要素(Node オブジェクト)を返します。
  • append() は複数の要素(ノード)と文字列を追加できますが、appendChild() は1つの要素(ノード)しか追加できません。

append()

<div id="wrapper">
  <div id="target">
    <p id="foo">foo</p>
    <p id="bar">bar</p>
    <p id="baz">baz</p>
  </div>
</div>
<script>
//id が target の要素(対象の要素)
const target = document.getElementById('target');

//p 要素を生成
const appendElem = document.createElement('p');
//生成した p 要素にクラス属性を設定
appendElem.setAttribute('class', 'append');
//生成した p 要素にテキストを設定
appendElem.append('this will be appended to the target children');

//対象の要素の子要素の末尾に生成した要素を挿入
target.append(appendElem);
</script>
append() の実行結果
<div id="wrapper">
  <div id="target">
    <p id="foo">foo</p>
    <p id="bar">bar</p>
    <p id="baz">baz</p>
    <p class="append">this will be appended to the target children</p>
  </div>
</div>

prepend()

<div id="wrapper">
  <div id="target">
    <p id="foo">foo</p>
    <p id="bar">bar</p>
    <p id="baz">baz</p>
  </div>
</div>
<script>
//id が target の要素(対象の要素)
const target = document.getElementById('target');

//p 要素を生成
const prependElem = document.createElement('p');
//生成した p 要素にクラス属性を設定
prependElem.setAttribute('class', 'prepend');
//生成した p 要素にテキストを設定
prependElem.append('this will be prepende to the target childrend');

//対象の要素の子要素の末尾に生成した要素を挿入
target.prepend(prependElem);
</script>
prepend() の実行結果
<div id="wrapper">
  <div id="target">
    <p class="prepend">this will be prepended to the target children</p>
    <p id="foo">foo</p>
    <p id="bar">bar</p>
    <p id="baz">baz</p>
  </div>
</div>

after()

<div id="wrapper">
  <div id="target">
    <p id="foo">foo</p>
    <p id="bar">bar</p>
    <p id="baz">baz</p>
  </div>
</div>
<script>
//id が target の要素(対象の要素)
const target = document.getElementById('target');
//p 要素を生成
const afterElem = document.createElement('p');
//生成した p 要素にクラス属性を設定
afterElem.setAttribute('class', 'after');
//生成した p 要素にテキストを設定
afterElem.append('this will be insereted after the target');

//対象の要素の後(親要素 wrapper の子要素の末尾)に生成した要素を挿入
target.after(afterElem);
</script>
after() の実行結果
<div id="wrapper">
  <div id="target">
    <p id="foo">foo</p>
    <p id="bar">bar</p>
    <p id="baz">baz</p>
  </div>
  <p class="after">this will be insereted after the target</p>
</div>

before()

<div id="wrapper">
  <div id="target">
    <p id="foo">foo</p>
    <p id="bar">bar</p>
    <p id="baz">baz</p>
  </div>
</div>
<script>
//id が target の要素(対象の要素)
const target = document.getElementById('target');
//p 要素を生成
const beforeElem = document.createElement('p');
//生成した p 要素にクラス属性を設定
beforeElem.setAttribute('class', 'before');
//生成した p 要素にテキストを設定
beforeElem.append('this will be insereted before the target');

//対象の要素の前(親要素 wrapper の子要素の先頭)に生成した要素を挿入
target.before(beforeElem);
</script>
before() の実行結果
<div id="wrapper">
  <p class="before">this will be insereted before the target</p>
  <div id="target">
    <p id="foo">foo</p>
    <p id="bar">bar</p>
    <p id="baz">baz</p>
  </div>
</div>
insertAdjacentHTML

insertAdjacentHTML() は指定した HTML や XML の文字列をパースしてその解析結果のノードを、指定した位置に挿入する Element インターフェースのメソッドです。

insertAdjacentHTML() による操作は、innerHTML での代入による操作よりも動作が高速とのことです(操作方法によってはあまり違いがない場合もあるようです)。

innerHTML は文字列をパースしてその解析結果をその要素の子要素に設定するプロパティです。

※ また、innerHTML 同様、外部からの入力など制御できない文字列を insertAdjacentHTML() を使って挿入するのはセキュリティリスクが発生するので注意が必要です。insertAdjacentText() を使えばタグなどの文字はエスケープされます。

以下が insertAdjacentHTML() の書式とパラメータです。

targetElement.insertAdjacentHTML(position, text);
targetElement
対象の要素(メソッドを実行する要素オブジェクト)
text
HTML または XML として解析し DOM ツリーに挿入することが可能な文字列
position
指定可能な値(文字列) 意味
beforebegin targetElement の直前に挿入
afterbegin targetElement 内部の最初の子要素の前に挿入
beforeend targetElement 内部の最後の子要素の後に挿入
afterend targetElement の直後に挿入

※ beforebegin および afterend が使えるのは、ノード(実行する要素)がツリー内にあり、かつ親要素が存在する場合です。

例えば、以下のような HTML があり、

<div id="foo"> <!-- 対象の要素の開始タグ -->
  <p>対象の要素内の既存の p 要素</p>
</div> <!-- 対象の要素の閉じタグ -->

<style>
  #foo { border: 1px solid #999; padding: 10px; }
  .red { color: red;}
  .blue { color: blue;}
  .orange { color: orange;}
  .green { color: green;}
</style>

対象の要素内の既存の p 要素

id 属性の値が foo の div 要素に以下を実行すると、

const foo = document.getElementById('foo');

foo.insertAdjacentHTML('beforebegin', '<p class="red">BB</p>');
foo.insertAdjacentHTML('afterbegin', '<p class="blue">AB</p>');
foo.insertAdjacentHTML('beforeend', '<p class="orange">BE</p>');
foo.insertAdjacentHTML('afterend', '<p class="green">AE</p>');

以下のような位置に HTML が挿入され、

<p class="red">BB</p>; <!-- beforebegin で挿入-->
<div id="foo">; <!-- 対象の要素の開始タグ -->
  <p class="blue">AB</p> <!-- afterbegin で挿入 -->
  <p>対象の要素内の既存の p 要素</p>
  <p class="orange">BE</p> <!-- beforeend で挿入 -->
</div> <!-- 対象の要素の閉じタグ -->
<p class="green">AE</p> <!-- afterend で挿入 -->

以下のような結果になります。

対象の要素内の既存の p 要素

insertAdjacentText

insertAdjacentText() は指定した文字列を、メソッドを実行した要素を基準とした相対的な位置にテキストノードとして挿入する Element インターフェースのメソッドです。

※タグなどの特殊文字(& < >)を含む文字列を指定すると、それらはエスケープされて挿入されます。

以下が insertAdjacentText() の書式とパラメータです。

targetElement.insertAdjacentText(position, text);
targetElement
対象の要素(メソッドを実行する要素オブジェクト)
text
挿入する文字列
position
指定可能な値(文字列) 意味
beforebegin targetElement の直前に挿入
afterbegin targetElement 内部の先頭(最初の子要素の前)に挿入
beforeend targetElement 内部の末尾(最後の子要素の後)に挿入
afterend targetElement の直後に挿入

※ beforebegin および afterend が使えるのは、ノード(実行する要素)がツリー内にあり、かつ親要素が存在する場合です。

例えば、以下のような HTML があり、

<p id="bar">既存のテキスト</p>

<style>
   #bar { background-color: #D1F8E0;}
</style>

既存のテキスト

id 属性の値が bar の p 要素に以下を実行すると、

const bar = document.getElementById('bar');

bar.insertAdjacentText('beforebegin', '[BB]');
bar.insertAdjacentText('afterbegin', '[AB]');
bar.insertAdjacentText('beforeend', '[BE]');
bar.insertAdjacentText('afterend', '[AE]');

以下のような位置にテキストが挿入され、

[BB]<p id="bar">[AB]既存のテキスト[BE]</p>[AE]

以下のような結果になります。

既存のテキスト

特殊文字(& < >)はエスケープされて挿入されます。

const bar = document.getElementById('bar');
bar.insertAdjacentText('afterend', '<p>タグと特殊文字:&""</p>'); 

以下のように特殊文字は変換されて挿入されます(クォートは変換されません)。

<p id="bar">既存のテキスト</p>&lt;p&gt;タグと特殊文字:&amp;""&lt;/p&gt;
insertAdjacentElement

insertAdjacentElement() は指定した要素を、指定した位置に挿入する Element インターフェースのメソッドです。

appendChild()insertBefore() と同様、挿入する要素が既に存在している場合、その要素は現在の親ノードから削除され、新しい親ノードに自動的に追加(移動)されます。

insertAdjacentHTML() は第2パラメータに指定した HTML 文字列をパースして使用するのに対して、insertAdjacentElement() は HTML 要素を事前に用意して第2パラメータに指定します。

以下が insertAdjacentElement() の書式とパラメータです。

targetElement.insertAdjacentElement(position, element);
targetElement
対象の要素(メソッドを実行する要素オブジェクト)
element
挿入する HTML 要素
position
指定可能な値(文字列) 意味
beforebegin targetElement の直前に挿入
afterbegin targetElement 内部の最初の子要素の前に挿入
beforeend targetElement 内部の最後の子要素の後に挿入
afterend targetElement の直後に挿入

※ beforebegin および afterend が使えるのは、ノード(実行する要素)がツリー内にあり、かつ親要素が存在する場合です。

以下は新しい p 要素を生成し、その要素を対象の要素(id="target" の div 要素)の直前(beforebegin)に挿入する例です。

<div class="content">
  <div id="target">
    <p id="foo">foo</p>
    <p id="bar">bar</p>
  </div>
</div>

<script>
//挿入する p 要素を生成
const baz = document.createElement('p');
//上記 p 要素に設定するテキストを作成
const bazText = document.createTextNode("baz 参上");
//p 要素にテキストを設定
baz.appendChild(bazText);
//p 要素に id 属性を設定
baz.setAttribute("id","baz");

//対象の要素を取得(id="target" の div 要素)
const target = document.getElementById('target');

//対象の要素の直前に挿入
target.insertAdjacentElement('beforebegin', baz);
</script> 
結果
<div class="content">
  <p id="baz">baz 参上</p>
  <div id="target">
    <p id="foo">foo</p>
    <p id="bar">bar</p>
  </div>
</div>
afterbegin
<div class="content">
  <div id="target">
    <p id="foo">foo</p>
    <p id="bar">bar</p>
  </div>
</div>

<script>
・・・中略・・・
//対象の要素の最初の子要素(id="foo")の前に挿入
target.insertAdjacentElement('afterbegin', baz);
</script>

<!-- 結果 -->
<div class="content">
  <div id="target">
    <p id="baz">baz 参上</p>
    <p id="foo">foo</p>
    <p id="bar">bar</p>
  </div>
</div>
beforeend
<div class="content">
  <div id="target">
    <p id="foo">foo</p>
    <p id="bar">bar</p>
  </div>
</div>

<script>
・・・中略・・・
//対象の要素の最後の子要素(id="bar")の後に挿入
target.insertAdjacentElement('beforeend', baz);
</script>

<!-- 結果 -->
<div class="content">
  <div id="target">
    <p id="foo">foo</p>
    <p id="bar">bar</p>
    <p id="baz">baz 参上</p>
  </div>
</div>
afterend
<div class="content">
  <div id="target">
    <p id="foo">foo</p>
    <p id="bar">bar</p>
  </div>
</div>

<script>
・・・中略・・・
//対象の要素の直後に挿入
target.insertAdjacentElement('afterend', baz);
</script>

<!-- 結果 -->
<div class="content">
  <div id="target">
    <p id="foo">foo</p>
    <p id="bar">bar</p>
  </div>
  <p id="baz">baz 参上</p>
</div>

以下は id="foo" の要素の後に生成した p 要素を挿入する例です。この場合、対象の要素を id="foo" の要素としています。

<div class="content">
  <div id="target">
    <p id="foo">foo</p>
    <p id="bar">bar</p>
  </div>
</div>

<script>
・・・中略・・・
//対象の要素(id="foo" の要素)を取得
const foo = document.getElementById('foo');

//対象の要素(id="foo" の要素)の直後に挿入
foo.insertAdjacentElement('afterend', baz);
</script>

<!-- 結果 -->
<div class="content">
  <div id="target">
    <p id="foo">foo</p>
    <p id="baz">baz 参上</p>
    <p id="bar">bar</p>
  </div>
</div> 

以下は上記と同じことを insertBefore() を使って行う例です。

・・・中略・・・

//id="target" の要素を取得
const target = document.getElementById('target');
//id="foo" の要素を取得
const foo = document.getElementById('foo');
//insertBefore() の基準となる要素(第2パラメータ)に foo の次の兄弟要素を指定
target.insertBefore(baz, foo.nextSibling);

//以下でも同じ結果
//id="bar" の要素を取得
const bar = document.getElementById('bar');
//現在の要素の親要素を参照して insertBefore() を実行
bar.parentNode.insertBefore(baz, bar);  

以下はラジオボタンで選択した位置に、そのボタンのラベルの文字列を含むテキストを設定した要素を生成して、insertAdjacentElement() で挿入する例です。

foo

bar

以下は上記の HTML と CSS です。

<div id="positions">
  <input type="radio" name="insertPosition" value="beforebegin" checked>beforebegin
  <input type="radio" name="insertPosition" value="afterbegin">afterbegin
  <input type="radio" name="insertPosition" value="beforeend">beforeend
  <input type="radio" name="insertPosition" value="afterend">afterend
</div>
<button type="button" id="insertBtn">Insert</button>
<button type="button" id="remove">Remove</button>

<div class="content">
  <div id="target">
    <p id="foo">foo</p>
    <p id="bar">bar</p>
  </div>
</div>
#target {
  border: 1px solid #999;
  padding: 20px;
  width: 200px;
}
.sample {
  width: 150px;
}

以下が上記のスクリプトです。

//対象の要素
const target = document.getElementById('target');
//ラジオボタンを囲む div 要素(ラジオボタンの親要素)
const positions = document.getElementById('positions');
// insertAdjacentElement() の第1パラメータ及び挿入する要素に設定する文字列に使う変数
let position = 'beforebegin';
//要素を挿入するボタン
const insertBtn = document.getElementById('insertBtn');
//挿入した要素を削除するボタン
const removeBtn = document.getElementById('remove');

//div 要素(ラジオボタンの親要素)に change イベントのリスナーを登録
positions.addEventListener('change', (e) => {
  //発生したイベントの要素がラジオボタンであれば
  if( e.target.type === 'radio' ) {
    //ラジオボタンの value 属性の値を変数 position に代入
    position = e.target.value;
  }
});

//要素を挿入するボタンにイベントリスナを登録
insertBtn.addEventListener('click', () => {
  //挿入する p 要素を生成
  const baz = document.createElement('p');
  //生成した要素にスタイルとクラスを設定
  baz.style.backgroundColor = 'yellow';
  baz.setAttribute('class', 'sample');
  //p 要素のテキストに baz : と選択された position の値を設定
  baz.textContent = 'baz :' + position;
  //対象の要素が存在すれば(一応確認)
  if (target) {
    //対象の要素に生成した要素を挿入(位置は変数 position に入っている)
    target.insertAdjacentElement(position, baz);
  }
});

//挿入された要素を削除するボタンにイベントリスナを登録
removeBtn.addEventListener('click', () => {
  //挿入した要素にはクラス .sample が付与されているので該当する要素をすべて取得
  const insertedElems = document.querySelectorAll('p.sample');
  // NodeList のメソッド forEach() を使って挿入されたすべての要素を削除
  insertedElems.forEach((elem) => {
    elem.remove();
  });
});
cloneNode() ノードをコピー

cloneNode() は現在のノードを複製して返す Node のメソッドです。

ノードを複製すると、そのノードのすべての属性とその値がコピーされます。

cloneNode によって返される複製ノードは、何らかの方法で他のノードに追加されるまではドキュメントの一部ではありませんし、親ノードを持ちません。

以下が cloneNode() の書式とパラメータです。

var dupNode = node.cloneNode(deep);
node
複製するノード
deep
node の子孫ノードも複製する場合は true を指定。 false を指定した場合は node のみを複製し、子孫ノードは複製されず、ノードに含まれる Text ノードも複製されません。省略した場合は false。
戻り値(dupNode)
生成される node の複製

以下は id が foo の要素を子孫ノードも含めて複製して、id が bar の要素に追加しています。

複製した要素(foo_clone)は id が foo になっているので(同じ id が重複してしまうため)、id 属性を変更してから要素に追加しています。

<div id="foo">
  <p>Hello !</p>
</div>

<div id="bar"></div>

<script>
//複製するノードを取得
const foo = document.getElementById('foo');

//取得したノードを複製
let foo_clone = foo.cloneNode(true);

//複製したノードの id 属性を変更
foo_clone.setAttribute('id', 'foo_clone');

//複製したノードを id が bar の要素に追加
document.getElementById('bar').appendChild(foo_clone);
</script>

以下のような結果になります。

<div id="foo">
  <p>Hello !</p>
</div>

<div id="bar">
  <div id="foo_clone">
    <p>Hello !</p>
  </div>
</div>

以下は複製する際にパラメータを省略してノードのみ(外側の要素のみ)を複製する例です。

複製した要素(foo_clone)には insertAdjacentHTML() を使って p 要素を設定しています。

<div id="foo">
  <p>Hello !</p>
</div>

<div id="bar"></div>

<script>
//複製するノードを取得
const foo = document.getElementById('foo');

//取得したノードのみを複製(引数を省略するか false を指定すると子孫ノードを複製しない)
let foo_clone = foo.cloneNode();

//複製したノードの id 属性を変更
foo_clone.setAttribute('id', 'foo_clone');

//p 要素を foo_clone に挿入
foo_clone.insertAdjacentHTML('afterbegin', '<p>Hello from Clone!</p>');

//複製したノードを id が bar の要素に追加
document.getElementById('bar').appendChild(foo_clone);
</script>

<!-- 結果 -->
<div id="foo">
  <p>Hello !</p>
</div>

<div id="bar">
  <div id="foo_clone">
    <p>Hello from Clone!</p>
  </div>
</div>
要素をラップ

特定の要素を何らかの要素でラップするには、outerHTML を使うのが簡単です。

以下は id が wrap-target の要素を div.foo でラップする例です。

<p id="wrap-target">ラップされる要素</p>

<script>
  const targetElem = document.querySelector('#wrap-target');
  targetElem.outerHTML = '<div class="foo">' + targetElem.outerHTML + '</div>';
</script>

注意点

但し、上記の outerHTML を使う方法は、簡単ですが注意が必要です。

例えば、以下はイベントを設定したボタンを div 要素でラップする例ですが、div 要素でラップする前はボタンをクリックするとコンソールに hello! と出力されますが、outerHTML を使ってラップすると、クリックしても何も出力されなくなくなります。

<button id="wrap-target">ラップされるボタン</button>

<script>
  const targetElem = document.querySelector('#wrap-target');
  targetElem.addEventListener('click', ()=> { console.log('hello!'); });
  // outerHTML でラップするとイベントが発生しなくなる
  targetElem.outerHTML = '<div class="foo">' + targetElem.outerHTML + '</div>';
</script>

これは、outerHTML の値を設定すると、その要素とそのすべての子孫を、指定された htmlString を解釈して構築された新しい DOM ツリーで置き換えられるためです(MDN より)。

少し面倒ですが、例えば、以下のようにラップする要素を作成して、まず insertAdjacentElement などで DOM(対象の要素の前)に挿入し、挿入した要素に対象の要素を appendChild などで追加すれば、イベントは残ります。

<button id="wrap-target">ラップされるボタン</button>

<script>
  const targetElem = document.querySelector('#wrap-target');
  targetElem.addEventListener('click', ()=> { console.log('hello!'); });
  // 要素を作成
  const wrapper = document.createElement('div');
  wrapper.classList.add('foo');
  // 作成した要素を DOM に挿入
  targetElem.insertAdjacentElement('beforebegin', wrapper);
  // 作成した要素の子要素に対象の要素を追加(移動)
  wrapper.appendChild(targetElem);
</script>

以下は insertAdjacentElement の代わりに insertBefore を使う例です。

appendChild は追加しようとしたノードが既に存在していた場合は、その子ノードを現在の位置から新しい位置へ移動するので、11行目の removeChild() は不要です。

insertAdjacentElement の beforebegin と insertBefore の違いは、insertBefore の場合、その親ノードが必要ということになります。

<button id="wrap-target">ラップされるボタン</button>

<script>
  const targetElem = document.querySelector('#wrap-target');
  targetElem.addEventListener('click', ()=> { console.log('hello!'); });
  // 要素を作成
  const wrapper = document.createElement('div');
  wrapper.classList.add('foo');
  // 親ノードの insertBefor で作成した要素を DOM に挿入
  targetElem.parentNode.insertBefore(wrapper, targetElem);
  // targetElem.parentNode.removeChild(targetElem); // 省略可能
  // 作成した要素の子要素に対象の要素を追加(移動)
  wrapper.appendChild(targetElem);
</script>

要素の置換・削除

ノードを置換するには、replaceChild() メソッドを、ノードを削除するには innerHTML プロパティや removeChild() メソッドを使用できます。

Node のメソッド(一部抜粋)
メソッド 説明
replaceChild() 第1引数に置き換える新しいノードを、第2引数に置き換えられる既存ノードを指定してノードを置換。置換対象のノードは、現在のノードの子ノードでなければなりません(置換対象の親ノードでこのメソッドを呼び出します)
removeChild() 引数に指定した子ノードを削除。削除対象のノードは、現在のノードの子ノードでなければなりません(削除対象の親ノードでこのメソッドを呼び出します)

要素自身を削除するには ChildNode のメソッド remove() を使用できます(IE 未対応)。

Node のメソッド(一部抜粋)
メソッド 説明
remove() 要素自身を削除します。その要素が子孫要素を持つ場合は、子孫要素も含めて削除します。

以下は生成した p 要素で既存の p 要素を置換する例です。replaceChild() は置換対象の親ノードで実行します。

<div id="foo">
  <p>Old text</p>
</div>

<script>
//p 要素の生成
var new_elem = document.createElement('p');
//生成した要素に class 属性 'bar' を付与
new_elem.setAttribute('class', 'bar');
//テキストノードを生成して作成した要素に追加
new_elem.appendChild(document.createTextNode('New text'));
//置換対象の親ノードを取得
var parent_node = document.getElementById('foo');
//置換対象のノード (#foo の最初の子要素)
var old_elem = parent_node.firstElementChild;
//既存の p 要素を生成した要素に置換
parent_node.replaceChild(new_elem, old_elem);
</script> 

以下のように作成した要素で置換されます。

<div id="foo">
  <p class="bar">New text</p>
</div>

上記の場合、以下でも同じことができます。

document.getElementById('foo').innerHTML = '<p class="bar">New text</p>';

要素(ノード)の削除

全ての子ノードを削除する簡単な方法は innerHTML に空文字列(または null)を指定します。

<p id="foo"><a href="http://example.com">Link</a></p>

<script>
document.getElementById('foo').innerHTML = '';
</script>

以下が実行結果です。

<p id="foo"></p>

removeChild() メソッドを使用して子ノードを削除することもできます。

<p><a id="foo" href="http://example.com">Link</a></p>

<script>
// 削除対象のノード
var target = document.getElementById("foo");
//削除対象の親ノード(target.parentNode)で removeChild() を呼び出して削除
target.parentNode.removeChild(target);
</script>

removeChild() は削除した子ノードへの参照を返すので、変数に保存(代入)しておけば、後でコード中で再利用することができます。

以下は削除した要素で置換する例です。

<p><a id="foo" href="http://example.com">Link</a></p>
<p id="bar">Text</p>

<script>
// 削除対象のノード
var target = document.getElementById('foo');
//削除したノードを変数に代入(保存)
var removed = target.parentNode.removeChild(target);
//置換対象の親ノードを取得
var parent_node = document.getElementById('bar');
//既存のテキスト(parent_node.firstChild) を削除した要素で置換
parent_node.replaceChild(removed, parent_node.firstChild);
</script> 

以下が実行結果です。

<p></p>
<p id="bar"><a id="foo" href="http://example.com">Link</a></p>

要素自身の削除

以下は、id 属性の値が bar の p 要素を取得して、その要素自身を削除する例です(IE 未対応)。

<div id="foo">
  <p id="bar">bar</p>
</div>

<script>
//id が bar の p 要素を取得
const bar = document.getElementById('bar');
//要素自身を削除
bar.remove();
</script> 

以下が実行結果です。

<div id="foo"></div>

parentNode で親要素を取得して removeChild() を使って、要素自身を削除することもできます。

//id が bar の p 要素を取得
const bar = document.getElementById('bar');

//取得した要素の親要素から自身を削除
bar.parentNode.removeChild(bar);

何番目の要素か(インデックス)を取得

その要素が、集まり(NodeList や HTMLCollection)の中で何番目なのかを調べるには、NodeListHTMLCollection配列に変換して、配列のメソッドでインデックス(位置)を取得することができます。

以下は querySelectorAll() で取得した li 要素の集まり(NodeList)を配列に変換し、配列のメソッド indexOf() を使って1つだけある active クラスの li 要素のインデックスを取得する例です。

indexOf() は最初に見つかった要素のインデックスを返します。

<ul id="menu">
  <li>HOME</li>
  <li>WORKS</li>
  <li>ABOUT</li>
  <li class="active">NEWS</li>
  <li>CONTACT</li>
</ul>
<script>
//li 要素の集まり(NodeList)を配列に変換
const menuArray =  Array.prototype.slice.call( document.querySelectorAll('#menu li') ) ;

//配列のメソッド indexOf() で .active の要素のインデックスを取得
const index = menuArray.indexOf(document.querySelector('#menu li.active') );

console.log(index); //3
console.log(menuArray[index].textContent); //NEWS
</script>

以下は Array.from() を使って NodeList を配列に変換し、配列のメソッド findIndex() を使って active クラスの li 要素のインデックスを取得しています(結果は同じ)。

findIndex() は配列から条件に合う「最初の要素」の「位置」を返します。indexOf() は引数に与えられた値(配列要素)を比較しますが、findIndex() はコールバック関数を使って条件に合う要素を見つけることができるのでより柔軟です。

<ul id="menu">
  <li>HOME</li>
  <li>WORKS</li>
  <li>ABOUT</li>
  <li class="active">NEWS</li>
  <li>CONTACT</li>
</ul>
<script>
//li 要素の集まり(NodeList)を配列に変換
const menuArray = Array.from( document.querySelectorAll('#menu li') );

//配列のメソッド findIndex() で .active の要素のインデックスを取得
const index = menuArray.findIndex( menu => menu.className === 'active' );

console.log(index); //3
console.log(menuArray[index].textContent); //NEWS
</script>

querySelectorAll() は NodeList を返すので、NodeList の forEach メソッドを使用できます。

getElementsByClassName() などを使う場合は、HTMLCollection を返すので配列に変換して、配列の forEach メソッドを使うことができます。

//li 要素の集まり(NodeList)
const menuArray = document.querySelectorAll('#menu li');
let targetIndex = -1;

//NodeList の forEach メソッドを使用
menuArray.forEach( (elem, index) => {
  if(elem.className === 'active') {
    targetIndex = index;
  }
})

console.log(targetIndex); //3
console.log(menuArray[targetIndex].textContent); //NEWS

forEach は全ての要素に対して実行されるので、以下では複数の active クラスの要素がある場合を考慮して取得したインデックスを配列に入れて、その最初の要素を使っています。

前述の例の場合、複数の active クラスの要素がある場合は最後の要素のインデックスが取得されます。

//li 要素の集まり(NodeList)
const menuArray = document.querySelectorAll('#menu li');
let indexes = [];

//NodeList の forEach メソッドを使用
menuArray.forEach( (elem, index) => {
  if(elem.className === 'active') {
    indexes.push(index);
  }
});

console.log(indexes[0]); //3
console.log(menuArray[indexes[0]].textContent); //NEWS

イベント処理

イベントの処理は Element のメソッド addEventListener() や removeEventListener()、Event のメソッド preventDefault() や stopPropagation() などを使うことができます。

Element インターフェースのメソッド(イベント関連一部抜粋)
メソッド 説明
addEventListener() 要素にイベントリスナーを登録
removeEventListener() 要素から登録したイベントリスナーを削除
dispatchEvent() イベントを発火
Event インターフェイスのメソッド(一部抜粋)
メソッド 説明
preventDefault() ブラウザが標準で実装している処理(デフォルトの動作)をキャンセル(停止)
stopPropagation() イベントが 伝播 (propagation) するのを停止

参考サイト:MDN イベントの紹介

イベントタイプ

利用可能なイベントタイプの一覧は「MDN イベントリファレンス」に掲載されています。

イベント処理の設定方法

イベントに対する処理の設定方法は大きく分けると以下の DOM 要素のプロパティである onevent ハンドラーを使う方法と EventTarget のメソッド addEventListener() を使う方法があります。

設定方法 説明
onevent ハンドラー

onevent ハンドラーは特定の DOM 要素のプロパティで通常、onclick、onfocus、onload など on の後にイベント名を続けた名前が付けられています。

IE8 など古いブラウザでも作動しますが、同じイベントに対して複数のハンドラーを設定できず、必要な時にハンドラーを削除することもできません。

関連項目:イベントハンドラーの設定

addEventListener()

同じ要素に複数のイベントハンドラーを登録できたり、必要な時にイベントハンドラーを削除できる、キャプチャフェーズかバブリングフェーズのどちらで処理されるかを制御できるなどのメリットがあります。

但し、IE9 未満には対応していません。

addEventListener

ある要素に対してイベントリスナーを登録するには、そのオブジェクト(EventTarget)のメソッド addEventListener() を使用することができます。

target.addEventListener(type, listener[, useCapture]); (従来からの書式)

target.addEventListener(type, listener[, options]);

target はイベントを設定する要素です。

パラメータ
  • type:イベントリスナーを登録するイベントタイプ(イベント名)。全て小文字。
  • listener:指定した種類のイベントが発生したときに呼び出されるリスナー関数。リスナー関数には Event オブジェクトが引数として渡されます。
  • useCapture:イベントハンドラがキャプチャフェーズかバブリングフェーズのどちらで処理されるかを制御する真偽値(省略可)。初期値は false で、バブリングフェーズで実行。
  • options:イベントリスナーの特性を指定するオブジェクト(once、passive、capture のオプションに真偽値を設定)を指定。 (省略可)

※第3引数に真偽値(true|false)を指定した場合は、従来通りのキャプチャフェーズかバブリングフェーズの制御になります。ブラウザが第3引数の useCapture と options のどちらをサポートしているかを検出するには オプションの検出 を参照。

戻り値
undefined

以下はボタンをクリックすると「Hello」とアラート表示する例です。

<button id="btn">Click Me</button>

<script>
//イベントを登録する要素を取得
var btn = document.getElementById("btn");

//イベントが発生したときに呼び出されるリスナー関数
function sayHello() {
  alert("Hello");
}

//要素にイベントリスナーを登録
btn.addEventListener('click', sayHello, false);
</script> 

MDN : addEventListener()

無名関数を使う

リスナー関数を無名関数(匿名関数)を使って、addEventListener() の引数に記述することもできます。

以下はボタンをクリックすると「Hello」とアラート表示する例を無名関数を使って書き換えたものです。

<button id="btn">Click Me</button>

<script>
//イベントを登録する要素を取得
var btn = document.getElementById("btn");

//匿名関数を使って要素にイベントリスナーを登録
btn.addEventListener('click', function() {
  alert("Hello");
}, false);
</script> 

但し、無名関数を使って登録すると、リスナー関数に名前がないため removeEventListener() を使ってイベントリスナーを削除することができません。

また、以下のような場合、ループごとに無名関数が作成されるので、繰り返しが多くなると効率的ではありません。

//全ての button 要素を取得
var btn = document.getElementsByTagName('button');

//取得した button 要素の数だけイベントリスナーを無名関数を使って登録
for(var i = 0; i < btn.length; ++i){
  btn[i].addEventListener("click", function() {
    /*関数の処理*/
  }, false);
}    

以下のように別途リスナー関数を定義しておけば、定義された同じ関数が使われるため、メモリの消費が抑えられ効率的になります。

//全ての button 要素を取得
var btn = document.getElementsByTagName('button');

//リスナー関数の定義
function dosomething() {
  /*関数の処理*/
}

//取得した button 要素の数だけイベントリスナーを登録
for(var i = 0; i < btn.length; ++i){
  btn[i].addEventListener("click", dosomething, false);
}

this キーワード

現在の実装では addEventListener() で登録された function() を使ったリスナはターゲット要素のメソッドとして呼び出され、リスナが呼び出されると、this キーワードはリスナが登録されたオブジェクトを参照しますが、DOM レベル2 では関数がどのように呼び出されるかや、this キーワードの値については何も規定されていようです。

仕様で規定されていない動作を使いたくない場合は、リスナ関数に渡される Event オブジェクトの currentTarget(リスナーを登録した要素)や target(イベントを発生させた要素)を使います。

アロー関数

アロー関数と function() を使った関数定義では this の参照先が異なります。

関連項目:アロー関数は this を持たない

以下は input 要素の値(テキストフィールドに入力された値)が変化するたびに input イベントで値を p 要素に出力する例です。

function() を使った関数定義では this が参照するのは、このイベントが発生した要素になります。

<input type="text" id="textInput" size="30">
<p id="output"></p>

<script>
//テキストフィールドの id 属性が textInput を取得(input 要素)
const textInput = document.getElementById('textInput');
//id 属性が output の p 要素を取得(出力先)
const output = document.getElementById('output');

//テキストフィールドに input イベントを登録
textInput.addEventListener('input', function() {
  //出力先の p 要素の textContent に値を設定(出力)
  output.textContent = this.value;  //this は textInput
});
</script>

アロー関数を使って以下のように記述すると textInput(テキストフィールド)を this で参照できない(this は外側のスコープを探索する)ため何も出力されません。

この例の場合の this は window オブジェクトになり、this.value は undefined になってしまいます。

textInput.addEventListener('input', () => {
  //this は textInput(テキストフィールド)を参照できないため何も出力されない
  output.textContent = this.value;
  console.log(this.value);  //undefined
  console.log(this.innerWidth);  //表示領域の幅が出力される
}, false);

イベントハンドラには、発生したイベント自体(Event オブジェクト)が引数(以下の例では e)として渡されるので、以下のように Event オブジェクトを使って this の代わりに Event のプロパティ e.currentTarget を使えばテキストフィールドの値を取得できます。

この例の場合は currentTarget の代わりに target を使っても同じですが、currentTarget と target は必ずしも同じとは限りません。

currentTarget はイベントを登録した要素を参照し、target はイベントを発生させたオブジェクトを参照します。

//テキストフィールドに input イベントを登録
textInput.addEventListener('input', (e) => {
  //出力先の p 要素の textContent にイベントが発生している要素(e.target)の値を設定
  output.textContent = e.target.value;
  //output.textContent = e.currentTarget.value;
}, false);

Event オブジェクトを使わずに以下のように記述することもできます。

textInput.addEventListener('input', () => {
  //出力先の p 要素の textContent にテキストフィールド(textInput)の値を設定
  output.textContent = textInput.value;
}, false);

関連項目:ブラウザに組み込まれた JavaScript/addEventListener()

リスナーを複数同時に設定

addEventListener に同時に複数のイベントタイプを登録したい場合、複数のイベントタイプにまとめて同じリスナー関数を設定する方法はないようなので、別々に設定するか関数を作成します。

以下のテキストエリアには、3つのイベントタイプ(copy, cut, paste)を設定してコピーや切り取り、貼り付けをできないようにしています。コピーや切り取り、貼り付けの操作を行うとそれができない旨のメッセージを表示します。

また、表示されたメッセージは、input イベントを使って入力操作を行うとクリアします。

以下はそれぞれのイベントタイプごとに同じ内容のリスナー関数を設定する例です。

<textarea id="inputArea" cols="40" rows="5"></textarea>
<p id="alertMessage" style="color: red;"></p>

<script>
  //テキストエリアを取得
  const inputArea = document.getElementById('inputArea');
  //メッセージを表示する p 要素を取得
  const alertMessage = document.getElementById('alertMessage');

  //テキストエリアに copy イベントを設定
  inputArea.addEventListener('copy', (e) => {
    // e.type で発生したイベント名を取得してメッセージを表示
    alertMessage.textContent = e.type + ' はできません';
    //コピーを禁止(させない)
    e.preventDefault();
  });

  //テキストエリアに cut イベントを設定
  inputArea.addEventListener('cut', (e) => {
    alertMessage.textContent = e.type + ' はできません';
    e.preventDefault();
  });

  //テキストエリアに paste イベントを設定
  inputArea.addEventListener('paste', (e) => {
    alertMessage.textContent = e.type + ' はできません';
    e.preventDefault();
  });

  //テキストエリアに input イベントを設定
  inputArea.addEventListener('input', () => {
    //入力された内容が変わるとメッセージをクリア
    alertMessage.textContent = '';
  });

</script>

以下は上記と同じことを、複数のイベントタイプに同じリスナー関数を設定する独自関数 addMultiEventListener を作成して登録する例です。

この例ではイベントタイプをカンマ区切りの文字列で関数に渡しています。カンマを区切り文字として split() で分割し、map() でそれぞれの要素に対して trim() で前後に空白文字があれば削除しています。

イベントタイプの配列(events)を forEach() でループして各イベントタイプを addEventListener を使って登録しています。

リスナー関数は別途定義して addMultiEventListener に渡しています。

<script>
  const inputArea = document.getElementById('inputArea');
  const alertMessage = document.getElementById('alertMessage');

  //複数のイベントタイプに同じリスナー関数を設定する関数
  const addMultiEventListener = (target, eventList, listener) => {
    //カンマ区切りのイベントタイプのリストを分割して、前後の空白文字を除去
    const events = eventList.split(',').map(event => event.trim());
    //それぞれのイベントタイプにリスナー関数(別途定義した listener)を設定
    events.forEach(event => target.addEventListener(event, listener));
  };

  //リスナー関数の定義
  const noCopyPasteCut = (e) => {
    alertMessage.textContent = e.type + ' はできません';
    e.preventDefault();
  }

  //copy, cut, paste の各イベントにリスナーを設定(addMultiEventListener の実行)
  addMultiEventListener(inputArea, "copy, cut, paste", noCopyPasteCut);

  //リスナー関数が異なるので別途設定
  inputArea.addEventListener('input', () => {
    alertMessage.textContent = '';
  })

</script>
リスナーを一度だけ呼び出す

addEventListener() の第3引数に {once: true} を指定すると、イベントリスナーは一度だけ呼び出され、一度実行された際に自動的に削除されます。

以下は id が alert_once の要素をクリックすると一度だけアラート表示します。2回目以降クリックしてもアラートは表示されません(ページを再読込みするとまた一度だけアラートが表示されます)。

一度だけアラートを表示

<p id="alert_once">一度だけアラートを表示</p>
document.getElementById('alert_once').addEventListener('click', ()=> {
  alert('一度だけアラートを表示します');
}, {once: true});

但し、古いブラウザー(IE など)では addEventListener() の第3引数はキャプチャーを使用するかどうかを示す論理値となっているので上記は機能しません(オプションの検出)。

can i use "once" event listener option

古いブラウザーに対応するには removeEventListener() を使います。

removeEventListener() を使う

以下は removeEventListener() を使ってイベントリスナーを一度だけ呼び出す例です。

removeEventListener() の第1引数には addEventListener() で指定したイベント名を指定します。以下では e.type でイベント名('click')を取得しています。

第2引数には addEventListener() で登録したリスナー関数を指定する必要があるので、以下ではリスナー関数を名前付きで定義して removeEventListener() でその関数を指定しています。以下では funcName という名前で定義していますが、任意の名前を付けられます。

//リスナーを登録する要素を取得
var elem = document.getElementById('alert_once');

//リスナー関数を名前付きで定義して、removeEventListener でその関数を指定
elem.addEventListener('click', function funcName(e) {
  this.removeEventListener(e.type, funcName);
  // または e.currentTarget.removeEventListener(e.type, funcName);
  alert('一度だけアラートを表示します');
});

上記の場合、addEventListener() で function() を使ってリスナを登録しているので、this キーワードはリスナが登録された要素(e.currentTarget.)を参照するので、以下と同じことです。

elem.addEventListener('click', function funcName() {
  elem.removeEventListener('click', funcName);
  alert('一度だけアラートを表示します');
});

以下のように一度だけ実行するイベントを登録する関数を定義しておくこともできます。doOnce には別途定義した関数を指定することも、以下のように function() で指定することもできます。

//一度だけ実行するイベントを登録する関数を定義
var addOneTimeEvent = function(element, type, doOnce) {
  element.addEventListener(type, function funcName() {
    element.removeEventListener(type, funcName);
    doOnce();
  });
};

//対象の要素
var elem = document.getElementById('alert_once');

//elem に一度だけ実行するイベントを登録
addOneTimeEvent(elem, 'click', function() {
  alert('一度だけアラートを表示します');
});
リスナーに引数を渡す handleEvent

addEventListener の第2引数にイベントリスナーとして handleEvent() メソッドを実装したオブジェクトを指定することができます。

handleEvent() メソッドはそのオブジェクトが登録されたイベントが発生する度に呼び出されます(リスナー関数のようなものです EventListener)。

この仕組を利用して addEventListener の第2引数に handleEvent() メソッドを実装したオブジェクトを指定して、そのオブジェクトのプロパティに渡したい引数を設定することができます。

以下は addEventListener の第2引数にオブジェクトを指定して、イベント発生時に実行する関数 handleEvent() にプロパティ message を渡しています。

この場合、プロパティにアクセスするには this を使います。そのため、handleEvent は function 文で定義します(アロー関数では this の参照が異なるため)。

<button type="button" id="btn">Click</button>

<script>
document.getElementById('btn').addEventListener('click', {
  //handleEvent() メソッドを実装
  handleEvent:  function (e) {
    alert(this.message + 'イベントタイプ:' + e.type );
  },
  message: 'Hello! ' //プロパティ(引数として渡す値)
}, false);
</script>

第2引数に指定するオブジェクトは以下のように別途定義することもできます。

//第2引数に指定するオブジェクト
const myEventListener = {
  handleEvent: function(e) {
    alert(this.message + 'イベントタイプ:' + e.type );
  },
  message: 'Hello! '
}

document.getElementById('btn').addEventListener('click', myEventListener, false);

以下は input イベントを使ってテキストフィールドに入力された値とイベントの発生回数をリアルタイムに出力する例です。

入力値:

イベント発生回数:

addEventListener の第2引数にリスナー関数の代わりにオブジェクトを渡す例です。

handleEvent の定義ではプロパティにアクセスするのに this を使います。

また、カウントをリセットする際には、オブジェクトのカウントをリセットする必要があります(41行目)。単に count = 0 としてもオブジェクトのカウントはリセットできません。

<input type="text" id="textInput" size="30" placeholder="テキストを入力">
<input type="button" id="clear" value="Reset">

<p>入力値: <span id="output"></span></p>
<p>イベント発生回数: <span id="countOut"></span></p>

<script>
//id 属性が textInput の要素(テキストフィールド)
const textInput = document.getElementById('textInput');
//id 属性が output の要素(入力値の出力先)
const output = document.getElementById('output');
//id 属性が countOut の要素(カウントの出力先)
const countOut = document.getElementById('countOut');
//カウント(イベントの発生回数)
let count = 0;

//addEventListener の第2引数に渡すオブジェクト
const eventListenerObj = {
  output : output,  //入力値の出力先
  count : count,   //カウント(イベントの発生回数)
  countOut : countOut, //カウントの出力先
  //handleEvent() メソッドを実装
  handleEvent: function(e) {
    //output の textContent プロパティに入力された値を出力
    this.output.textContent = e.target.value;
    //発生回数を1増加
    this.count ++;
    //countOut の textContent プロパティに発生回数を出力
    this.countOut.textContent = this.count;
  }
};
textInput.addEventListener('input', eventListenerObj, false);

//id 属性が clear の要素に click イベントを設定
document.getElementById('clear').addEventListener('click', ()=> {
  //テキストフィールドの value や出力先のテキストを空に
  textInput.value = '';
  output.textContent = '';
  countOut.textContent = '';
  //※オブジェクトのカウントを 0 に
  eventListenerObj.count = 0;
}, false);

</script>

この例の場合、実際にはあえてリスナーに引数を使って値を渡す必要はないので、以下のように単純にリスナー関数を使って記述した方が簡単ですが。

<script>
const textInput = document.getElementById('textInput');
const output = document.getElementById('output');
const countOut = document.getElementById('countOut');
let count = 0;

const eventListener = (e)=> {
  output.textContent = e.target.value;
  count ++;
  countOut.textContent = count;
}

/* //function 文の場合
function eventListener(e) {
  output.textContent = e.target.value;
  count ++;
  countOut.textContent = count;
}*/

textInput.addEventListener('input', eventListener, false );

document.getElementById('clear').addEventListener('click', ()=> {
  textInput.value = '';
  output.textContent = '';
  countOut.textContent = '';
  count = 0;
}, false);
</script>
イベント終了時に処理を実行

リサイズイベントやスクロールイベントなど発生する頻度が高いイベントにそのまま処理を記述して実行するとブラウザに負担がかかったり、イベント終了後も処理が続いてしまう場合があります。

例えば、ユーザーがブラウザのサイズを変更した後に何らかの処理をお行えば良い場合(リサイズ中には処理が不要な場合)はイベント終了時に処理を実行すれば良いことになります。

以下は setTimeout() と clearTimeout() を使ってイベント(操作)が終了した時点から一定の時間(以下の場合は200ミリ秒)経過した際に処理を行う方法です。

終了してからの経過時間(以下の interval)は内容により調整する必要があります。

let eventTimer;
//操作(イベント)を停止して 200ms 経過したら終了と判定
const interval = 200;

window.addEventListener('イベント', (e) => {
  if (eventTimer !== false) {
    clearTimeout(eventTimer);
  }
  eventTimer = setTimeout( () => {
    // イベントが終了したら行う処理
  }, interval);
});

例えば、resize イベントの場合、以下のようになります。

  1. サイズを変更する操作
  2. resize イベントが発生
  3. setTimeout()が呼び出され、interval で指定した時間(200ms)後に処理を実行すると定義され、eventTimer に timeoutID(タイマーを識別するための正の整数値)が代入される
  4. サイズを変更中(200ms 経過する前にサイズを変更する操作が継続)
  5. resize イベントが発生
  6. setTimeout() は既に呼び出され、eventTimer は false ではないため clearTimeout(eventTimer) により定義した処理がキャンセルされる
  7. サイズを変更する操作が継続
  8. resize イベントが発生
  9. setTimeout()が呼び出され、interval で指定した時間後に処理を実行すると定義される
  10. ・・・サイズを変更する操作が継続する間同じことの繰り返し・・・
  11. サイズを変更する操作が終了して interval で指定した時間(200ms)が経過
  12. setTimeout() で定義された処理が実行される

clearTimeout() に渡す timeoutID は有効な値を渡さなければ効果がないので、以下のように記述しても同じです。

let eventTimer;
//操作(イベント)を停止して 200ms 経過したら終了と判定
const interval = 200;

window.addEventListener('イベント', (e) => {
  clearTimeout(eventTimer);
  eventTimer = setTimeout( () => {
    // イベントが終了したら行う処理
  }, interval);
});

以下はブラウザ(表示領域)のサイズを変更(リサイズ)すると、変更が完了した時点(正確には0.2秒後)でその幅を表示する例です。

<p>ウィンドウ幅:<span id="resize_event_output"></span></p>
<script>
  const resizeEventOutput = document.getElementById('resize_event_output');
  // window の表示領域の幅を取得して表示
  resizeEventOutput.textContent = window.innerWidth;

  let myTimer = false;
  window.addEventListener('resize', (e) => {
    if (myTimer !== false) {
      clearTimeout(myTimer);
    }
    myTimer = setTimeout(function() {
      //e.target(window)の表示領域の幅を取得して表示
      resizeEventOutput.textContent = e.target.innerWidth;
      //または resizeEventOutput.textContent = window.innerWidth;
    }, 200);
  });
</script>

ウィンドウ幅:

参考サイト:JavaScript で window リサイズ終了後のタイミングをハンドリングする方法

イベントの処理回数を減らす

スクロールイベントなど発生する頻度が高いイベントでは、ブラウザに負担がかかる可能性があります。setTimeout() を利用して発生するイベントに対して処理回数を減らすことができます。

どのぐらいの頻度で処理を実行するかは interval の値で調整します。

setTimeout() で返される値(eventTimer)は正の整数値になりますが、setTimeout() 内で eventTimer に 0 を設定しているので、その時(interval で指定した時間経過後)だけ処理が実行されます。

let eventTimer;
const interval = 300;

window.addEventListener('イベント', (e) => {
  // eventTimer が 0 でなければ何もしない(0はfalse)
  if ( eventTimer ) return ;

  eventTimer = setTimeout( () => {
    // eventTimer に 0 を設定
    eventTimer = 0 ;
    // 間引いて実行する処理
  }, interval);
});

以下は scroll イベントの処理を 300ミリ秒に1回行なう例です。

<p>通常イベントの発生回数: <span id="normal_count_output"></span></p>
<p>間引いたイベントの発生回数: <span id="reduced_count_output"></span></p>
<script>
  const normal_count_output = document.getElementById('normal_count_output');
  const reduced_count_output = document.getElementById('reduced_count_output');

  let eventTimer;
  const interval = 300;
  var count1 = 0 ;
  var count2 = 0 ;

  window.addEventListener('scroll', (e) => {
    count1 ++;
    normal_count_output.textContent = count1;
    if ( eventTimer ) return ;

    eventTimer = setTimeout( () => {
      eventTimer = 0 ;
      count2 ++;
      reduced_count_output.textContent = count2;
    }, interval);
  });
</script>

通常イベントの発生回数:

間引いたイベントの発生回数:

参考サイト:スクロールイベントの頻度を減らす方法

オプションの検出

古いブラウザーでは addEventListener() の第3引数はキャプチャーを使用するかどうかを示す論理値ですが、新しいブラウザではイベントリスナーの特性を指定するオブジェクトになっています。

このため、例えば第3引数に passive を有効にするために {passive: true} を指定すると、{passive: true} は真偽値としては true になるので古いブラウザでは useCapture を true にしたと解釈されてしまいます。これを避けるにはオプションごとに機能検出を使用します。

例えば passive オプションが使えるかどうかを調べるには以下のように記述します。

passive オプションの検出
//passive オプションがサポートされているかどうかの真偽値
let passiveSupported = false;

try {
  const options = {
    get passive() {
      passiveSupported = true;  //passive オプションがサポートされていれば true
      return false;
    }
  };

  window.addEventListener("test", null, options);
  window.removeEventListener("test", null, options);
} catch(err) {
  passiveSupported = false;
}

そして第3引数を指定する際に passiveSupported の値を調べてサポートされていれば { passive: true } に、そうでなければ useCapture を false にすることができます。

document.addEventListener('wheel', (e) => {
   //イベント処理
}, passiveSupported ? { passive: true } : false);

参考:オプションの対応の安全な検出(MDN)

パッシブイベントリスナー

タッチイベントリスナーとホイールイベントリスナーに {passive:true} を指定することでパフォーマンスを改善できる可能性があります。

参考:Passive event listeners

また、一部のブラウザー (Chrome と Firefox) では、Window, Document, Document.body の touchstart 及び touchmove イベントの passive オプションの既定値を true に変更しているようです。

参考:パッシブリスナーを用いたスクロールの性能改善(MDN)

ブラウザサポート状況: can i use Passive event listeners

removeEventListener

removeEventListener() は登録したイベントリスナーを削除します。

target.removeEventListener(type, listener[, options]);

パラメータ
  • type:登録されたイベントを表す文字列。(addEventListener() で指定したイベント名)
  • listener:登録時のリスナー関数
  • options:登録時に指定した場合は capture/useCapture フラグは一致させる
戻り値
undefined

登録した際のイベント名(type)と関数名(listener)を一致させる必要があります。また、登録時に第3引数の capture/useCapture オプションを指定した場合は、値を一致させる必要があります(capture/useCapture 以外の値は一致していなくてもかまいません)。

var btn = document.getElementById('foo');

function hello() {
  alert('Hellow World!');
}
//イベントリスナーを登録
btn.addEventListener('click', hello, false);
//登録したイベントリスナーを削除
btn.removeEventListener('click', hello, false);
//または以下でも同じ
btn.removeEventListener('click', hello, {capture: false});  

MDN : removeEventListener

※ リスナー関数を無名関数で登録した場合は削除できません。

以下はエラーにはなりませんが、イベントリスナーを削除することはできません。

var btn = document.getElementById('foo');

//イベントリスナーを無名関数で登録
btn.addEventListener('click', function() {
  alert('Hellow World!');
});

//(削除できない例)無名関数で登録したイベントリスナーは削除できない
btn.removeEventListener('click', function() {
  alert('Hellow World!');
});

以下は一度だけアラートを表示するイベントリスナーを登録する例で、リスナー関数を名前付きで定義して removeEventListener() でその関数名を指定しています(リスナーを一度だけ呼び出す)。

var btn = document.getElementById('foo');

//イベントリスナーを登録して1度実行したら削除
btn.addEventListener('click', function hello() {
  alert('Hellow World!');
  this.removeEventListener('click', hello);
});

但し、イベントリスナーを登録する際に名前付きで定義したリスナー関数は、外部からはアクセスできないので以下の場合はエラーになります。

var btn = document.getElementById('foo');

//イベントリスナーを登録
btn.addEventListener('click', function hello() {
  alert('Hellow World!');
});
//hello() にアクセスできないのでエラーになる
btn.removeEventListener('click', hello);   //hello is not defined
Event インターフェース

リスナー関数には発生したイベント(Event オブジェクト)が引数として渡されるので、event や e など任意の名前で参照することができます。

そしてリスナー関数では必要に応じて渡されたイベントのプロパティを調べて処理の中で利用することができます。

//イベントを登録する要素を取得
var div = document.getElementById('foo');

//リスナー関数の引数にはイベントのオブジェクト(以下の場合は event)が渡される
function show_target_id(event) {
  //イベントオブジェクトの target プロパティの id をコンソールに出力
  console.log(event.target.id);  //foo
}

//要素に click イベントのリスナーを登録
div.addEventListener('click', show_target_id);

全ての Event オブジェクトは Event インターフェースを実装しています。以下は Event インターフェースのプロパティの例です。

プロパティ 概要
type 発生したイベントタイプ。このプロパティの値はイベントタイプの名前。イベントリスナーを登録するときに使う文字列と同じ。 (「click」や「mouseover」など)
target イベントを発生させたオブジェクトへの参照。イベントリスナーがバブリングまたはキャプチャリング段階の間に呼び出されたとき、event.currentTarget とは異なります。
currentTarget イベントの現在のターゲットへの参照。イベントリスナーがアタッチされた要素(リスナーを登録した要素)を参照するのでリスナー関数中で this キーワードの代わりにこのプロパティが使えます。イベント伝播のキャプチャリング段階やバブリング段階でイベントが 処理される場合、このプロパティの値は target プロパティの値とは異なります。
eventPhase イベント伝播のどの段階を現在処理中かを表す数値(定数)。値は、Event.CAPTURING_PHASE, Event.AT_TARGET, Event.BUBBLING_PHASE のうちいずれか。
timeStamp イベントが発生した時間を表す Date オブジェクト。
bubbles このイベント(タイプ)がドキュメントのツリー構造をバブリングするかどうかを表す論理値。
cancelable イベントがデフォルトの動作を持ち、そのデフォルトの動作が preventDefault() メソッドで中止可能かどうかを表す論理値。

また、イベントには MouseEventUIEventProgressEvent などのいくつかの種類(イベント型/モジュール)があり、それぞれにプロパティやメソッドがあります。

イベントモジュールが定義するインターフェースと対応するイベントタイプの例
モジュール名 インターフェース イベントタイプ
HTMLEvents Event abort, blur, change, error, focue, load, reset, resize, scroll, select, submit, unload
MouseEvents MouseEvent click, mousedown, mousemove, mouseout, mouseover, mouseup
UIEvents UIEvent DOMActivate, dOMFocusIn, DOMFocusOut

以下はリスナー関数に渡されるイベント(この例では e)を使ってマウスダウン時に、イベント発生元の要素やイベントタイプ、マウスポインタの位置などを表示する例です。

マウスボタンのタイプは別途 getButtonType という関数を定義して取得しています。

<div id="foo">Event Area</div>
<pre id="bar"></pre>

<script>
//イベントを登録する領域の div 要素を取得
var div = document.getElementById('foo');
//イベントの内容(プロパティ)を表示する領域の pre 要素を取得
var pre = document.getElementById('bar');

//イベントリスナー関数(イベントオブジェクトは e で渡される)
function showEventProps(e) {
  var result = [];
  result.push("発生元要素:" + e.target.id);
  result.push("イベントタイプ: " + e.type);
  result.push("ボタン:" + getButtonType(e));
  result.push("X座標:" + e.clientX); //MouseEvent プロパティ
  result.push("Y座標:" + e.clientY); //MouseEvent プロパティ
  result.push("発生時刻(タイムスタンプ):" + e.timeStamp);
  //pre 要素にテキスト出力
  pre.textContent = result.join('\n');
}

//マウスボタンのタイプを返す関数
function getButtonType(e) {
  //e.button は MouseEvent プロパティ
  switch(e.button) {
    case 0: return "left";
    case 1: return "center";
    case 2: return "right";
  }
}

//イベントリスナーを div 要素に登録
div.addEventListener('mousedown', showEventProps, false);
</script>

<!-- 出力例
発生元要素:foo
イベントタイプ: mousedown
ボタン:left
X座標:61
Y座標:28
発生時刻(タイムスタンプ):1087.2500000004948
-->

以下はクリックした div 要素(イベントを発生させた div 要素)の背景色をランダムに変更する例です。

event.target を使うことで、簡単にイベントを発生させた div 要素を特定することができます。

<div id="foo">
  <div></div> <div></div> <div></div> <div></div>
</div>

<script>
//イベントを登録する全ての領域の div 要素を取得
var divs = document.getElementById('foo').getElementsByTagName('div');
//背景色の配列
var colors = ['green', 'blue', 'red', 'yellow', 'orange', 'silver'];

//イベントを発生させた要素(event.target)の背景色を変更するリスナー関数
function changeBGColor(event) {

  //Math.floor(Math.random()*6) は 0~5 のランダムな整数を返します。
  event.target.style.backgroundColor = colors[ Math.floor(Math.random()*6) ];

  //イベントを発生させた要素(オブジェクト)をコンソールに出力
  console.log(event);
}

//取得した div 要素全てにイベントリスナーを登録
for (var i = 0; i < divs.length; i++) {
  divs[i].addEventListener('click', changeBGColor);
}
</script>

<!-- コンソール出力例(イベントオブジェクト)
MouseEvent {isTrusted: true, screenX: 311, screenY: 584, clientX: 226, clientY: 302, …}
-->

上記サンプル(同じ色が続くことがあります)

preventDefault

Web ブラウザが標準で実装している処理(デフォルトの動作)を中止するには Event インターフェイスのメソッド preventDefault() メソッドを使用します。

例えば、a 要素をクリックするとそのリンク先にページが移動しますが、preventDefault() メソッドを実行すると、このデフォルトの動作を止めることができます。

但し、イベントの中には、preventDefault() メソッドで中止できないイベントもあります。Event インターフェースのプロパティ cancelable で確認することができます。

以下は Email 入力欄と送信ボタンのみのフォームで、Email 入力欄が空のまま送信ボタンをクリックすると「Email を入力してください」とメッセージを表示する例です。

フォームの送信ボタンをクリックすると、submit イベントが発火してフォームが送信されます。以下の例では、入力欄が空の場合は、e.preventDefault() でデフォルトの動作(フォームの送信)を止めてメッセージを表示します。

<form>
  <div>
    <label for="email">Email: </label>
    <input id="email" type="text">
  </div>
  <div>
     <input id="submit" type="submit">
  </div>
</form>
<p class="message"></p>

<script>
//フォーム要素を取得
var form = document.forms[0];
//Email 入力欄の要素を取得
var email = document.getElementById('email');
//メッセージを表示する要素を取得
var message = document.getElementsByClassName('message')[0];

//リスナー関数(Email 入力欄をチェック)
function validate(e) {
  //Email 入力欄が空の場合
  if (email.value === '') {
    //デフォルトの動作(フォームの送信)を停止
    e.preventDefault();
    //メッセージを表示
    message.textContent = 'Email を入力してください';
  }
}
//フォーム要素にイベントリスナーを登録
form.addEventListener('submit', validate);
</script> 
stopPropagation

HTML ドキュメントは要素が入れ子になって構成されています。デフォルトではページ上で発生したイベントは上位の要素にも通知されます。

以下のような入れ子になった div 要素がある場合、下位の要素(id="bar")で発生したイベントは上位要素(id=""foo")にも通知されます(バブリング)。

<div id="foo">Foo
  <div id="bar">Bar</div>
</div>

以下のように両方の要素にアラートを表示するイベントリスナを登録した場合、下位の要素(id="bar")をクリックすると、イベントが上位要素に伝播するのでアラートは2回表示されます。

var foo = document.getElementById("foo");
var bar = document.getElementById("bar");

function fooAlert(e) {
  alert("foo: 上位要素のリスナ");
}

function barAlert(e) {
  alert("bar: 下位要素のリスナ");
}

foo.addEventListener('click', fooAlert);
bar.addEventListener('click', barAlert);
Foo
Bar

伝播のキャンセル

イベントを伝播させないようにするには、stopPropagation() メソッドを使います。以下のように伝播させたくないイベントリスナー内で実行します。

var foo = document.getElementById("foo");
var bar = document.getElementById("bar");

function fooAlert(e) {
  alert("foo: 上位要素のリスナ");
}

function barAlert(e) {
  alert("bar: 下位要素のリスナ");
  e.stopPropagation();  //バブリングを抑止
}

foo.addEventListener('click', fooAlert);
bar.addEventListener('click', barAlert);
Foo
Bar
イベントの移譲(Event delegation)

ページ上で発生したイベントは上位の要素にも通知され、バブリングと呼びます(イベントの伝播)。

このバブリングにより、イベント移譲(Event delegation)と呼ばれるイベント処理のパターンを実装することができます。

イベント移譲は、複数の要素のイベントを処理する際に、それらの1つ1つにイベントリスナーを割り当てるのではなく、共通の祖先(親要素)にイベントリスナーを設定します(子要素で発生したイベントはバブリングしてくるので親要素のイベントリスナーで捉えることができます)。

親要素にイベントリスナーを設定するため、動的に子要素を追加しても追加した要素で発生したイベントを捕捉することができます(動的に追加/削除が可能)。

親要素に登録したイベントリスナーでは、引数で渡されるイベントの target プロパティを調べて、実際にどこでイベントが起きたか(イベントの発生した要素)を確認して処理をします。

以下のような table 要素があり、その子要素の td 要素をクリックしたらその背景色を変更する場合、全ての td 要素にイベントリスナーを登録する代わりに、親要素の table 要素にイベントリスナーを登録して、子要素で発生するイベントを処理することができます。

<table id="myTable">
  <caption>Sample Table</caption>
  <tr>
      <th>TH</th>
      <th>TH</th>
      <th>TH</th>
    </tr>
  <tr>
    <td>1</td>
    <td>2</td>
    <td>3</td>
  </tr>
  ・・・中略・・・
</table>

<!--以下は発生したイベントの target と currentTarget の値を出力するための p 要素-->
<p>event.target のタグ <span id="event_target"></span></p>
<p>event.currentTarget のタグ <span id="event_currentTarget"></span></p>

id 属性が myTable の table 要素を取得してイベントリスナーを登録します。

リスナー関数では、引数に受け取ったイベント(e)を調べます。e.target にはイベントの発生した要素が、e.currentTarget にはイベントを登録した要素が入っています(以下では 8〜10行目で確認のためにそれらの値を出力しています)。

実際の処理は13行目からで、まず、クリックイベントが発生した要素を tagName プロパティで調べて、それが td 要素でなければ何もしないように return しています。

tagName プロパティの値(タグ名)は大文字で表現されます(Element プロパティ)。

イベントが発生した要素が td 要素であれば、その要素のスタイルを操作して背景色を変更しています。

// id 属性が myTable の table 要素
const myTable = document.getElementById('myTable');

//対象の td 要素の親要素の table にイベントリスナーを登録
myTable.addEventListener('click', (e) => {

   //クリックイベントが発生した要素(e.target)のタグ名を出力(確認用:通常は不要)
  document.getElementById('event_target').textContent = e.target.tagName;
  //イベントリスナーを登録した要素(e.currentTarget)のタグ名を出力(確認用:通常は不要)
  document.getElementById('event_currentTarget').textContent = e.currentTarget.tagName;

  // クリックイベントが発生した要素を変数 target に代入
  const target = e.target;
  // クリックイベントが発生した要素が td 要素でなければ何もしない
  if (target.tagName != 'TD') return;
  // 要素の背景色を変更
  if(target.style.backgroundColor ==='pink') {
    //背景色が pink なら背景色を削除
    target.style.backgroundColor = null;
  }else{
    //背景色が pink なら背景色を pink に
    target.style.setProperty('background-color', 'pink');
  }
});

td 要素をクリックした場合のみ、その要素の背景色を変更しています。

Sample Table
TH TH TH
1 2 3
4 5 6
7 8 9
event.target のタグ
event.currentTarget のタグ

以下は個々のラジオボタンにイベントリスナーを登録する代わりに、親要素の div 要素にリスナーを登録する例です。

div 要素では change イベントは発生しませんが、子要素で発生する change イベントを捕捉するために change イベントを指定しています。

この例ではイベントが発生した要素のタグ名(tagName)ではなく、type 属性と name 属性で対象の要素かどうかを判定しています。

<div id="myRadio">
  <input type="radio" name="color" value="green" id="radioGreen">
  <label for="radioGreen"> Green </label>
  <input type="radio" name="color" value="blue" id="radioBlue">
  <label for="radioBlue"> Blue </label>
  <input type="radio" name="color" value="red" id="radioRed">
  <label for="radioRed"> Red  </label>
</div>
<p>選択された項目の値 : <span id="checkedItems"></span></p>

<script>
  //ラジオボタンの親要素の div 要素
  const myDiv = document.getElementById('myRadio');
  // 選択された項目の値を出力する id が checkedItems の span 要素
  const checkedItems = document.getElementById('checkedItems');

  //div 要素に change イベントのリスナーを登録
  myDiv.addEventListener('change', (e) => {
    //イベントが発生した要素を変数に代入
    const target = e.target;
    //イベントが発生した要素の type 属性が radio で name 属性が color であれば
    if(target.type === 'radio' && target.name === 'color') {
      //イベントが発生した要素の value を span 要素に出力
      checkedItems.textContent = target.value;
      //span 要素の親要素(p 要素)の文字色を value の値に
      checkedItems.parentElement.style.color = target.value;
    }
  });
</script>

選択された項目の値 :

動的に追加/削除が可能

イベント移譲を使うと、親要素にイベントリスナーを設定するため、動的に子要素を追加しても追加した要素で発生したイベントを捕捉することができます。

以下は、チェックボックスの個々の要素にリスナーを設定するのではなく、親(祖先)要素に change イベントのリスナーを設定しているので動的にチェックボックスを追加してもイベント処理が行われます。

チェックボックスを選択・解除すると、その要素の値(value)と操作の内容が「操作結果:」に表示するイベントを設定しています。

また、Add ボタンをクリックするとチェックボックスが追加され、Remove ボタンをクリックすると最後のチェックボックスを削除します。

操作結果 :

HTML
<div id="myCheckbox">
  <label><input type="checkbox" name="checkValue" value="1"> value 1</label>
  <label><input type="checkbox" name="checkValue" value="2"> value 2</label>
  <label><input type="checkbox" name="checkValue" value="3"> value 3</label>
</div>
<p>操作結果 : <span id="checkedResult"></span></p>
<button type="button" id="addCheckbox">Add</button>
<button type="button" id="removeCheckbox">Remove</button>
JavaScript
//チェックボックスの親(祖先)要素
const myCheckbox = document.getElementById('myCheckbox');
//メッセージ(操作結果)を表示する要素
const checkedResult = document.getElementById('checkedResult');
//チェックボックスを追加するボタン要素
const addCheckbox = document.getElementById('addCheckbox');
//チェックボックスを削除するボタン要素
const removeCheckbox = document.getElementById('removeCheckbox');

//チェックボックスの親要素にイベントリスナーを登録
myCheckbox.addEventListener('change', (e) => {
  //イベントが発生した要素
  const target = e.target;
  //イベントが発生した要素の type 属性が checkbox で name 属性が checkValue であれば
  if(target.type === 'checkbox' && target.name === 'checkValue') {
    if(target.checked === true) { // checked プロパティが true であれば選択された
      checkedResult.textContent = target.value + ' が選択されました。';
    }else{
      checkedResult.textContent = target.value + ' が解除されました。';
    }
  }
});

// label 要素のカウント(長さ)
let labelCount = document.querySelectorAll('#myCheckbox label').length;

//チェックボックスを追加するボタンにイベントリスナーを登録
addCheckbox.addEventListener('click', () => {
  // label 要素を生成
  const label = document.createElement('label');
  // label 要素のカウントを1増加
  labelCount++;
  // 生成した label 要素の HTML にチェックボックスを設定(value と表示する文字列に label 要素のカウントを設定)
  label.innerHTML = `<input type="checkbox" name="checkValue" value="${labelCount}"> value ${labelCount}`;
  //親要素に生成した label 要素を追加
  myCheckbox.appendChild(label);
});

//チェックボックスを削除するボタンにイベントリスナーを登録
removeCheckbox.addEventListener('click', () => {
  //すべての label 要素を取得
  const  labels = document.querySelectorAll('#myCheckbox label');
  //取得した label 要素の最後の要素が存在すれば
  if(labels[labels.length -1]) {
    //最後の label 要素を削除してカウントを1減らす
    labels[labels.length -1].remove();
    labelCount--;
  }else{
    checkedResult.textContent = "削除する項目がありません";
  }
});

参考サイト:

setTimeout()

setTimeout() は第一引数に指定された関数を、第二引数に指定した時間(ミリ秒)が経過したら実行するようにタイマーをセットするグローバルのメソッドです。

言い換えると、第一引数に指定された関数を第二引数に指定された時間が経過したら実行するようにスケジュールするメソッドです。

構文
let timeoutID = setTimeout(functionRef, delay, [params, ...])

引数

以下の引数を受け取ります。

functionRef
指定した時間が経過したら実行する関数(直接関数のコードを記述することができます)。但し、関数の後に括弧 () をつけてはいけません(実行してはいけません)
delay
指定した関数やコードを実行するまでの時間(タイマーの時間)をミリ秒単位の数値で指定(省略可能)。省略した場合は 0 が使用され、(ほぼ)直ちに実行されます。
params, ...
第一引数で指定された関数やコードに渡す追加の引数(省略可能)

戻り値

戻り値の timeoutID は正の整数値で、setTimeout() を呼び出して作成したタイマー(スケジュール)を識別します。この値を clearTimeout() メソッドへ渡すことで、スケジュールした処理の実行を取り消すことができます。

以下は2秒後にメッセージをコンソールに出力する例です。

const timer = setTimeout(() => {
  console.log("2秒後に出力");
}, 2000);

// function() を使う場合
const timer = setTimeout(function() {
  console.log("2秒後に出力");
}, 2000);

上記は以下のように別途関数を定義して指定しても同じです。

const logFunc = () => {
  console.log("2秒後に出力");
}
const timer = setTimeout(logFunc, 2000);

期待通りに動作しない例

但し、以下のように関数名の後に括弧 () をつけると、即座に実行されてしまいます。第一引数にはコードまたは関数の参照を渡します(そこで実行してはいけません)。

const logFunc = () => {
  console.log("2秒後に出力");
}
//以下のように関数名の後に () を付けるのは間違いです(即座に実行されてしまいます)
const timer = setTimeout(logFunc(), 2000);

同様に以下も即座に実行されてしまいます。

const timer = setTimeout(console.log("2秒後に出力"), 2000);  //即座に実行されてしまいます

第三引数を指定

以下は setTimeout() の第三引数に、第一引数に指定した関数に渡す追加の引数を指定する例です。前述の例同様、コンソールに「2秒後に出力」と出力されます。

const timeout = 2000;

const logFunc2 = (sec) => {
  console.log(sec + "秒後に出力");
}
const timer = setTimeout(logFunc2, timeout, timeout/1000);

以下は上記と同じことです。

const timeout = 2000;

const timer = setTimeout( (sec) => {
  console.log(sec + "秒後に出力");
}, timeout, timeout/1000);

第三引数以降には複数の引数を指定して、第一引数に指定した関数に渡すことができます。

以下では第三引数と第四引数に第一引数に渡す2つの引数を指定しています。以下の場合、例えば「2秒後に出力 2023/1/8 14:31:47」のように出力されます。

const timeout = 2000;

const logFunc3 = (sec, date) => {
  console.log(sec + "秒後に出力", date);
}
const timer =
setTimeout(logFunc3, timeout, timeout/1000, new Date().toLocaleString());

setTimeout() は非同期関数

setTimeout()は非同期関数なので、後続の関数(関数スタック内の他の関数)をブロックしません。

以下の場合、oneSecMsg() は同期処理なので後続の console.log() をブロックしますが、setTimeout() は、後続の関数の実行をブロックしません。

const logFunc = () => {
  console.log("2秒後に出力");
}

//1秒経過したらメッセージを表示する関数(1秒間処理をブロック)
const  oneSecMsg = (msg) => {
  const start = Date.now();
  while (true) {
    const diff = Date.now() - start;
    if (diff >= 1000) {
      console.log(msg);
      return;
    }
  }
}

const timer = setTimeout(logFunc, 2000);
oneSecMsg('1秒経過したら表示されるメッセージ')
console.log('すぐに出力');

//以下の順番で出力される
/*
1秒経過したら表示されるメッセージ
すぐに出力 (oneSecMsg にはブロックされるが setTimeout にはブロックされない)
2秒後に出力
*/

setTimeout() を使った繰り返し

関数や処理を繰り返して呼び出すには setTimeout()setInterval() を利用できますが、setTimeout() の方が柔軟性があります。

参考ページ:ja.javascript.info スケジューリング: setTimeout と setInterval

setTimeout() を使って指定した時間後に処理を繰り返すには、再帰的に setTimeout() を使います。

以下は最初は 1000ms 待機してコンソールにカウントを出力し、その後ほぼ 500ms 間隔でコンソールにカウントを 10 まで出力します。

let count = 1;
// 繰り返し処理を実行
setTimeout(function tick() {
  // 終了条件
  if(count > 10) {
    return
  }
  // 500ms 待機して再帰的に自身(tick)を呼び出す
  setTimeout(tick, 500);
  console.log(count);
  count ++;
}, 1000);  // 最初は1000ms 待機

以下は繰り返し処理を関数として定義し、呼び出して実行する例です。前述の例とほぼ同じですが、呼び出した時点で即座に実行されます。

let count = 1;
//関数として定義
const tick = () => {
  // 終了条件
  if(count > 10) {
    return
  }
  // 500ms 待機して再帰的に自身(tick)を呼び出す
  setTimeout(tick, 500);
  console.log(count);
  count ++;
}
//上記の関数を呼び出す
tick();

以下は上記の処理部分を callback という関数に別途定義したものです(同じこと)。

let count = 1;
// 処理部分を関数に別途定義
const callback = () => {
  console.log(count);
  count ++;
}

const tick = () => {
  // 終了条件
  if(count > 10) {
    return
  }
  setTimeout(tick, 500);
  // 処理部分の呼び出し
  callback();
}
tick();

以下は setTimeout() の戻り値と clearInterval() を使って 5000ms 後に繰り返しを終了する例です。

let count = 1;
// setTimeout() の戻り値を格納する変数
let timerId ;

setTimeout(function tick() {
  // 戻り値 を timerID に格納
  timerId = setTimeout(tick, 500);
  console.log(count);
  count ++;
}, 0);

// 5000ms 後に繰り返しを終了
setTimeout(() => {
  clearInterval(timerId)
}, 5000);

以下は待機時間を変化させる例です。以下の場合、最初の待機時間は 200ms で、繰り返しごとに 100ms 増加し、終了条件に達する最後の待機時間は 1100ms になります。

let count = 1;
const tick = () => {
  // 終了条件
  if(count > 10) {
    return
  }
  // 待機時間を 100ms ずつ増加
  setTimeout(tick, 100 + count * 100);
  console.log(count);
  count ++;
}
//上記関数を呼び出す
tick();

以下も待機時間を変化させる例です。以下の場合、待機時間の初期値は 300ms ですが count が5以上の場合は 1000ms に変更しています。

let count = 1;

setTimeout(function tick() {
  // 終了条件
  if(count > 10) {
    return
  }
  // 待機時間
  let ms = 300;
  // count が5以上の場合は待機時間を 1000 に
  if(count >= 5) ms = 1000;
  setTimeout(tick, ms);
  console.log(count);
  count ++;
}, 0);

this の問題

setTimeout() によって実行されるコードは、 setTimeout が呼び出された関数とは別の実行コンテキスト内から呼び出されます。

呼び出された関数に bind() を使って this を設定しないと、setTimeout が呼び出された関数の this 値と同じにはならず、ストリクトモードでは undefined に、ストリクトモードでなければ global (または window) になります。

以下はオブジェクトのメソッドを実行してオブジェクトのプロパティにアクセスしてその値を出力する例ですが、setTimeout() を使用すると this が失われてしまい、プロパティにアクセスできません。

"use strict";

const foo = {
  userName : 'Foo',
  hello() {
    console.log(`Hello,my name is ${this.userName}`);
  }
}
foo.hello();  //Hello,my name is Foo

//setTimeout() を使用すると foo.userName を参照できず undefined になる
setTimeout(foo.hello, 1000); //Hello,my name is undefined

解決策

以下のように this に必要な値を設定するラッパー関数を使用することで期待通りに動作します。または、bind() を使って this を設定します。

//function() を使ったラッパー関数
setTimeout(function () {
  foo.hello()
}, 1000);  //Hello,my name is Foo

//アロー関数を使ったラッパー関数
setTimeout(() => { foo.hello() }, 2000);  //Hello,my name is Foo

//ラッパー関数を別途定義
const wrapperFunc = () => {
  foo.hello()
}
setTimeout(wrapperFunc, 3000);  //Hello,my name is Foo

// foo.hello の this を foo にバインド
setTimeout(foo.hello.bind(foo), 4000);  //Hello,my name is Foo

参考ページ:MDN setTimeout() this の問題

clearTimeout()

clearTimeout() メソッドに setTimeout() の戻り値を渡すことで、スケジュールされた処理の実行をキャンセルすることができます(タイマーを解除します)。

構文
clearTimeout(timeoutID)

引数 timeoutID

対応する setTimeout() から返された ID

clearTimeout() へ妥当ではない ID を渡しても、何も起きません(例外は発生しません)。

戻り値

戻り値はありません。

以下は setTimeout() による logFunc() の実行をキャンセルする例です(何も出力されません)。

const logFunc = () => {
console.log("2秒後に出力");
}
//2秒後に logFunc() を実行するようにタイマーをセット
const timer = setTimeout(logFunc, 2000);

//logFunc() の実行をキャンセル(タイマーを解除)
clearTimeout(timer);

setInterval()

setInterval() は定期的に指定された時間間隔(インターバル)で関数やコードを繰り返し呼び出します。

setInterval() の構文は setTimeout() と同じです。

構文
let timeoutID = setInterval(functionRef, delay, [params, ...])

引数

以下の引数を受け取ります。

functionRef
指定した時間が経過したら実行する関数(直接関数のコードを記述することができます)。但し、関数の後に括弧 () をつけてはいけません(実行してはいけません)
delay
指定した関数やコードを実行するまでの時間(タイマーの時間)をミリ秒単位の数値で指定(省略可能)。
params, ...
第一引数で指定された関数やコードに渡す追加の引数(省略可能)

戻り値

戻り値の timeoutID は正の整数値で、setInterval() を呼び出して作成したタイマーを識別します。この値を clearInterval() メソッドへ渡すことで、インターバル(繰り返し処理)を取り消すことができます。

this の問題

setInterval() によって実行されるコードは、呼び出し元とは別の実行コンテキスト内で実行されるため、this が失われる問題があります。解決策は setTimeout() の this の問題と同じです。

以下は、1秒毎に alert で文字とカウントを表示し、5秒後に停止します。

let count = 1;

// 1秒のインターバルで繰り返し
const timerId = setInterval(() => {
  alert('アラート' + count);
  count ++;
}, 1000);

// 5秒後に停止
setTimeout(() => {
  //繰り返しを終了
  clearInterval(timerId);
  alert('停止します');
}, 5000);

以下は1秒毎に alert で文字とカウントを表示し、count の値が5より大きくなると繰り返しを停止します。

let count = 1;
const timerId = setInterval(() => {
  alert('アラート' + count);
  count ++;
  if(count > 5) {
    // 繰り返しを停止
    clearInterval(timerId);
    alert('停止します');
  }
}, 1000);

※ ほとんどのブラウザでは、alert や confirm、prompt を表示している間、内部のタイマーは継続されます。上記の例の場合、alert ウィンドウをすぐに閉じない場合、次の alert はすぐに表示されます。

setInterval での処理の実行にかかる時間はインターバルの一部になるため待機時間は変動しますが、setTimeout を使った繰り返しの場合は処理が完了してから一定時間待機します(固定の遅延)。

以下は第三引数に、第一引数に指定した関数に渡す引数を指定する例です。

setInterval は指定したインターバルが経過したら処理が始まるので、以下ではその前に callback を呼び出して即座に実行しています。

let count = 1;
// 繰り返し呼び出す関数
const callback = (str) => {
  console.log(count + ': hello ' + str);
  count ++;
  if(count > 10) clearInterval(intervalID)
}
// 即座に実行
callback('foo');
// 1秒間隔で callback を実行(第三引数に callback に渡す引数を指定)
const intervalID = setInterval(callback, 1000, 'foo');

/* 以下が出力
1: hello foo
2: hello foo
・・・中略・・・
10: hello foo  */

apply() と call()

apply()call()Function オブジェクトの prototype プロパティ(Function.prototype)に用意されたメソッドで、すべての関数が共通して持っているメソッドです。

apply() と call() は明示的に this を指定して関数を実行するメソッドで、この2つのメソッドを利用すると、引数に指定したオブジェクトのメソッドであるかのように関数を実行することができます。

参考サイト:JavaScript Primer:call、apply、bindメソッド

以下が構文です。

apply()
関数.apply(thisの値, [関数の引数1, 関数の引数2, ...]);
call()
関数.call(thisの値, 関数の引数1, 関数の引数2, ...);

第一引数

関数が呼び出されたときに this として使用される値(参照されるオブジェクト)を指定します。言い換えると、呼び出す関数の本体で this となるオブジェクトを指定します。

strict mode でない場合、this が undefined や null の場合は、this はグローバルオブジェクトで置き換えられます(グローバルオブジェクトを参照するように変換されてしまいます)。

第二引数

  • apply() の場合は、関数の引数を配列として渡します(ECMAScript 5 以降では配列や配列のようなオブジェクト)。

  • call() の場合は、第二引数以降には呼び出す関数の引数を指定します。

call() と apply() の違いは呼び出す関数の引数の指定方法(第二引数)だけです。

引数のリストとして配列や配列のようなオブジェクトを渡すことができるスプレッド構文(...)を call() で使えば、apply() とほぼ同じです。

const args = [1, 2, 3];
関数.apply(thisの値, args);
関数.call(thisの値, ...args);

但し、実際に args が配列や配列のようなオブジェクトの場合は、apply() のほうがスプレッド構文の処理がないため(多少は)高速になります。

戻り値

this の値と引数を指定して関数を呼び出した結果

使用例

以下の場合、オブジェクト foo には、メソッド greet が定義されていますが、オブジェクト bar には定義されていません。そのため、bar.greet() を実行するとエラーになります。

const foo = {
  name : 'Foo',
  //メソッドの短縮記法
  greet() {
    console.log('Hello, my name is ' + this.name);
  }
}
// foo の greet() メソッドの呼び出し
foo.greet();  //Hello, my name is Foo

const bar = {
  name : 'Bar'
}
// bar の greet() メソッドの呼び出し(存在しないのでエラー)
bar.greet(); //Uncaught TypeError: bar.greet is not a function

この場合、以下のように apply() や call() の第一引数(thisの値)に bar を指定して、foo の greet() メソッドを bar として呼び出すことができます。

const foo = {
name : 'Foo',
  greet() {
    console.log('Hello, my name is ' + this.name);
  }
}
const bar = {
  name : 'Bar'
}
// foo の greet() メソッドを bar として呼び出す(this の値として bar を指定)
foo.greet.apply(bar); //Hello, my name is Bar
foo.greet.call(bar); //Hello, my name is Bar

上記の例では、関数に引数を渡さないので apply() と call() では全く同じでしたが、以下は引数を渡す例です。apply() には第二引数に配列(または配列のようなオブジェクト)で指定し、call() には第二引数以降に指定します。

const foo = {
name : 'Foo',
  //引数を受け取る
  greet(greeting) {
    console.log(greeting + ', my name is ' + this.name);
  }
}
const bar = {
  name : 'Bar'
}

//関数へ渡す引数は配列(または配列のようなオブジェクト)で指定
foo.greet.apply(bar, ['Good morning']); //Good morning, my name is Bar
//関数へ渡す引数は第二引数以降に指定
foo.greet.call(bar, 'Good afternoon'); //Good afternoon, my name is Bar

arguments オブジェクト(関数に渡された引数の値)は配列のようなオブジェクトで、配列ではないので配列のメソッド Array.prototype.join() は使えません。以下はエラーになります。

const joinArgs = function() {
//arguments は配列ではないので配列の join() メソッドは使えない
console.log(arguments.join('+'));
}

joinArgs('a','b','c'); //Uncaught TypeError: arguments.join is not a function

call() や apply() を使うことで配列の join() メソッドを arguments に対して使用することができます。

join() には引数に区切り文字を指定できるので、この例の場合は call() を使用しています。call() の第一引数(this の値)には、arguments オブジェクトを指定します。

const joinArgs = function() {
  //call() の第一引数には arguments オブジェクトを、第二引数には join() へ渡す引数を指定
  console.log(Array.prototype.join.call(arguments, '+'));
}
joinArgs('a','b','c');  //a+b+c

ちなみに、アロー関数は arguments オブジェクトを参照できませんが、以下のように Rest パラメータを使って関数に渡された引数の値を配列として受け取ることができます(上記の場合も、この方法を使えば call() や apply() を使う必要はありませんが)。

const joinArgs = (...args) => {
  //Rest パラメータを使って引数の値を配列として受け取る
  console.log(args.join('+'));
}
joinArgs('a','b','c');  //a+b+c

this の値が不要な場合は null を渡す

以下は、関数 multiply に配列を渡して呼び出すために apply() を使用していますが、this の値を指定する必要はないので、apply() の第一引数に null を指定しています(multiply(array) ではエラーになります)。

const multiply = (x,y)=> {
  return x * y;
}
const array =  [ 3, 6 ];
//this の値が不要な場合は null を渡す
console.log(multiply.apply(null, array)); //18

以下は配列内の数値の最大値を Math.max ビルトイン関数を使って取得する例です。

Math.max() は引数に数値のリストを受け取るので、そのままでは配列を渡せませんが、 apply() は引数に配列を受け取るので、以下のように記述することができます。

const numbers = [3, 61, 25, 3373, 4795, 70, 1289];
//this の値が不要な場合は null を渡す
const maxVal = Math.max.apply(null, numbers);

console.log(maxVal); //4795

上記はスプレッド構文を使っても簡単に記述できます。

const numbers = [3, 61, 25, 3373, 4795, 70, 1289];
//スプレッド構文で配列を展開して Math.max に渡す
const maxVal = Math.max(...numbers);
console.log(maxVal); //

NodeList や HTMLCollection で配列のメソッドを使う

要素を取得するメソッドの戻り値は配列のようなオブジェクト(NodeList や HTMLCollection)なので、直接配列のメソッドを使用することはできませんが、この場合も call() や apply() を使って配列のメソッドを実行することができます。

以下はクラス属性が foo の要素を全て取得して、先頭の要素を取り出す例です。配列の slice() メソッドを使ってコピーした要素リストは、通常の配列になっているので、shift() で先頭の要素を取り出すことができます。

// .foo の要素を全て取得(HTMLCollection)
const foo = document.getElementsByClassName('foo');
// call() を使って配列のメソッド slice() を引数なしで実行して配列として切り出す
const fooArray = Array.prototype.slice.call(foo);
// fooArray は配列なので shift() が使える
const fooFirstElem = fooArray.shift();
console.log(fooFirstElem.innerHTML);

上記では call() を使用していますが、この場合は apply() でも同じです。

const bar = Array.prototype.slice.apply(document.querySelectorAll('.bar')).shift();

関連項目:配列のようなオブジェクトを配列に変換

bind()

bind() を使うと、apply() や call() と同様、this を制御することができます。

但し、bind() は this の値を束縛(bind)した新しい関数を作成するメソッドなので、処理は別途実行する必要があります。

以下が構文です。call() と同じです(戻り値が異なります)。

関数.bind(thisの値, 関数の引数1, 関数の引数2, ...);

第一引数

関数が呼び出されたときに this として使用される値(参照されるオブジェクト)を指定します。言い換えると、呼び出す関数の本体で this となるオブジェクトを指定します。

strict mode でない場合、this が undefined や null の場合は、this はグローバルオブジェクトで置き換えられます(グローバルオブジェクトを参照するように変換されてしまいます)。

第二引数

第二引数以降には呼び出す関数の引数を指定します。

戻り値

this の値と初期の引数(提供された場合)が指定された関数のコピー

使用例

以下はオブジェクトのメソッドを実行する例です。

この例では use strict でストリクトモードを指定していません。use strict を指定していないと、this が undefined の場合、this がグローバルオブジェクトを参照します。

当然ですが、オブジェクト foo の greet() メソッドの呼び出し foo.greet() では、foo オブジェクトの name プロパティを参照して Hello, my name is Foo と出力します。

foo.greet を変数に代入してから関数を呼び出すと、呼び出し元のオブジェクトが foo からグローバルオブジェクトに切り替わるため、this はグローバルオブジェクト(Window)を参照してしまいます。

Window.name はデフォルトでは空なので、 Hello, my name is と出力されます。

//ストリクトモードの指定なし
//オブジェクト
const foo = {
  name : 'Foo',
  //オブジェクトのメソッド(短縮記法)
  greet() {
    //console.log(this);
    console.log('Hello, my name is ' + this.name);
  }
}
// foo の greet() メソッドの呼び出し
foo.greet();  //Hello, my name is Foo

// foo.greet を一度変数に代入してから関数を呼び出す(呼び出し元が変わる)
const myGreet = foo.greet;
//グローバルオブジェクトの name を参照(name は空)
myGreet();  // Hello, my name is

7行目のコメントアウトを外して this の値を確認すると、コンソールには以下のように出力されます。

以下は use strict でストリクトモードを指定した例です。この場合、this は undefined になる(this を失う)ので、name が参照できず以下のようにエラーになります。

"use strict";  //ストリクトモードを指定
const foo = {
  name : 'Foo',
  greet() {
    //console.log(this);
    console.log('Hello, my name is ' + this.name);
  }
}
foo.greet();  //Hello, my name is Foo
const myGreet = foo.greet;
myGreet();  // this.name が参照できず以下のエラーになる
//Uncaught TypeError: Cannot read properties of undefined (reading 'name')

5行目のコメントアウトを外して this の値を確認すると、コンソールには以下のように出力され、this が undefined になっている(this が失われている)のが確認できます。

以下は bind() を使って this で参照するオブジェクトを指定する例です。

this で参照するオブジェクトを foo にしているので、foo の name プロパティが参照されます。

"use strict";
const foo = {
  name : 'Foo',
  greet() {
    console.log('Hello, my name is ' + this.name);
  }
}
foo.greet();  //Hello, my name is Foo

// bind() を使って this を使った時に参照させたいオブジェクトを指定
const myGreet = foo.greet.bind(foo);
// foo の name プロパティが参照される
myGreet();  // Hello, my name is Foo

以下は greet() メソッドを持たないオブジェクト bar の name プロパティを参照させる例です。

foo.greet に bar をバインドしています。

"use strict";
const foo = {
  name : 'Foo',
  greet() {
    console.log('Hello, my name is ' + this.name);
  }
}
foo.greet();  //Hello, my name is Foo

// greet() メソッドを持たないオブジェクト
const bar = {
  name : 'Bar'
}

//参照させたいオブジェクトに bar を指定
const barGreet = foo.greet.bind(bar)
barGreet();  //Hello, my name is Bar

//以下と同じこと
foo.greet.apply(bar); //Hello, my name is Bar
foo.greet.call(bar)  //Hello, my name is Bar

以下は引数を渡す例です。使い方は call() と同じです。

"use strict";
const foo = {
  name : 'Foo',
  greet(greeting) {
    console.log(greeting + ', my name is ' + this.name);
  }
}
const bar = {
  name : 'Bar'
}
// 引数を渡す
const barGreet = foo.greet.bind(bar, 'Good afternoon');
barGreet();  //Good afternoon, my name is Bar
// 以下と同じ
foo.greet.call(bar, 'Good afternoon');  //Good afternoon, my name is Bar

setTimeout の利用

前述のオブジェクトのメソッドを変数に代入して使用した場合のように、あるメソッドがオブジェクトから別の場所に渡されると this は失われます。

setTimeout を利用する場合も、同様のことが起きます。

以下の場合、this が失われ、Hello,my name is undefined と1秒後に出力されます。これは、setTimeout がオブジェクトとは別に関数 foo.hello を取得したためです。

"use strict";
const foo = {
  userName : 'Foo',
  hello() {
    //console.log(this);
    console.log(`Hello,my name is ${this.userName}`);
  }
}
setTimeout(foo.hello, 1000); //Hello,my name is undefined

5行目のコメントを外すと、以下のようにコンソールに出力されます。

ブラウザにおいて、setTimeout は特別で、関数呼び出しでは this に window を設定します。そのため、この場合、this.userName は、存在しない window.userName を取得しようとします。他のケース(setTimeout 以外)では、通常 this は undefined になります。

上記の最後の行(9行目)は以下のように記述したのと同じことです。

const fooHello = foo.hello; //foo.hello を一度変数に代入
setTimeout(fooHello, 1000); //Hello,my name is undefined

以下のように bind() を使って foo.hello をオブジェクト foo にバインドすれば、期待通りに動作します。

const foo = {
  userName : 'Foo',
  hello() {
    console.log(`Hello,my name is ${this.userName}`);
  }
}
// foo.hello の this を foo にバインド
setTimeout(foo.hello.bind(foo), 1000); //Hello,my name is Foo

引数のバインド

bind() は this だけでなく、引数もバインドすることができます。

以下のように bind() を使って、関数の開始引数をバインドすることができます。

以下の場合、 の第一引数の this の値(コンテクスト)は必要ないので、null にしています。

const multiple = (a, b) => {
  return a * b;
}

//開始引数を 2 で固定した multiple で double を作成
const double = multiple.bind( null, 2 );

console.log( double(3) ); // = multiple(2, 3) = 6
console.log( double(4) ); // = multiple(2, 4) = 8
console.log( double(5) ); // = multiple(2, 5) = 10

既存のパラメータのいくつかを固定にすることで新しい関数を作成することができます。

参考サイト: javascript.info 関数バインディング

配列のメソッドを使う

要素を取得するメソッドが返す値(要素の集合)は HTMLCollectionNodeList という配列のようなオブジェクトなので、配列(Array)のメソッドを直接使うことができません。

HTMLCollection や NodeList などの配列のようなオブジェクトで配列のメソッドを使うには、call や apply を利用します。

または配列のようなオブジェクトを配列に変換して、配列のメソッドを使うという方法もあります。

call() を使って配列のメソッドを呼び出す

以下は、getElementsByClassName() を使ってクラスが foo の要素(HTMLCollection)を取得し、call() を使って配列(Array.prototype)のメソッド forEach() を呼び出す例です。

var foo = document.getElementsByClassName('foo');  //HTMLCollection

Array.prototype.forEach.call(foo, function (elem, index) {
  console.log( index + ' : ' + elem.textContent );
}); 

call() を使うとあるオブジェクトのメソッドであるかのように関数を呼び出すことができます。上記の場合、forEach() を foo のメソッドとして呼び出しています。

Array.prototype の代わりに空の配列リテラル [] を使って以下のように記述することもできます(※ 但し、Array.prototype の方が余分な配列オブジェクト [] の作成を避けることができるので効率的?)。

//Array.prototype.forEach の代わりに [].forEach と記述
[].forEach.call(foo, function (elem, index) {
  console.log( index + ' : ' + elem.textContent );
}); 

取得した HTMLCollection を変数に格納する必要がなければ、以下のように続けて記述できます。

Array.prototype.forEach.call(document.getElementsByClassName('foo'), function (elem, index) {
  console.log( index + ' : ' + elem.textContent );
}); 

以下は配列のメソッド filter() を使って要素の種類が div の場合は配列 foo_div に格納して、配列のメソッド forEach() を使う例です。

var foo = document.getElementsByClassName('foo');  //HTMLCollection

//filter で div 要素を抽出して配列に格納
var foo_div = Array.prototype.filter.call(foo, function (elem) {
  return elem.tagName === 'DIV';
});

//foo_div は配列なので、forEach() を直接使える
foo_div.forEach(function(elem, index) {
 console.log( index + ' : ' + elem.textContent);
});
    

配列のようなオブジェクトを配列に変換

要素を取得するメソッドの戻り値は配列のようなオブジェクト(NodeList や HTMLCollection)なので、直接配列のメソッドを使用することはできませんが、call() などを使って配列のメソッドを利用することができます。

NodeList を返すメソッドの例

  • document.getElementsByName(IE および Edge では HTMLCollection)
  • document.querySelectorAll
  • Node.childNodes

HTMLCollection を返すメソッドの例

  • document.getElementsByTagName
  • document.getElementsByClassName
  • document.forms

call() と配列のメソッド slice() を使えば、HTMLCollection や NodeList などの配列のようなオブジェクトから、新たに配列を生成することができます。

以下は配列のようなオブジェクトを配列に変換してから配列のメソッドを使用します。一度配列に変換する必要がなければ、前述の call() を使って配列のメソッドを呼び出す方がシンプルです。

以下は querySelectorAll() を使ってセレクタにマッチする全ての要素を取得した NodeList から配列を生成する例です。マッチする要素がない場合は空の配列を返します。

Array.prototype.slice.call(document.querySelectorAll(セレクタ) || []);

例えば、ページの全ての a 要素を取得する場合、getElementsByTagName を使って以下のように記述します。

var anchors = document.getElementsByTagName('a');

上記で取得した a 要素の集まり anchors は HTMLCollection(ブラウザによっては NodeList)という配列のようなオブジェクトで配列ではありません。

call() を使って anchors に配列のメソッド slice() を実行すると、anchors から要素のみを抽出して新たに配列を生成することができます。slice() を引数なしで実行すると、インデックス 0 から最後 (array.length) までを切り出した配列を返します。

また、Array.prototype の代わりに [] を使って記述することもできます。

//配列のようなオブジェクトを取得
var anchors = document.getElementsByTagName('a');

//配列のようなオブジェクトを配列に変換
var anchors_array = Array.prototype.slice.call(anchors);

//または以下のように記述することもできます(記述は短くなるが Array.prototype の方が効率的)。
var anchors_array = [].slice.call(anchors);

上記はまとめて以下のように記述することもできます。

var anchors_array = Array.prototype.slice.call( document.getElementsByTagName('a'));

//または
var anchors_array = [].slice.call( document.getElementsByTagName('a')); 

上記の anchors_array は配列なので、配列のメソッドを使用することができます。

以下は、ページの全ての a 要素を取得して、配列のメソッド forEach() を使ってその要素の target 属性が _blank の場合はインデックス番号と href 属性の値を出力する例です。

var anchors_array = Array.prototype.slice.call( document.getElementsByTagName('a'));

anchors_array.forEach(function(elem, index) {
  if(elem.target ==='_blank') {
    console.log(index + ': ' + elem.href);
  }
});

//変数 anchors_array に格納する必要がなければ以下のように記述できます。
Array.prototype.slice.call( document.getElementsByTagName('a')).forEach(function(elem, index) {
  if(elem.target ==='_blank') {
    console.log(index + ': ' + elem.href);
  }
});

以下のように、call() を使って配列のようなオブジェクトに forEach() を実行させても同じことです。

Array.prototype.forEach.call(document.getElementsByTagName('a'), function (elem, index) {
  if(elem.target ==='_blank') {
    console.log(index + ': ' + elem.href);
  }
});
ES6 Array.from() メソッド

ES6(ES2015)から導入されている Array.from() メソッドを使えば、配列のようなオブジェクトから簡単に配列を生成することができます。

例えば、getElementsByTagName() を使って取得した配列のようなオブジェクトから配列を生成するには以下のように記述できます。

var anchors_array = Array.from(document.getElementsByTagName('a'));  //配列

以下の Array.from() を使わない場合と比べると簡潔に記述できるのがわかります。

var anchors_array = Array.prototype.slice.call( document.getElementsByTagName('a'));  //配列
ES6 スプレッド演算子

ES6 のスプレッド演算子( ... )を使っても配列のようなオブジェクトから簡単に配列を生成することができます。

var anchors_array = [...document.getElementsByTagName('a')];

alert(Array.isArray(anchors_array));  //true

配列のようなオブジェクト

配列のようなオブジェクト(array-like object)とは length プロパティとインデックスでアクセスできる要素を持つ(配列のように扱えるが配列ではない)オブジェクトのことです。

機能 配列 配列のようなオブジェクト
インデックス(添字 [n])でのアクセス 可能 可能
length プロパティ(長さ) ある ある
配列のメソッド 使用可能 使用できない(※)

各要素にはインデックスでアクセスでき、length プロパティがあるので for 文を使ってそれぞれの要素にアクセスして処理を行えます。

<input type="checkbox" name="music" value="jazz" checked> Jazz
<input type="checkbox" name="music" value="hiphop"> Hip Hop
<input type="checkbox" name="music" value="classic"> Classic

<script>
  //name="music" の input 要素の集まり(NodeList:配列のようなオブジェクト)
  const myCheckbox = document.querySelectorAll('input[name="music"]');

  //インデックス(i)と length プロパティを使って for 文を実行
  for(let i=0; i<myCheckbox.length; i++) {
    //value プロパティの値をコンソールに出力
    console.log(myCheckbox[i].value);  //jazz hiphop classic
  }

  //myCheckbox が配列かどうか
  console.log(Array.isArray(myCheckbox));  //false
</script> 

※ 配列のようなオブジェクトは配列ではないので、配列のメソッドは使用できません。

但し、オブジェクトによっては独自に同じ名前のメソッド(例 forEach)を持っているものもあります。また、オブジェクトによっては「配列のようなオブジェクト」であり、且つ「反復可能オブジェクト」であるものもあります。

例えば、querySelectorAll() の戻り値は Nodelist なので、forEach() というメソッドを持っていますが、 getElementsByClassName() の戻り値は HTMLCollection で forEach() というメソッドを持っていないため使用するとエラーになります。

var nodelist = document.querySelectorAll('.foo');

var htmlCollection = document.getElementsByClassName('foo');

//Nodelist は forEach メソッドを持っているので使用可能
nodelist.forEach(function(elem) {
  console.log(elem); //<p class="foo"></p>
});

//HTMLCollection は forEach メソッドを持っていないのエラーになる
htmlCollection.forEach(function(elem) {
  console.log(elem); //以下のエラー
  //Uncaught TypeError: htmlCollection.forEach is not a function
});

配列のようなオブジェクトで配列のメソッドを使用するには call() を使って配列のメソッドを呼び出すか、配列のようなオブジェクトを配列に変換する必要があります。

例えば以下のような HTML がある場合、

<p class="foo"></p>
<p class="foo"></p>

querySelectorAll() と getElementsByClassName() を使ってクラス foo の要素を取得すると、それぞれ Nodelist と HTMLCollection のオブジェクトを取得できます。

ブラウザのインスペクタで、NodeList(2) や HTMLCollection(2) の部分をクリックするとそのオブジェクトの概要が展開されます。

var nodelist = document.querySelectorAll('.foo');
console.log(nodelist);

var htmlCollection = document.getElementsByClassName('foo');
console.log(htmlCollection);

//出力例
NodeList(2) [p.foo, p.foo]  //この部分をクリックすると以下が展開
0: p.foo  //index と要素
1: p.foo
length: 2  //length プロパティ
__proto__: NodeList

HTMLCollection(2) [p.foo, p.foo]
0: p.foo
1: p.foo
length: 2
__proto__: HTMLCollection

更に、__proto__ の部分をクリックすると __proto__ プロパティ(オブジェクトの内部のプロパティ)が展開され、Nodelist には forEach メソッドがあることが確認できます。

また、両方とも Symbol(Symbol.iterator) というプロパティを持っていて、反復可能オブジェクト(iterable オブジェクト)でもあることがわかります(for of 文が使用可能)。

配列かどうかの判定 isArray()

Array.isArray() メソッドを使用すれば、配列かどうかを判定することができます。配列のようなオブジェクトは配列ではないので結果は false になります。

var arr = [1,2,3];
console.log(Array.isArray(arr));  //true

var nodelist = document.querySelectorAll('.foo');
console.log(Array.isArray(nodelist));  //false

var htmlCollection = document.getElementsByClassName('foo');
console.log(Array.isArray(htmlCollection));  //false

ES6 for...of 文

ES6(ES2015)から導入されている for...of 文を使うと反復可能オブジェクトである HTMLCollection や NodeList に対して要素を1つずつ取り出して繰り返し処理を行うことができます。

<p class="foo">Foo 1</p>
<div class="foo">Foo 2</div>
<p><span class="foo">Foo 3</span></p>

<script>
let foo = document.getElementsByClassName('foo');

for (let elem of foo) {
  console.log(elem.tagName + ': ' + elem.textContent);
}
</script>

/* 出力
P: Foo 1
DIV: Foo 2
SPAN: Foo 3
*/

実行タイミング

Web ページを表示する際、HTML のソースコードは上から下へ順番に読み込まれて解析されていきます(HTML パース)。

HTML パースでは JavaScript も含まれ、JavaScript を記述する位置(実行タイミング)により結果が異なる場合があります。

例えば、以下の2つの場合、JavaScript を読み込んで実行する時点では p 要素は読み込まれていない(DOM の解析が完了していない)のでエラーになります。

<head>
<script>
//この時点では DOM のレンダリングが完了しておらず該当の p 要素にアクセスできない
document.getElementById('sample').style.setProperty('color', 'blue');
</script>
</head>
<body>
  <p id="sample">Sample</p>
</body>
<head>
</head>
<body>
  <script>
  //この時点では DOM のレンダリングが完了しておらず該当の p 要素にアクセスできない
  document.getElementById('sample').style.setProperty('color', 'blue');
  </script>
  <p id="sample">Sample</p>
</body>

上記の場合、JavaScript を実行する時点では id が sample の p 要素が読み込まれていないので、document.getElementById('sample') は null になり「Uncaught TypeError: Cannot read property 'style' of null」のようなエラーになります。

以下のように p 要素が読み込まれた後で JavaScript を実行すれば問題なく処理され、この例の場合は id が sample の p 要素の文字色が青色で表示されます。

<body>
  <p id="sample">Sample</p>
  <script>
  //以下が読み込まれる前に上記 DOM のレンダリングが完了しているので p 要素にアクセスできる
  document.getElementById('sample').style.setProperty('color', 'blue');
  </script>
</body>  

body の閉じタグの直前に記述

JavaScript が読み込まれる前に DOM がレンダリングされている必要がある場合は、JavaScript の読み込みを body の閉じタグ </body> の直前に記述すれば、ほとんどの場合は問題ないと思いますが、必ずしも DOM のレンダリングの完了が保証されているわけではありません。

そのため、DOM を操作する場合はイベントを利用することができます。

イベントの利用

DOM を操作する際に利用できるイベント(一部)には次のようなものがあります。

イベント 説明
DOMContentLoaded ブラウザが HTML を完全に読み込み、DOM ツリーが構築された際に発生(次の load より先に発生)。※画像(img 要素)やスタイルシートはまだ読み込まれていない可能性があります。
load ブラウザがすべてのリソース(画像, スタイルなど)の読み込みを完了した時点で発生。
beforeunload 他のページへ遷移する(リソースがアンロードされる)直前に発生。
unload 他のページへ遷移する(リソースがアンロードされる)際に発生。beforeunload の後に発生

以下はブラウザにおける JavaScript の処理のおおまかな流れです。

  1. Window オブジェクトを生成し、Window オブジェクトのプロパティとして Document オブジェクトが生成される
  2. Document オブジェクトは DOM ツリーを読み込んで解析が終わると DOMContentLoaded イベントが発生
  3. DOM ツリーの構築完了後、画像やスタイルシートなどのリソースの読み込みすべてが完了すると Window オブジェクトに対して load イベントが発生

DOMContentLoaded イベント

DOMContentLoaded は DOM ツリーの構築(解析)が完了した時点で発生するイベントです。

画像やスタイルシートの読み込みを待つ必要がない場合は、DOMContentLoaded イベントを利用します。

このイベントの対象は読み込まれた Document なので、Document に addEventListener を使ってイベントリスナーを登録します(Window に登録することもできます)。

document.addEventListener('DOMContentLoaded', function(){
    //処理の記述
});

前述のエラーになる2つの例は、以下のように記述すればエラーにならずに処理できます。

但し、基本的には JavaScript は head 内に記述しないほうが良いと思います。

<head>
<script>
//DOM ツリーが構築されたら実行 ※画像やスタイルシートの読み込み前
document.addEventListener('DOMContentLoaded', function(){
  document.getElementById('sample').style.setProperty('color', 'blue');
});
</script>
</head>
<body>
  <p id="sample">Sample</p>
</body>
<head>
</head>
<body>
  <script>
  //DOM ツリーが構築されたら実行 ※画像やスタイルシートの読み込み前
  document.addEventListener('DOMContentLoaded', function(){
    document.getElementById('sample').style.setProperty('color', 'blue');
  });
  </script>
  <p id="sample">Sample</p>
</body>

window と document

DOMContentLoaded を window と document に登録した場合の実行の順序は以下のようになります。

参考サイト:Window vs. Document Loading Events

window.addEventListener('DOMContentLoaded', () => {
  console.log('window に登録 - useCapture に true を指定'); // 1st
}, true);

document.addEventListener('DOMContentLoaded', () => {
  console.log('document に登録 - useCapture に true を指定'); // 2nd
}, true);

document.addEventListener('DOMContentLoaded', () => {
  console.log('document に登録 '); // 2nd
});

window.addEventListener('DOMContentLoaded', () => {
  console.log('window に登録 '); // 3rd
});

script の読み込み

ブラウザが HTML を読み込んで script タグ(src を持つ外部スクリプトを含む)に遭遇すると(すぐにスクリプトを実行する必要があるため)DOM の構築を続けることができず DOM の解析を一時的に中断して、そのスクリプトを実行します。

このため、スクリプトがすべて実行された後に DOMContentLoaded が発生することになり処理が遅くなる可能性があります。

javascript.info :DOMContentLoaded と scripts

できるだけ早く DOM が解析されるようにするには、可能であれば JavaScript を非同期にしたり、外部スクリプトを async や defer 属性を設定して非同期に読み込みます。

スタイルシートの読み込み

DOMContentLoaded はスタイルシートの読み込みを待ちませんが、通常の読み込みの場合、スタイルシートは HTML と並行で読み込まれ DOM 解析の速度を低下させる可能性があります。

可能であれば、スタイルシートの読み込みを最適化します(CSS の配信を最適化する)。

関連ページ:レンダリングを妨げるリソースの除外/CSSを非同期で読み込む

load イベント

load イベントは、DOM ツリー構築完了後、画像やスタイルシートなどが読み込まれ、読み込みのすべてが完了すると Window オブジェクトに対して発生するイベントです。

画像やスタイルシートの読み込みを待つ必要がある場合は、load イベントを利用します(DOMContentLoaded に比べると処理の開始が遅くなります)。

load イベントは Window に addEventListener を使ってイベントリスナーを登録します。

window.addEventListener('load', function(){
    //処理の記述
}); 

画像の load イベント

ロードイベントは画像要素(img)でも発生します。

以下は img 要素に load イベントを登録して、画像が読み込まれた後に画像のサイズを取得する例です。画像の読み込みが完了していないと高さと幅のプロパティ(height と width)の値は 0 になってしまいます。

テンプレートリテラルを使ってコンソールに出力しています。

<script>
function loadimage(src) {
  //img 要素を生成
  let myImg = document.createElement('img');
  //img 要素の src 属性に引数で指定されたパスを設定
  myImg.src = src;
  //画像の load イベント(画像がロードされたら実行)
  myImg.addEventListener('load', function(){
    //画像の height 及び width プロパティの値をコンソールに出力
    console.log(`height:${myImg.height} / width:${myImg.width}`);
  });
}

//上記関数を実行
loadimage('../images/sample.jpg');
</script> 

load イベントは img 要素以外にも script 要素や link 要素でも機能します。

スクリプトを動的に読み込む

スクリプトを動的に読み込むには scipt 要素を作成して src 属性を設定し、ドキュメントに追加します。

以下は Lodash というライブラリを CDN で読み込む例です。

<script>
  //script 要素を作成
  let myScript = document.createElement('script');

  //script 要素に src を設定(任意のドメインから任意のスクリプトがロード可能)
  myScript.src = "https://cdn.jsdelivr.net/npm/lodash@4.17.21/lodash.min.js";

  //body 要素の最後にスクリプト(script 要素)を追加
  document.body.appendChild(myScript);

  //以下は head 要素にスクリプト(script 要素)を追加する場合
  //document.head.appendChild(myScript);
  //以下は古い書き方(document.head をサポートしていない場合)
  //document.getElementsByTagName("head")[0].appendChild(myScript);

</script>

上記を記述すると以下のようなスクリプトが追加されます。

<script src="https://cdn.jsdelivr.net/npm/lodash@4.17.21/lodash.min.js"></script>

スクリプトの load イベント

スクリプトの中で宣言された関数を実行するには、そのスクリプトの読み込みが完了するまで待つ必要があるので、スクリプトの load イベントを利用します。

<script>
  //script 要素を作成
  let myScript = document.createElement('script');

  //script 要素に src を設定(任意のドメインから任意のスクリプトがロード可能)
  myScript.src = "https://cdn.jsdelivr.net/npm/lodash@4.17.21/lodash.min.js";

  //body 要素の最後にスクリプト(script 要素)を追加
  document.body.appendChild(myScript);

  //スクリプトの読み込みが完了したら(script の load イベントを登録)
  myScript.addEventListener('load', function() {
    // 読み込んだスクリプト(Lodash.js)を使って処理を実行
    const users = [
      { id: 1, name: 'Mike' },
      { id: 2, name: 'David' },
      { id: 3, name: 'Lisa' },
      { id: 4, name: 'Maria' },
    ];
    const user = _.find(users, { id: 3 });
    console.log(user.name); //Lisa と出力される
  });

</script>

onload を使って以下のように記述することもできます。

<script>
  let myScript = document.createElement('script');
  myScript.src = "https://cdn.jsdelivr.net/npm/lodash@4.17.21/lodash.min.js";
  document.body.appendChild(myScript);

  // onload を使う
  myScript.onload = function() {
    // 読み込んだスクリプト(Lodash.js)を使って処理を実行
    const users = [
      { id: 1, name: 'Mike' },
      { id: 2, name: 'David' },
      { id: 3, name: 'Lisa' },
      { id: 4, name: 'Maria' },
    ];
    const user = _.find(users, { id: 3 });
    console.log(user.name); //Lisa
  };

</script>

スタイルシートを動的に読み込む

スタイルシートもスクリプトと同様に動的に読み込むことができ、load イベントも使用できます。

以下は動的に head 要素内にスタイルシートを読み込む例です。

<script>
  //link 要素を作成
  let myStyle = document.createElement('link');

  //rel 属性を stylesheet に設定
  myStyle.rel = "stylesheet";
  //href 属性を設定
  myStyle.href = "https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/css/bootstrap.min.css";

  //head 要素にスタイルシート(link 要素)を追加
  document.head.appendChild(myStyle);

  myStyle.addEventListener('load', function() {
    console.log('スタイルを読み込みました');
  })

</script>
window.onload

window.onload は load イベントに対応するイベントハンドラで、前述の load イベントを使った処理は以下のように記述することもできます。

window.onload = function() {
    //処理の記述
};

複数の window.onload は上書きされる

但し、複数の window.onload を記述した場合、上書きされてしまい、最後に記述したイベントだけが実行されるので注意が必要です。

以下の場合、最後の console.log() のみが実行されます。

window.onload = function() {
  console.log('hello foo!');
};
window.onload = function() {
  console.log('hello bar!');
};
window.onload = function() {
  console.log('hello baz!'); //hello baz!
};
//最後の hello baz! のみが出力される

以下のように addEventListener で load イベントを使えば、全て実行されます。

window.addEventListener('load', function(){
  console.log('hello foo!');  //hello foo!
});

window.addEventListener('load', function(){
  console.log('hello bar!');  //hello bar!
});

window.addEventListener('load', function(){
  console.log('hello baz!');  //hello baz!
}); 

Document.readyState

当然ですが、ドキュメントがロードされた後に DOMContentLoaded や load イベントを設定しても実行されません。

ドキュメントが準備できたかどうかが定かでない場合、document.readyState プロパティをチェックすることでドキュメントの現在の状態を知ることができます。

document.readyState プロパティは、その文書の読み込み状態を示し、状態により以下の値を取ります。

説明
loading ドキュメントがロード中(まだ読み込み中)
interactive ドキュメントは読み込みが終わって解析が済みましたが、画像、スタイルシート、フレームなどのリソースはまだ読み込み中。DOMContentLoaded とほぼ同時に発生しますが、その前に発生します。
complete ドキュメントとすべてのリソースの読み込みが終了。この状態は load イベントがもうすぐ発火することを意味します。window.onload とほぼ同時に発生しますが、その前に発生します。

以下を記述すると順番にコンソールにメッセージが出力されます。

function checkReadyState() {
  switch (document.readyState) {
    case "loading":
      console.log('readyState:loading');
      break;
    case "interactive":
      console.log('readyState:interactive');
      break;
    case "complete":
      console.log('readyState:complete');
      break;
  }
}

checkReadyState();  //readyState:loading

document.addEventListener('DOMContentLoaded', function(){
  checkReadyState();  //readyState:interactive
});

window.addEventListener('load', function(){
  checkReadyState();  //readyState:complete
});

また、このプロパティの値が変化するとき、readystatechange イベントが document オブジェクト上で発生します。言い換えると、読み込みの状態が変わったときに readystatechange イベントが発生します。

以下は readystatechange イベントが発生したらコンソールにメッセージとイベント(event.target.readyState)を出力する例です。

document.addEventListener('readystatechange', function(event) {
  console.log('Triggerd: ' + event.target.readyState);
});

//出力結果
/*
Triggerd: interactive
Triggerd: complete
*/

以下は前述の2つを一緒に実行した例です。

checkReadyState(); //前述の readyState の値により出力を変える関数

//DOMContentLoaded イベントで関数を実行
document.addEventListener('DOMContentLoaded', function(){
  checkReadyState();
});

//load イベントで関数を実行
window.addEventListener('load', function(){
  checkReadyState();
});

//readystatechange  イベントでイベント名を出力
document.addEventListener('readystatechange', function(event) {
  console.log('Triggerd: ' + event.target.readyState);
});

//出力結果
/*
readyState:loading
Triggerd: interactive  //readystatechange イベント
readyState:interactive  //DOMContentLoaded イベント
Triggerd: complete  //readystatechange イベント
readyState:complete  //load イベント
*/

DOMContentLoaded イベントの代替として readystatechange を使う

document.onreadystatechange = function () {
  if (document.readyState === 'interactive') {
    //DOMContentLoaded とほぼ同時だがその前に行う処理
  }
}

load イベントの代替として readystatechange を使う

document.onreadystatechange = function () {
  if (document.readyState === 'complete') {
    //window.onload とほぼ同時だがその前に行う処理
  }
}

readystatechange をイベントリスナーとして状態により処理を変える

document.addEventListener('readystatechange', event => {
  if (event.target.readyState === 'interactive') {
    //DOMContentLoaded とほぼ同時だがその前に行う処理
  }
  else if (event.target.readyState === 'complete') {
    //window.onload とほぼ同時だがその前に行う処理
  }
});

参考サイト: