Javascript:イベントバブル、onClick, onMouseOver(Out), onMouseEnter(Leave)についてのメモ
こちらで教えてもらったことを元に、「下位要素ノードで発生したイベントは、上位要素方向へ伝播する」というイベントバブルを軽く理解した上で、
======= onClick =======
「event.bubbles」を参考に、下(Sample1)の色のついた領域のどこがクリックされたかを表示させるスクリプトを書いてみました。
Sample1
p span
どこがクリックされたかをここに表示します。
ソース(CSSは大きさ、色など、文字の太さを指定しているだけなので省略)
JavaScript↓
function clickTest(evt){
t=evt.target || evt.srcElement;
document.getElementById("clickedAera").innerHTML=t.tagName+" がクリックされました。"
}
html↓
<div id="clickTest" onClick="clickTest(event);">div <p>p <span>span</span> <input type="button" value="input"> </p> </div> <p id="clickedAera">どこがクリックされたかをここに表示します。</p>
【メモΦ(。。)】
- イベント発生源:event.target
(IE は event.srcElement) - 現在 onclick 属性が実行されているノード:event.currentTarget
(IE にはこれに該当するプロパティがないので this で代用)
======= onMouseOver / onMouseOut =======
次に、少しやっかいな(と思った)mouseOverとmouseOut。何がやっかいかというと、境界線をまたげば、同じイベントでもそれは立場(要素)によってmouseOutでもありmouseOverでもあるんですが、これにイベントバブルが絡むと、同じ要素で同時にmouseOverとmouseOutが起きるということです。
「イベントバブル、マウスオーバーなどに関して – JavaScript – 教えて!goo」にも書いたとおり、div内で、divの中のpに入るとき、一旦divはmouseOutしてからmouseOverになるんですが、その根拠とおぼしき事が「掲示板/JavaScript質問板/過去ログ/一覧/ 右クリで呼び出しができるならば、ボタン3では? – TAG index Webサイト」の後半で書かれてあります。
詳しくはそちらを参照してもらうとして、結論として「event.target」と「event.relatedTarget」(IEでは「event.toElement」と「event.fromElement」)の関係で納得できました。
Sample2
ソース(CSSは大きさ、色など、文字の太さを指定しているだけなので省略)
JavaScript↓
function mouseOverOutOver(evt){
var t = evt.target || evt.toElement; // mouseOver した要素
var f = evt.relatedTarget || evt.fromElement; // mouseOut した要素
document.getElementById("whereMouseIs").innerHTML=
"マウスは "+f.id+" から "+t.id+" に移動しました。"
}
function mouseOverOutOut(evt){
var t = evt.relatedTarget || evt.toElement; // mouseOver した要素
var f = evt.target || evt.fromElement; // mouseOut した要素
document.getElementById("whereMouseIs").innerHTML=
"マウスは "+f.id+" から "+t.id+" に移動しました。"
}
html↓
<div id="outside">outside <div onMouseOver="mouseOverOutOver(event);" onMouseOut="mouseOverOutOut(event);" id="div">div <p id="p">p</p> (白いところ)ul↓ <ul id="ul"> <li id="li1"><a id="a1">a1</a> li1</li> <li id="li2"><a id="a2">a2</a> li2</li> <li id="li3"><a id="a3">a3</a> li3</li> <li id="li4"><a id="a4">a4</a> li4</li> </ul> </div> </div> <p id="whereMouseIs">マウスの状態をここに表示します。</p>
【メモΦ(。。)】
マウスが境界線をまたぐとき、
- mouseOver(Out)の対象となる要素 = event.Target
(IEでは常にmouseOverした要素がevent.toElement) - event.TargetがmouseOver(Out)したときにmouseOut(Over)した要素 = event.relatedTarget
(IEでは常にmouseOutした要素がevent.fromElement)
┌───────────── mouseOver─────────────┐
│ │ │
│(マウスの動き)⇒⇒⇒⇒⇒⇒⇒⇒⇒⇒⇒⇒⇒⇒⇒ │
│ │ │
│(IE以外)event.relatedTarget │ event.Target │
│(IE )event.fromElement │ event.toElement │
└───────────────┴───────────────┘
┌───────────── mouseOut ─────────────┐
│ │ │
│(マウスの動き)⇒⇒⇒⇒⇒⇒⇒⇒⇒⇒⇒⇒⇒⇒⇒ │
│ │ │
│(IE以外)event.Target │event.relatedTarget │
│(IE )event.fromElement │event.toElement │
└───────────────┴───────────────┘
Sample2のプログラムでは、例えばマウスがulにいるのかdivにいるのかは分かりますが、「divの中のul」であってもulにいる限りはdivの外側とみなされてしまいます。
では、ある領域の「内側」にいるのか「外側」にいるのかを判別するにはどうすれば良いか。Sample3のような図で「area2」の中か外かを判別する例を考えてみます。
area2にonMouseOver(Out)イベントが起きるのは、area2内側で境界線をマウスがまたぐときです。その時のマウスの行き先は、IE以外ではonMouseOverの場合はevent.Target、onMouseOutの場合はevent.relatedTarget、IEの場合はいつでもevent.toElementであることはSample2で確認したとおりです。
こうして、マウスの行き先のエレメントを取得した上で、行き先のエレメント、その親、さらにその親、さらにその親、、、のいずれもarea2に該当するものがない場合、「area2の外側に出た」(逆に、どれか一つでもarea2であれば、マウスは内側にいる)ということができます。
(area1~4の位置関係から判定しても良いですが、ロジックを一般化するために)
下のSample3では、for(……){……}の内部で行き先の要素、その親、さらにその親、さらにその親……が「area2」であるかどうか、順次調べようとして「if(tmp.id=”area2″){……}」と記述したのですが、そうするとなぜか表示が崩れる(理由は現在追求中)ので、「要素のid名+親要素のid名+その親要素のid名+……」という文字列を作って、その中に「area2」という文字が含まれるかどうかで判別する(文字列.indexOf(検索文字列、検索位置)で検索文字位置(存在しない場合は「-1」)を返す)プログラムにしてみました。
Sample3
ここに、マウスがarea2の外側か内側かを表示します。
ソース(CSSは大きさ、色など、文字の太さを指定しているだけなので省略)
JavaScript↓
function mouseInsideOutsideOver(evt){
var t = evt.target || evt.toElement; // mouseoOver した要素
var elmnts=""; // 「その要素から親方向へエレメントのid名を連ねた文字列」の初期化
for (tmp=t;tmp;tmp=tmp.parentNode){
elmnts=elmnts+tmp.id
}
if(elmnts.indexOf("area2")==-1){ // 「area2」という文字列が含まれなければ
msg="外側";
}
else msg="内側";
document.getElementById("whereMouseInsideOutside").innerHTML="マウスは、area2 の"+msg+"の "+t.id+" にいます。";
}
function mouseInsideOutsideOut(evt){
var t = evt.relatedTarget || evt.toElement; // mouseoOver した要素
var elmnts=""; // 「その要素から親方向へエレメントのid名を連ねた文字列」の初期化
for (tmp=t;tmp;tmp=tmp.parentNode){
elmnts=elmnts+tmp.id
}
if(elmnts.indexOf("area2")==-1){ // 「area2」という文字列が含まれなければ
msg="外側";
}
else msg="内側";
document.getElementById("whereMouseInsideOutside").innerHTML="マウスは、area2 の"+msg+"の "+t.id+" にいます。";
}
html↓
<div id="area1">area1 <div onMouseOver="mouseInsideOutsideOver(event)" onMouseOut="mouseInsideOutsideOut(event)" id="area2">area2 <div id="area3">area3 <div id="area4">aera4 </div> </div> </div> </div> <p id="whereMouseInsideOutside">ここに、マウスがarea2の外側か内側かを表示します。</p>
======= onMouseEnter / onMouseLeave =======
ちなみに「onMouseEnter」「onMouseLeave」というハンドラはその中に入ったか出たときにイベントが発生するので便利ですが、これが有効なのはIEとOperaだけのようなので、残念です。
Sample4では、Area1(2,3,4)の中にマウスがいる(in)かいない(out)かの状態を下の表に表示します(ただし、IE、Operaのみ)。
Sample4
| Area1 | Area2 | Area3 | Area4 | IE Operaのみ状態を表示 | IE Operaのみ状態を表示 | IE Operaのみ状態を表示 | IE Operaのみ状態を表示 |
|---|
ソース(CSSは大きさ、色など、文字の太さを指定しているだけなので省略)
JavaScript↓
function area1in() {
document.getElementById("area1InOut").innerHTML="in"
}
function area1out() {
document.getElementById("area1InOut").innerHTML="out"
}
function area2in() {
document.getElementById("area2InOut").innerHTML="in"
}
function area2out() {
document.getElementById("area2InOut").innerHTML="out"
}
function area3in() {
document.getElementById("area3InOut").innerHTML="in"
}
function area3out() {
document.getElementById("area3InOut").innerHTML="out"
}
function area4in() {
document.getElementById("area4InOut").innerHTML="in"
}
function area4out() {
document.getElementById("area4InOut").innerHTML="out"
}
html↓
<div id="mouseEnterLeaveArea1" onMouseEnter="area1in();" onMouseLeave="area1out();">area1 <div id="mouseEnterLeaveArea2" onMouseEnter="area2in();" onMouseLeave="area2out();">area2 <div id="mouseEnterLeaveArea3" onMouseEnter="area3in();" onMouseLeave="area3out();">area3 <div id="mouseEnterLeaveArea4" onMouseEnter="area4in();" onMouseLeave="area4out();">area4 </div> </div> </div> </div> <table> <thead> <th>Area1</th> <th>Area2</th> <th>Area3</th> <th>Area4</th> </thead> <tbody> <td id="area1InOut">IE Operaのみ状態を表示</td> <td id="area2InOut">IE Operaのみ状態を表示</td> <td id="area3InOut">IE Operaのみ状態を表示</td> <td id="area4InOut">IE Operaのみ状態を表示</td> </tbody> </table>
FC2ノウハウ
IE対策:tableにおけるdisplayのデフォルト値やinnerHTMLについて
- 1列目に見出し
- 2列目にデータ
- 3列目は結合されたセルで、中にボタン
- ボタンを押すごとに特定の行が「表示」「非表示」と切り替わる
という動的テーブルを作りました。すなわち、特定の行を2行目としたとき、こんなテーブル↓
| 1行目見出し | 1行目データ | |
|---|---|---|
| 2行目見出し | 2行目データ | |
| 3行目見出し | 3行目データ |
最初は、さほど難しくないと思って取りかかったものの、IEがらみでつまづき、でも得るものが多かったので書き留めておきます。
ちなみに、不本意ながらIEに優しいページを作るようにしているので、こんな手順でした。
まず、罫線のためのスタイルを
table {
border-collapse:collapse;
}
th,td {
border:1px solid #000;
}
とし、
| 1行目見出し | 1行目データ | |
|---|---|---|
| 2行目見出し | 2行目データ | |
| 3行目見出し | 3行目データ |
<table> <tr> <th>1行目見出し</th> <td>1行目データ</td> <td rowspan="3"> <input type="button" value="2行目を非表示" /> <input type="button" value="2行目を表示" /> </td> </tr> <tr> <th>2行目見出し</th> <td>2行目データ</td> </tr> <tr> <th>3行目見出し</th> <td>3行目データ</td> </tr> </table>
というのを用意し、(以下、「2行目を非表示」ボタンを「非表示」ボタン、「2行目を表示」ボタンを「表示」ボタンと略す)
あらかじめ「表示ボタン」を非表示とさせておき、
「非表示ボタン」が押されたら、「非表示ボタン」と2行目を非表示にして「表示」ボタンを表示、
「表示ボタン」が押されたら、「非表示ボタン」と2行目を表示させ「表示ボタン」を非表示にする
というつもりで作ったのがこれ↓
サンプルA(IE以外はテーブルが崩れる
| 1行目見出し | 1行目データ | |
|---|---|---|
| 2行目見出し | 2行目データ | |
| 3行目見出し | 3行目データ |
css↓
#addAbtn {
display:none;
}
JavaScript↓
function delA() {
document.getElementById('delAbtn').style.display="none";
document.getElementById('addAbtn').style.display="block";
document.getElementById('row2A').style.display="none";
}
function addA() {
document.getElementById('delAbtn').style.display="block";
document.getElementById('addAbtn').style.display="none";
document.getElementById('row2A').style.display="block";
}
html↓
<table> <tr id="row1A"> <th>1行目見出し</th> <td>1行目データ</td> <td rowspan="3"> <input type="button" value="2行目を非表示" onclick="delA()" id="delAbtn" /> <input type="button" value="2行目を表示" onclick="addA()"id="addAbtn" /> </td> </tr> <tr id="row2A"> <th>2行目見出し</th> <td>2行目データ</td> </tr> <tr id="row3A"> <th>3行目見出し</th> <td>3行目データ</td> </tr> </table>
IEではうまく行くのに、IE以外ではテーブルが崩れるのはなぜか、調べてみた結果、<tr>を表示させるために「display:block;」としていたことが原因でした。
<tr>のデフォルトは、IEでは「block」、それ以外では「table-row」
ということですが、IEではテーブルに関するdisplayプロパティがことごとく対応していないと理解すべきようです。
詳細は
にある通りですが、この投稿の一番下にdisplayプロパティの値について、引用させてもらいました。
そこで、以前学んだことを利用して、IEのときは「block」それ以外は「table-row」にする、という風に書き換えたのがこれ↓
サンプルB(これでもIE以外はいまひとつ)
| 1行目見出し | 1行目データ | |
|---|---|---|
| 2行目見出し | 2行目データ | |
| 3行目見出し | 3行目データ |
css↓
#addBbtn {
display:none;
}
JavaScript↓
function delB() {
document.getElementById('delBbtn').style.display="none";
document.getElementById('addBbtn').style.display="block";
document.getElementById('row2B').style.display="none";
}
function addB() {
document.getElementById('delBbtn').style.display="block";
document.getElementById('addBbtn').style.display="none";
userAgent = window.navigator.userAgent.toLowerCase();
if (userAgent.indexOf("msie") > -1) {
document.getElementById('row2B').style.display="block";
}
else {
document.getElementById('row2B').style.display="table-row";
}
}
html↓
<table> <tr id="row1B"> <th>1行目見出し</th> <td>1行目データ</td> <td rowspan="3"> <input type="button" value="2行目を非表示" onclick="delB()" id="delBbtn" /> <input type="button" value="2行目を表示" onclick="addB()"id="addBbtn" /> </td> </tr> <tr id="row2B"> <th>2行目見出し</th> <td>2行目データ</td> </tr> <tr id="row3B"> <th>3行目見出し</th> <td>3行目データ</td> </tr> </table>
これでIEもIE以外もうまくいったかな?と思いましたが、2行目を非表示にしたとき、よく見るとFirefoxで(実はLunascapeでも)ボタンの下の罫線が消えてしまっています。
考えてみた結果、htmlにおける、テーブル1行目の「<td rowspan="3">」が原因かな?うん、たぶんそうです。Firefox、Lunaspcape以外でうまく見えていても、構造的に、2行目を非表示にしたら「<td rowspan="2">」とすべきではないか、と。そこで、html構造を書き換えてくれる「innerHTML」を使用(参考:特定の要素の中身をごっそり書き換える – JavaScript TIPSふぁくとりー)。ついでに、「表示」「非表示」ボタンも1行にまとめられる(あらかじめ「表示ボタン」を非表示にするCSSも不要になる)ぞ!と、こうしてみました↓
サンプルC(今度はIEがダメ)
| 1行目見出し | 1行目データ | |
|---|---|---|
| 2行目見出し | 2行目データ | |
| 3行目見出し | 3行目データ |
CSSは無し
JavaScript↓
function delC() {
document.getElementById('row2C').style.display="none";
document.getElementById('row1C').innerHTML=
'<th>1行目見出し</th>'+
'<td>1行目データ</td>'+
'<td rowspan="2">'+
'<input type="button" value="2行目を表示" onclick="addC()" />'+
'</td>'
}
function addC() {
userAgent = window.navigator.userAgent.toLowerCase();
if (userAgent.indexOf("msie") > -1) {
document.getElementById('row2C').style.display="block";
}
else {
document.getElementById('row2C').style.display="table-row";
}
document.getElementById('row1C').innerHTML=
'<th>1行目見出し</th>'+
'<td>1行目データ</td>'+
'<td rowspan="3">'+
'<input type="button" value="2行目を非表示" onclick="delC()" />'+
'</td>'
}
html↓
<table> <tr id="row1C"> <th>1行目見出し</th> <td>1行目データ</td> <td rowspan="3"> <input type="button" value="2行目を非表示" onclick="delC()" /> </td> </tr> <tr id="row2C"> <th>2行目見出し</th> <td>2行目データ</td> </tr> <tr id="row3C"> <th>3行目見出し</th> <td>3行目データ</td> </tr> </table>
このようにrowspanの値を変えることによって、Firefox, Lunascapeでも罫線消失を免れ、動作もうまく行くことが確認されましたが、今度はIEに問題が発生しました。
1回目の押下はうまくいくのですが、そこでエラーとなってしまいます。「表示」→「非表示」とならないのです。どうやら、innerHTMLの使用がおかしいらしいです。試しに2行目を消す前にHTMLの書き換えをしたら(jsの記述の順序を変えたら)、1回目の押下でエラー(2行目が非表示にならずに、その前で止まってしまう)となりました。
ということでここがクサイ、と「IEにおけるinnerHTMLの記述ミス? – JavaScript – 教えて!goo」に質問したところ、
IE (少なくとも IE8-) の innerHTML は "table, tFoot, tHead, and tr elements" の書き換えに対応して
おらず、
IEでは、TABLE、TR、TDなんかのinnerHTMLは読み取り専用
だそうです。
ということで、また「表示」「非表示」ボタンをあらかじめ用意し、HTMLの書き換えはIE以外に適用(IEでは書き換えなくてもうまくいっていたし)ということで、以下のように(JavaScript内の記述の順番に注意して[htmlを書き換えてからボタンのスタイルのプロパティを変えるように])修正したところ、ようやくうまくいきました。
サンプルD(IEもIE以外もOK)
| 1行目見出し | 1行目データ | |
|---|---|---|
| 2行目見出し | 2行目データ | |
| 3行目見出し | 3行目データ |
css↓
#addDbtn {
display:none;
}
JavaScript↓
function delD() {
userAgent = window.navigator.userAgent.toLowerCase();
if (userAgent.indexOf("msie") == -1) {
document.getElementById('row1D').innerHTML=
'<th>1行目見出し</th>'+
'<td>1行目データ</td>'+
'<td rowspan="2">'+
'<input type="button" value="2行目を非表示" onclick="delD()" id="delDbtn" />'+
'<input type="button" value="2行目を表示" onclick="addD()"id="addDbtn" />'+
'</td>'
}
document.getElementById('delDbtn').style.display="none";
document.getElementById('addDbtn').style.display="block";
document.getElementById('row2D').style.display="none";
}
function addD() {
if (userAgent.indexOf("msie") > -1) {
document.getElementById('row2D').style.display="block";
}
else {
document.getElementById('row2D').style.display="table-row";
document.getElementById('row1D').innerHTML=
'<th>1行目見出し</th>'+
'<td>1行目データ</td>'+
'<td rowspan="3">'+
'<input type="button" value="2行目を非表示" onclick="delD()" id="delDbtn" />'+
'<input type="button" value="2行目を表示" onclick="addD()"id="addDbtn" />'+
'</td>'
}
document.getElementById('delDbtn').style.display="block";
document.getElementById('addDbtn').style.display="none";
}
html↓
<table> <tr id="row1D"> <th>1行目見出し</th> <td>1行目データ</td> <td rowspan="3"> <input type="button" value="2行目を非表示" onclick="delD()" id="delDbtn" /> <input type="button" value="2行目を表示" onclick="addD()"id="addDbtn" /> </td> </tr> <tr id="row2D"> <th>2行目見出し</th> <td>2行目データ</td> </tr> <tr id="row3D"> <th>3行目見出し</th> <td>3行目データ</td> </tr> </table>
ちなみにinnerHTMLのソースの書き方は、「innerHTML内では改行は禁止? – JavaScript – 教えて!goo」の
JavaScriptでは、1つのコードの途中に改行を入れるとエラーになってしまいます。
なので、指定した文字列などを分割したい場合は、それぞれを+で繋げて分割すればOKです。
を参考にしました。
ということで、まとめ。
displayプロパティの値は、以下のものが取れます。
| inherit | 一番近い上位要素で指定された値を継承します。 |
|---|---|
| none |
当該要素を非表示にします。レイアウト上、当該要素は存在していないかのように扱われます。 また、この値を与えられた要素の下位要素は、それらに与えられたdisplayプロパティの値に拘らず表示されなくなります。 |
| inline |
インラインボックスの表示となります(デフォルト)。 HTMLでは、インライン要素となっている要素にはデフォルトでこのスタイルが当てられております。 |
| block |
ブロックレヴェルボックスの表示となります。 HTMLでは、ブロックレヴェル要素となっている要素にはデフォルトでこのスタイルが当てられております。 |
| list-item |
ブロックレヴェルボックスの表示となりますが、リストアイテムの表示方式になります。 HTMLでは<li>要素にのみデフォルトでこのスタイルが当てられております。 |
| run-in |
ラン・インボックスの表示となります。 HTMLには該当する要素は無いようです。 |
| compact |
コンパクトボックスの表示となります。 HTMLでは、compact属性値を附与した<dl>要素直下の<dt>要素にのみデフォルトでこのスタイルが当てられているようです。 |
| table |
表(テーブル)の表示スタイル(基本的にはブロックレヴェルボックス)となります。 |
| inline-table |
インライン表(テーブル)の表示スタイル(基本的にはインラインボックス)となります。 HTMLには実在しませんが、前後で改行しない表(テーブル)の表示スタイルです。 |
| table-row-group |
表(テーブル)の行グループの表示スタイルとなります。HTMLでは<tbody>要素のスタイルとなります。 |
| table-header-group |
表(テーブル)のヘッダグループの表示スタイル(HTMLでは<thead>要素のスタイル)となります。具体的にはこのプロパティ値を与えられていない全ての行の前、表題が表の上にある場合にはその直後(すなわち、表の上にある表題とこのプロパティ値を与えられていない全ての行の間)に表示されます。 ※プリンタに対するスタイルシートに於いては、表が複数ページに跨る場合には、二ページ目以降の表にもこのプロパティ値を与えられた要素を一番上に表示するようにしても良い事となっております。 |
| table-footer-group |
表(テーブル)のフッタグループの表示スタイル(HTMLでは<tfoot>要素のスタイル)となります。具体的にはこのプロパティ値を与えられていない全ての行の後、表題が表の下にある場合には園直前(すなわち、このプロパティ値を与えられていない全ての行と表の下にある表題の間)に表示されます。 ※プリンタに対するスタイルシートに於いては、表が複数ページに跨る場合には、最終ページより前の表にもこのプロパティ値を与えられた要素を一番下に表示するようにしても良い事となっております。 |
| table-row |
表(テーブル)の行成分の表示スタイルとなります。HTMLでは<tr>要素のスタイルとなります。 |
| table-column-group |
表(テーブル)の列グループの表示スタイルとなります。HTMLでは<colgroup>要素のスタイルとなります。 |
| table-column |
表(テーブル)の列の表示スタイルとなります。HTMLでは<col>要素のスタイルとなります。 |
| table-cell |
表(テーブル)のコマの表示スタイル(基本的にはブロックレヴェルボックス)となります。HTMLでは<th>要素及び<td>要素のスタイルとなります。 |
| table-caption |
表(テーブル)の表題の表示スタイルとなります。HTMLでは<caption>要素のスタイルとなります。 ※caption-sideプロパティもご参照下さい。 |
table関連のdisplayプロパティデフォルト値
| <table>要素 | display: table; 但し、表そのものをインライン表示したい場合には display: inline-table; とします。 |
|---|---|
| <tbody>要素 | display: table-row-group; |
| <thead>要素 | display: table-header-group; |
| <tfoot>要素 | display: table-footer-group; |
| <tr>要素 | display: table-row; |
| <colgroup>要素 | display: table-column-group; |
| <col>要素 | display: table-column; |
| <th>要素/<td>要素 | display: table-cell; |
| <caption>要素 | display: table-caption; |
所感:IE用に作ろうとするとそれ以外でおかしくなり、IE以外用に作ろうとするとIEでおかしく表示される。つまり、IEが諸悪の根源。


