##なぜ学び直すのかとその目的
getElementByIdとか、querySelectorとかを使う簡単なDOM操作には結構慣れてきました。
でも、複数のDOM操作をする場合にあまり効率よくできてないんじゃないかな?
と思うことがあります。itemやらバブリングやらを使いこなして、スッキリしたコードをかけるようになるべく再入門です。
【今日のメインテーマ】
・イベント名の似ているややこしい違いについて
・属性名の取得って色々方法あるね
前回→https://qiita.com/irico/items/18471dd3260f1297cabb
(HTMLコレクションとNodeコレクション、ノードウォーキングについて)
【参考】
https://codeday.me/jp/qa/20190201/205623.html
・書籍「JavaScript本格入門」p293~p302
##イベントドリブンモデル
【基本のおさらい】
JavaScriptでよく行われる、ボタンが押された→イベント発生のようなプラグラミングモデルを
イベントドリブンモデル
と言います。
そしてイベントの処理内容を定義している関数をイベントハンドラー
と呼びます。
##わりにくいイベントまとめ
mouseup
とclick
とか、何がどのタイミング?がわかりにくいイベントが多いのでまとめておきます。
###マウスクリック系のイベント
mousedown
・・・要素上でマウスを押した時発生
mouseup
・・・要素上でマウスを離した時
click
・・・要素上でマウスをクリックした時
mouseup ≠click です。
例えば、関係のない要素でマウスを押し、ターゲットの要素でマウスを離した場合、
mouseup
は実行され、click
は実行されません。絶妙ですね。
###マウス移動系のイベント
mouseenter/mouseleave
・・・対象の要素の出入り時に発生
mouseover/mouseout
・・対象の要素の出入り時 + 内側の要素の出入り時にも発生
ちょっとわかりにくいのでコード付きで説明します。
<div class="parent">
<div class="child"></div>
</div>
.parent{
margin: 0 auto;
display: flex;
justify-content: center;
align-items: center;
width: 400px;
height: 400px;
background: black;
}
.child{
width: 200px;
height: 200px;
background: white;
}
let target = document.querySelector('.parent');
target.addEventListener('mouseover',()=>{
console.log('入った!');
})
target.addEventListener('mouseout',()=>{
console.log('でた!');
})
.child
を.parent
で囲み、.parent
にmouseover/mouseout
のイベントを付与しています。
↑こんな感じになる訳ですが、これをマウスで左端の白いとこから右端の白いところまでスーッと移動させると、
コンソールではこのようになります。
子要素に出入りする際も入った!``でた!
が表示されるようになるんですね。
(詳しく言うと、親要素から出るとき、子要素から出るとき共にmouseout
が働く
親要素に入るとき、子要素に入るとき共にmouseenter
が働く)
mouseenter/mouseleave
だと子要素には反応しません。
##間違えやすそうなイベント定義注意点
###プロパティとして設定するときは、関数オブジェクト!(関数呼び出しではない!)
window.onclick = event(); //ダメな例
window.onclick = event; //正しい
うっかり間違えそうですね。
###プロパティとして設定するときは、1つの要素に登録できるイベントリスナーは1つだけ!
onXXXXイベント系の制約です。addEventListener
を使いましょう。
###イベントハンドラーはDOMContentLoadedイベントハンドラーの配下におく!
DOMContentLoadedイベントハンドラー配下におくことで、画面全体のロードが終わってからイベントが実行できるようにします。
基本的にjsはbody閉じタグ直前におくので、要素が取得できない!
と言うことはないかもしれませんが、習慣づけることは大切です。
(ただし、重たいサイトで最初のページの方のイベント、とかだったら話が変わるかも。臨機応変に)
DOMContentLoaded
と似たものでonload
イベントがありますが、こちらは
画像を含む読み込みが終わったタイミングで実行されます。
DOMContentLoaded
は画像の読み込みを待ちません。
画像読み込みを待たなければそれだけ早く実行できるので、
画像の読み込み必要→onload
画像読み込み必要ない→DOMContentLoaded
で使い分けてください。
##属性の取得における属性名≠取得名問題
hrefを設定したければ
element.href ="http:~"
と、大体が一致しているのですが、
一部属性名≠取得名のものも存在します。
有名なものではclassがありますね。
element.className ='section'
私は普段これでアクセスしてましたが、
getAttribute
、setAttribute
で取得すると、この不一致問題が解消され、属性名を動的に変更可能という良さがあるみたいです。
let myHref = link.getAttribute('href'); //取得
myHref.setAttribute('href','http:~'); //設定
コードが長くなるので、これも臨機応変ですね。
##不特定の属性の取得
attributes
・・・特定の要素ノードの全ての属性を取得する。
全属性の戻り値ですが、NamedNodeMapオブジェクト
で返されます。初めて聞いた...
こいつはHTMLCollectionに似ていて、個別のノードに名前でもインデックス番号でもアクセスできちゃいます。
ただし、HTMLCollectionなどと違ってノードの順序を保守してないことに注意です。
NamedNodeMap.item(i).name
で属性名、
NamedNodeMap.item(i).value
で属性の値を取得できます。
document.addEventListener('DOMContentLoaded',()=>{
let target = document.querySelector('.target');
let attrs = target.attributes; //属性リストを取得
for(let i = 0;i < attrs.length;i++){
let attr = attrs.item(i);
console.log(attr.name + ':' + attr.value);
}
},false);
このコードは全ての属性をコンソールで列挙しています。
addEventListener
の第三引数falseがきになったらこちらをどうぞ。
https://qiita.com/irico/items/8a23071719aa28e3c032
https://qiita.com/irico/items/3e3cd9fad4610b83cf87
##NamedNodeMapは属性の付け外しも思いのまま!
//つけるとき
let title = document.createAttribute('title')
title.value = '画像です'
attrs.setNamedItem(title);
//外すとき
attrs.removeNamedItem('title');