以下は 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)を取得 |
ドキュメント(文書)から指定された ID に一致する要素(Element オブジェク)を取得します。
要素の ID は固有なので、特定の要素にすばやくアクセスすることができます。
var element = document.getElementById(id);
// id が foo の要素の文字色を赤にvar elem = document.getElementById('foo');elem.style.color = 'red';
MDN:getElementById()
指定されたタグ名を持つ要素の集まり(HTMLCollection)を取得します。
但し、WebKit ブラウザーでは NodeList を取得します。
var elements = document.getElementsByTagName(tagName);
var elements = element.getElementsByTagName(tagName)
//ドキュメント内の 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>
指定した name 属性を持つ要素の集まり(NodeList)を取得します。
var elements = document.getElementsByName(name);
//name 属性が submit の要素の NodeList を取得var elems = document.getElementsByName("submit");//1番めの要素の色を赤にelems[0].style.color = 'red';//以下でも同じelems.item(0).style.color = 'red';
指定されたクラス名を持つすべての要素の集まり(HTMLCollection)を取得します。
document オブジェクトに対して呼び出したときは、ルートノードを含む文書全体が検索され、任意の要素に対して呼び出した場合は、指定されたルート要素下が検索されます。
var elements = document.getElementsByClassName(names);
var elements = element.getElementsByClassName(names);
//クラス名が bar の要素を取得var elems = document.getElementsByClassName('bar');//取得した最初の要素の色を赤にelems[0].style.color = 'red';//foo クラスと bar クラス(の両方)を持っている全ての要素を取得var foo_bars = document.getElementsByClassName('foo bar');
指定された CSS セレクターに一致する文書内(document)の最初の要素(HTMLElement オブジェク)を取得します。一致するものが見つからない場合は null を返します。
document の代わりに対象の要素(baseElement)のメソッドとして呼び出した場合は、対象要素の子孫の中のマッチする最初の要素を取得します。
var element = document.querySelector(selectors);
var element = baseElement.querySelector(selectors);
//container クラスの div 要素の中の最後の p 要素を赤色にvar elem = document.querySelector('div.container p:last-child');elem.style.color = 'red';
指定された CSS セレクターに一致する文書中の要素の集まり(静的な NodeList)を取得します。
document の代わりに対象の要素(baseElement)のメソッドとして呼び出した場合は、対象要素の子孫の内のマッチする要素の集まり(静的な NodeList)を取得します。
var elementList = document.querySelectorAll(selectors);
var elementList = baseElement.querySelectorAll(selectors);
//セレクタ(.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>
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 のプロパティとして定義されています。
プロパティ | 説明 |
---|---|
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 |
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_formconsole.log(document.forms[1].id); //second_form//name 属性でフォーム要素を取得(アクセス)することもできます。console.log(document.forms.foo.id); //first_formconsole.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 は HTML 要素(element オブジェクト)の集まりから成る配列のようなオブジェクトで、各要素にはインデックス番号でアクセスできます。
配列のようなオブジェクトとは length プロパティとインデックス付けされた要素を持つオブジェクトのことです。(参考ページ:Array-likeオブジェクト)
また、HTMLCollection は元になった document が変更された時点で自動的に更新される(DOM に対する変更がリアルタイムで反映される)ライブオブジェクトです。
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: DIV1番目: nodeType: 1 nodeName: H32番目: nodeType: 1 nodeName: Plength番目: 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: DIVnodeType: 1 nodeName: H3nodeType: 1 nodeName: P-->
NodeList は配列のような DOM 要素(ノード)の集合を表すオブジェクトです。Node.childNodes などのプロパティや document.querySelectorAll() メソッドの戻り値は NodeList オブジェクトです。
配列のようなオブジェクトとは length プロパティとインデックス付けされた要素を持つオブジェクトのことです。
但し、Node.childNodes のプロパティは元になった document が変更された時点で自動的に更新される(DOM に対する変更がリアルタイムで反映される)ライブオブジェクトですが、querySelectorAll() は静的な(DOM 内の変更が内容に影響を与えない)NodeList オブジェクトを返します。
NodeList は以下のようなプロパティとメソッド(以下は一部)を提供します。
プロパティ | 説明 |
---|---|
length | NodeList に含まれる要素数(アイテム数) |
メソッド | 説明 |
item() | 指定された index (先頭はゼロ) 位置にある特定の要素を返します。index が範囲外なら null を返します。nodeList[i] のアクセスの代替手段です 。 |
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 でも可能です。
//クラス 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: DIV1: H32: 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); // #documentconsole.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)と呼び、ノードには、要素ノード、テキストノード、属性ノードなどがあります。
ノードには以下のようなプロパティが提供されています。
プロパティ (参照) | 取得するノードや要素 |
---|---|
parentNode | このノードの親ノード(存在しない場合は null) |
parentElement | このノードの親要素 Element(存在しない場合は null) |
childNodes | このノードの全ての子孫を含む NodeList(ライブオブジェクト) |
firstChild | ノードの直下の最初の子ノード(存在しない場合は null) |
lastChild | ノードの直下の最後の最後の子ノード(存在しない場合は null) |
previousSibling | ツリー構造で1つ前の兄弟ノード(存在しない場合は null) |
nextSibling | ツリー構造で次の兄弟ノード(存在しない場合は null) |
プロパティ (その他) | 説明 |
nodeName | Node の名前(名前の構造はノードの型によって異なります) |
nodeValue | ノードの値を取得または設定 |
textContent | 要素のテキストコンテンツを取得または設定 |
nodeType | ノードの型を表す値(数値)
|
また、空白ノードやコメントノードを除外して要素だけを取得するための以下のようなプロパティが提供されています。
プロパティ | 説明 |
---|---|
childElementCount | 子要素の数を返します。 |
children | 空白ノードやコメントノードを除外した全ての子要素(Element)を含むリスト HTMLCollection(ライブオブジェクト)を返します。 |
firstElementChild | 最初の子要素(Element)を返します。存在しない場合は null |
lastElementChild | 最後の子要素(Element)を返します。存在しない場合は null |
以下の兄弟要素への参照のプロパティも使えます。
プロパティ | 説明 |
---|---|
previousElementSibling | 1つ前の兄弟要素を返します。存在しない場合は null |
nextElementSibling | 次の兄弟要素を返します。存在しない場合は null |
Element の closest() メソッドも使うことができます。※但し、IE には対応していません。
要素を操作する場合、Element や HTMLElement などのプロパティやメソッドを使うことができます。
Element は Document の中にある全ての要素オブジェクトが継承するクラス(インターフェイス)です。
プロパティ | 説明 |
---|---|
attributes | 要素に関連したすべての属性のリスト NamedNodeMap(ライブオブジェクト) |
classList | class 属性のリスト(ライブオブジェクト)を返します。読み取り専用ですがそのリスト(DOMTokenList)のメソッドの add() や remove() で変更できます。 |
className | 要素のクラス |
clientHeight | 要素の内部の高さ(パディングは含むが、ボーダー、マージン、 垂直スクロールバーは含まない) |
clientWidth | 要素の内部の幅(パディングは含むが、ボーダー、マージン、 垂直スクロールバーは含まない) |
id | 要素の id |
innerHTML | 要素内容のマークアップ |
outerHTML | その要素を内容に含むマークアップ |
scrollTop | 文書の上端が垂直方向にスクロールされた量(要素の内容が垂直にスクロールするピクセル数を取得または設定) |
tagName | 要素のタグ名。タグ名は大文字で表現されます。div 要素なら DIV、p 要素なら P になります。 |
メソッド | 説明 |
---|---|
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() | 指定した座標まで要素をスクロールさせます。 |
HTML 要素は HTMLElement インターフェイスを実装しているので、以下のようなプロパティやメソッドを使うことができます。
プロパティ | 説明 |
---|---|
dataset | 要素に設定された全てのカスタムデータ属性 (data-*) へのアクセスを提供 |
hidden | 要素が hidden か否かを示す真偽値 |
innerText | ノードやその子孫のテキストの内容 |
style | 要素のインライン style の値(style 属性を表すオブジェクトであるCSSStyleDeclaration)。全ての CSS プロパティの値を取得するにはwindow.getComputedStyle() を使用します。 |
tabIndex | 要素の tabIndex の値 |
title | 要素のタイトル |
メソッド | 説明 |
---|---|
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 プロパティで操作します。
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>
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)で使用できるプロパティやメソッドです。
プロパティ | 説明 |
---|---|
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>
対象の要素を取得して、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 プロパティリファレンス
style プロパティで返される値はその要素に設定されているインラインのスタイルを表す 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); //greenconsole.log(fooStyle.fontSize); //18pxconsole.log(fooStyle.fontWeight); //bold// style.getPropertyValue()console.log(fooStyle.getPropertyValue('color')); //greenconsole.log(fooStyle.getPropertyValue('font-size')); //18pxconsole.log(fooStyle.getPropertyValue('font-weight')); //bold//style.cssTextconsole.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:greenfont-size:18pxfont-weight:bold*/</script>
但し、要素の style プロパティは、要素のインラインスタイル(style 属性)で設定された内容しか表していないため、適用されているススタイルシートや継承されたスタイルなどの情報は含まれていません。
要素に適用されている全ての CSS プロパティの値を取得するには、代わりに getComputedStyle() メソッドを使うことができます。
getComputedStyle() メソッドは、要素の算出スタイル(Computed Style)を取得するための API で Window オブジェクトのメソッドです。
window.getComputedStyle() はその要素のアクティブなスタイルを適用して算出したすべての CSS プロパティの値を含む(読み取り専用の)オブジェクトを返します。
var style = window.getComputedStyle(element,pseudoElt);
//以下は 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'));
以下は 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:0sanimation-direction:normalanimation-duration:0sanimation-fill-mode:noneanimation-iteration-count:1animation-name:noneanimation-play-state:runninganimation-timing-function:easebackground-attachment:scrollbackground-blend-mode:normalbackground-clip:border-boxbackground-color:rgba(0, 0, 0, 0)background-image:nonebackground-origin:padding-boxbackground-position:0% 0%background-repeat:repeatbackground-size:autoborder-bottom-color:rgb(0, 128, 0)border-bottom-left-radius:0pxborder-bottom-right-radius:0pxborder-bottom-style:noneborder-bottom-width:0pxborder-collapse:separate・・・以下省略・・・*/
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 要素(タグ)には必要に応じて属性を指定することもできます。但し、現在 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);
CSS を構成する要素は DOM ではオブジェクトで表され、CCS スタイルシートはCSSStyleSheet インターフェースを実装します。
CSSStyleSheet オブジェクトのリスト(StyleSheetList)は、document.styleSheets プロパティを使用して取得でき、document.styleSheets で返されるリストは次の順序で並べられます。
例えば、以下のような head 内で link タグで読み込むスタイルシートと style 要素で記述したスタイル、body 内で style 要素で記述したスタイルがある場合、
<!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>
* { 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);//以下がコンソールへの出力StyleSheetList0: 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)の cssTextconsole.log(myStyleSheets[0].cssRules[0].cssText);//* { margin: 0px auto; padding: 0px; box-sizing: border-box; }//reset.css の2番目のルール(cssRules)の cssTextconsole.log(myStyleSheets[0].cssRules[1].cssText);//::before, ::after { box-sizing: border-box; }//reset.css が無効化されているかどうかconsole.log(myStyleSheets[0] .disabled);//false//reset.css の URIconsole.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)の cssTextconsole.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()メソッドなどがあります。
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);//2console.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, …}
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';
CSSStyleSheet のメソッドinsertRule() を使って CSS ルールをスタイルシートの指定した位置に挿入(追加)することができます。
insertRule()
以下が insertRule() の書式です。
stylesheet は CSS スタイルシートを表わすCSSStyleSheet インターフェイスで、document.styleSheets で取得したリストからインデックスでアクセスするか、style 要素や link 要素のsheet プロパティでスタイルシートにアクセスできます。
stylesheet.insertRule( rule[, index] )
例えば以下のようなスタイルシート(style.css)を読み込んでいる 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>
* { 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 の CSSStyleSheetconst 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 要素の CSSStyleSheetconst 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 の CSSconst styleCss = myStyleSheets[0];//style.css の最後(styleCss.cssRules.length)にルールを追加styleCss.insertRule('div { background-color: #eee; }', styleCss.cssRules.length);//head 要素内の style 要素の CSSconst 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 + 左右 paddingconsole.log('offsetWidth : ' + sampleBox1.offsetWidth); //230//offsetHeight = CSS height + 上下 border + 上下 paddingconsole.log('offsetHeight : ' + sampleBox1.offsetHeight); //130//offsetWidth = CSS width + 左右 paddingconsole.log('clientWidth : ' + sampleBox1.clientWidth); //220//offsetWidth = CSS width + 上下 paddingconsole.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 widthconsole.log('offsetWidth : ' + sampleBox2.offsetWidth); //200//offsetHeight = CSS heightconsole.log('offsetHeight : ' + sampleBox2.offsetHeight); //100//clientWidth = CSS width - 左右 borderconsole.log('clientWidth : ' + sampleBox2.clientWidth); //190//clientHeight = CSS height - 上下 borderconsole.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); //134console.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;}
要素を取得して、そのコンピューテッドスタイルから上下のパディングを取得し、要素の高さからパディングを差し引きます。
要素の高さは 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 を返します。
オフセット座標の取得
要素のオフセット座標は 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 オブジェクトを返します。
プロパティ | 説明 |
---|---|
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 の値が変わります。
<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() でウィンドウ座標を取得し、それらに現在のスクロール量を加えます。
以下はボタンをクリックすると要素のドキュメント座標を取得してアラート表示する例です。
<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.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.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 で指定した座標までスクロール(文書の絶対座標へのスクロール)。
以下のプロパティを持つオブジェクト(ScrollToOptions)を指定可能。
※ IE 未対応 |
window.scrollTo() | window.scroll() と同じ(文書の絶対座標へのスクロール) |
window.scrollBy(X, Y) | window.scrollBy() 現在の位置から指定された量だけスクロール(指定量のスクロール)
|
以下はボタンをクリックすると現在の位置から垂直方向下に 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>
要素のテキストを取得・設定するには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"><p>Sample</p></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 は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 要素は取得されない)TitleThis 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"><p>Sample</p></div>
取得
以下は 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 要素です。
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>
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);
ユーザなどの外部からの入力を出力する場合、値をそのまま出力してしまうと不正なスクリプトが実行されてしまう(クロスサイトスクリプティング)などセキュリティが脆弱になってしまいます。
そのため外部からの入力を出力する場合は、それらの値をサニタイズ(無害化)する必要があります。
textContent やinnerText プロパティ、insertAdjacentHTML()、insertAdjacentText()、 createTextNode() を使って文字列をエスケープすることができます。
JavaScript には PHP のhtmlspecialchars()のようなサニタイジングを行う組み込み関数が用意されていないので、例えば以下のような独自の関数を定義して必要に応じてサニタイズを行うことができます。
以下の関数 h() は「&」「" 」「'」「<」「>」をエスケープします。
//引数に文字列を受け取り特殊文字を変換する関数 h()function h(str){ return str.replace(/&/g, '&') .replace(/"/g, '"') .replace(/'/g, ''') .replace(/</g, '<') .replace(/>/g, '>')}
または、以下でも同様にエスケープします。こちらは replace() を1回呼び出すので少し効率的です。
//引数に文字列を受け取り特殊文字を変換する関数 h()function h(str){ return str.replace(/[<>&'"]/g, (match) => { const specialChars = { "<": "<", ">": ">", "&": "&", "'": "'", '"': """, }; 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, '&') .replace(/"/g, '"') .replace(/'/g, ''') .replace(/</g, '<') .replace(/>/g, '>') } //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 プロパティに設定したものは「<form>」のように表示されます
また、スタイル(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>
関連項目:ノードの作成・追加
要素を作成して追加するには以下のようなメソッドを使うことができます。また、属性の設定は「属性の操作」を参照ください。
メソッド | 説明 |
---|---|
createElement() | 引数に指定したタグ名で新しい要素を生成して、生成した要素を返します |
createTextNode() | 引数に指定した文字列のテキストノードを生成します。このメソッドは HTML 文字をエスケープするのに利用できます |
メソッド | 説明 |
---|---|
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() を使うと指定した要素をその要素の子要素(の末尾)に追加することができます。
追加する要素が既に存在している場合、その要素は現在の親ノードから削除され、新しい親ノードに自動的に追加(移動)されます。既存の要素を複数箇所に追加するには cloneNode() メソッドを使って要素を複製することができます。
以下が appendChild() の書式です。
let aChild = element.appendChild(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()はある要素の前に新たな要素を追加するメソッドで、親となる要素で実行してその直下の子要素のいずれかを指定し、その直前に新たな要素を追加します。
appendChild() 同様、追加する要素が既に存在している場合、その要素は現在の親ノードから削除され、新しい親ノードに自動的に追加(移動)されます。既存の要素を複数箇所に追加するには cloneNode() メソッドを使って要素を複製することができます。
以下が insertBefore() の書式です。
第1パラメータで指定した要素(newNode)を現在の要素(parentNode)の子要素として、第2パラメータで指定した要素(referenceNode:現在の要素の子要素)の前に挿入します。
let insertedNode = parentNode.insertBefore(newNode, referenceNode)
以下は新しい 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 の場合は親の最後の子要素の後に追加されます。実際は親要素が最初の子要素を持っていないということは最後の子要素もないので、挿入された要素は唯一の要素になります。
最新のブラウザでは jQuery のようなメソッド append() や prepend()、after()、before() が使えます。
https://caniuse.com/?search=prepend
メソッド | 説明 |
---|---|
append() | その要素の子要素の末尾に指定した要素(ノード)またはテキストを挿入します。 テキストはテキストノードとして挿入されます。 |
prepend() | その要素の子要素の先頭に指定した要素(ノード)またはテキストを挿入します。 テキストはテキストノードとして挿入されます。 |
after() | その要素の後(親要素から見た子要素の末尾)に指定した要素(ノード)またはテキストを挿入します。 テキストはテキストノードとして挿入されます。 |
before() | その要素の前(親要素から見た子要素の先頭)に指定した要素(ノード)またはテキストを挿入します。 テキストはテキストノードとして挿入されます。 |
appendChild() と append() の違い
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>
<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>
<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>
<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>
<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() は指定した HTML や XML の文字列をパースしてその解析結果のノードを、指定した位置に挿入するElement インターフェースのメソッドです。
insertAdjacentHTML() による操作は、innerHTML での代入による操作よりも動作が高速とのことです(操作方法によってはあまり違いがない場合もあるようです)。
innerHTML は文字列をパースしてその解析結果をその要素の子要素に設定するプロパティです。
※ また、innerHTML 同様、外部からの入力など制御できない文字列を insertAdjacentHTML() を使って挿入するのはセキュリティリスクが発生するので注意が必要です。insertAdjacentText() を使えばタグなどの文字はエスケープされます。
以下が insertAdjacentHTML() の書式とパラメータです。
targetElement.insertAdjacentHTML(position, text);
指定可能な値(文字列) | 意味 |
---|---|
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()は指定した文字列を、メソッドを実行した要素を基準とした相対的な位置にテキストノードとして挿入する Element インターフェースのメソッドです。
※タグなどの特殊文字(& < >)を含む文字列を指定すると、それらはエスケープされて挿入されます。
以下が insertAdjacentText() の書式とパラメータです。
targetElement.insertAdjacentText(position, text);
指定可能な値(文字列) | 意味 |
---|---|
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><p>タグと特殊文字:&""</p>
insertAdjacentElement() は指定した要素を、指定した位置に挿入する Element インターフェースのメソッドです。
appendChild() やinsertBefore() と同様、挿入する要素が既に存在している場合、その要素は現在の親ノードから削除され、新しい親ノードに自動的に追加(移動)されます。
insertAdjacentHTML() は第2パラメータに指定した HTML 文字列をパースして使用するのに対して、insertAdjacentElement() は HTML 要素を事前に用意して第2パラメータに指定します。
以下が insertAdjacentElement() の書式とパラメータです。
targetElement.insertAdjacentElement(position, element);
指定可能な値(文字列) | 意味 |
---|---|
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>
<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>
<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>
<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() は現在のノードを複製して返す Node のメソッドです。
ノードを複製すると、そのノードのすべての属性とその値がコピーされます。
cloneNode によって返される複製ノードは、何らかの方法で他のノードに追加されるまではドキュメントの一部ではありませんし、親ノードを持ちません。
以下が cloneNode() の書式とパラメータです。
var dupNode = node.cloneNode(deep);
以下は 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() メソッドを使用できます。
メソッド | 説明 |
---|---|
replaceChild() | 第1引数に置き換える新しいノードを、第2引数に置き換えられる既存ノードを指定してノードを置換。置換対象のノードは、現在のノードの子ノードでなければなりません(置換対象の親ノードでこのメソッドを呼び出します) |
removeChild() | 引数に指定した子ノードを削除。削除対象のノードは、現在のノードの子ノードでなければなりません(削除対象の親ノードでこのメソッドを呼び出します) |
要素自身を削除するには ChildNode のメソッド remove() を使用できます(IE 未対応)。
メソッド | 説明 |
---|---|
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)の中で何番目なのかを調べるには、NodeList やHTMLCollection を配列に変換して、配列のメソッドでインデックス(位置)を取得することができます。
以下は 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); //3console.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); //3console.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); //3console.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]); //3console.log(menuArray[indexes[0]].textContent); //NEWS
イベントの処理はElement のメソッド addEventListener() や removeEventListener()、Event のメソッド preventDefault() や stopPropagation() などを使うことができます。
メソッド | 説明 |
---|---|
addEventListener() | 要素にイベントリスナーを登録 |
removeEventListener() | 要素から登録したイベントリスナーを削除 |
dispatchEvent() | イベントを発火 |
メソッド | 説明 |
---|---|
preventDefault() | ブラウザが標準で実装している処理(デフォルトの動作)をキャンセル(停止) |
stopPropagation() | イベントが 伝播 (propagation) するのを停止 |
参考サイト:MDN イベントの紹介
イベントタイプ
利用可能なイベントタイプの一覧は「MDN イベントリファレンス」に掲載されています。
イベント処理の設定方法
イベントに対する処理の設定方法は大きく分けると以下の DOM 要素のプロパティである onevent ハンドラーを使う方法とEventTarget のメソッド addEventListener() を使う方法があります。
設定方法 | 説明 |
---|---|
onevent ハンドラー | onevent ハンドラーは特定の DOM 要素のプロパティで通常、onclick、onfocus、onload など on の後にイベント名を続けた名前が付けられています。 IE8 など古いブラウザでも作動しますが、同じイベントに対して複数のハンドラーを設定できず、必要な時にハンドラーを削除することもできません。 関連項目:イベントハンドラーの設定 |
addEventListener() | 同じ要素に複数のイベントハンドラーを登録できたり、必要な時にイベントハンドラーを削除できる、キャプチャフェーズかバブリングフェーズのどちらで処理されるかを制御できるなどのメリットがあります。 但し、IE9 未満には対応していません。 |
ある要素に対してイベントリスナーを登録するには、そのオブジェクト(EventTarget)のメソッド addEventListener() を使用することができます。
target.addEventListener(type, listener[, useCapture]); (従来からの書式)
target.addEventListener(type, listener[, options]);
target はイベントを設定する要素です。
※第3引数に真偽値(true|false)を指定した場合は、従来通りのキャプチャフェーズかバブリングフェーズの制御になります。ブラウザが第3引数の useCapture と options のどちらをサポートしているかを検出するにはオプションの検出 を参照。
以下はボタンをクリックすると「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);
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('一度だけアラートを表示します');});
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 イベントの場合、以下のようになります。
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>
ウィンドウ幅:
スクロールイベントなど発生する頻度が高いイベントでは、ブラウザに負担がかかる可能性があります。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 オプションがサポートされているかどうかの真偽値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);
パッシブイベントリスナー
タッチイベントリスナーとホイールイベントリスナーに {passive:true} を指定することでパフォーマンスを改善できる可能性があります。
また、一部のブラウザー (Chrome と Firefox) では、Window, Document, Document.body の touchstart 及び touchmove イベントの passive オプションの既定値を true に変更しているようです。
参考:パッシブリスナーを用いたスクロールの性能改善(MDN)
ブラウザサポート状況:can i use Passive event listeners
removeEventListener() は登録したイベントリスナーを削除します。
target.removeEventListener(type, listener[, options]);
登録した際のイベント名(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 や 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() メソッドで中止可能かどうかを表す論理値。 |
また、イベントにはMouseEvent やUIEvent、ProgressEvent などのいくつかの種類(イベント型/モジュール)があり、それぞれにプロパティやメソッドがあります。
モジュール名 | インターフェース | イベントタイプ |
---|---|---|
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ボタン:leftX座標:61Y座標: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, …}-->
上記サンプル(同じ色が続くことがあります)
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>
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);
伝播のキャンセル
イベントを伝播させないようにするには、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);
ページ上で発生したイベントは上位の要素にも通知され、バブリングと呼びます(イベントの伝播)。
このバブリングにより、イベント移譲(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 要素をクリックした場合のみ、その要素の背景色を変更しています。
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 ボタンをクリックすると最後のチェックボックスを削除します。
操作結果 :
<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>
//チェックボックスの親(祖先)要素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() は第一引数に指定された関数を、第二引数に指定した時間(ミリ秒)が経過したら実行するようにタイマーをセットするグローバルのメソッドです。
言い換えると、第一引数に指定された関数を第二引数に指定された時間が経過したら実行するようにスケジュールするメソッドです。
let timeoutID = setTimeout(functionRef, delay, [params, ...])
引数
以下の引数を受け取ります。
数値
で指定(省略可能)。省略した場合は 0 が使用され、(ほぼ)直ちに実行されます。戻り値
戻り値の 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()
や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);
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
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() の構文はsetTimeout() と同じです。
let timeoutID = setInterval(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 foo2: hello foo・・・中略・・・10: hello foo */
apply() とcall() はFunction オブジェクトの prototype プロパティ(Function.prototype)に用意されたメソッドで、すべての関数が共通して持っているメソッドです。
apply() と call() は明示的に this を指定して関数を実行するメソッドで、この2つのメソッドを利用すると、引数に指定したオブジェクトのメソッドであるかのように関数を実行することができます。
参考サイト:JavaScript Primer:call、apply、bindメソッド
以下が構文です。
関数.apply(thisの値, [関数の引数1, 関数の引数2, ...]);
関数.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 Fooconst 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 Barfoo.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()を使うと、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 Fooconst 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 Barfoo.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) = 6console.log( double(4) ); // = multiple(2, 4) = 8console.log( double(5) ); // = multiple(2, 5) = 10
既存のパラメータのいくつかを固定にすることで新しい関数を作成することができます。
参考サイト: javascript.info 関数バインディング
要素を取得するメソッドが返す値(要素の集合)はHTMLCollection やNodeList という配列のようなオブジェクトなので、配列(Array)のメソッドを直接使うことができません。
HTMLCollection や NodeList などの配列のようなオブジェクトで配列のメソッドを使うには、call や apply を利用します。
または配列のようなオブジェクトを配列に変換して、配列のメソッドを使うという方法もあります。
以下は、getElementsByClassName() を使ってクラスが foo の要素(HTMLCollection)を取得し、call() を使って配列(Array.prototype)のメソッド forEach() を呼び出す例です。
var foo = document.getElementsByClassName('foo'); //HTMLCollectionArray.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 を返すメソッドの例
HTMLCollection を返すメソッドの例
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(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 のスプレッド演算子( ... )を使っても配列のようなオブジェクトから簡単に配列を生成することができます。
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.foolength: 2 //length プロパティ__proto__: NodeListHTMLCollection(2) [p.foo, p.foo]0: p.foo1: p.foolength: 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)); //truevar nodelist = document.querySelectorAll('.foo');console.log(Array.isArray(nodelist)); //falsevar htmlCollection = document.getElementsByClassName('foo');console.log(Array.isArray(htmlCollection)); //false
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 1DIV: Foo 2SPAN: 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 の処理のおおまかな流れです。
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 の配信を最適化する)。
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 は 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!});
当然ですが、ドキュメントがロードされた後に 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:loadingdocument.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: interactiveTriggerd: 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:loadingTriggerd: 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 とほぼ同時だがその前に行う処理 }});
参考サイト:
©2013-2025 Web Design Leavesprivacy policycontact