DOMイベントの基礎から深淵まで

50

description

DOMイベントの基礎から、細かい情報まで、最新のDOMイベントの仕様策定状況も含めて、解説しています。

Transcript of DOMイベントの基礎から深淵まで

Page 1: DOMイベントの基礎から深淵まで
Page 2: DOMイベントの基礎から深淵まで

Text

DOMイベントの基礎から深淵まで

Page 3: DOMイベントの基礎から深淵まで

中野雅之

•肩書き

•正式: Mozilla Japan 国際化担当マネージャ

•半公式: Mozilla Japan ジョギング部大阪支部長

•非公式: Mozilla Japan 大阪支部長

•大阪の自宅で、自宅警備しながら仕事してます

Page 4: DOMイベントの基礎から深淵まで

中野雅之

•色んなアカウント

•メールアドレス: [email protected]

• Skype: masayuki-nakano

• Twitter: @d_toybox

• Facebook: masayuki.nakano.560

• Blog: 「もずはっく日記」で検索

Page 5: DOMイベントの基礎から深淵まで

アジェンダ

• DOM イベントの仕様って?

• DOM イベント処理の基礎

• DOM Level 4でのイベント生成

•各入力イベントのふかーーーーーーいお話

Page 6: DOMイベントの基礎から深淵まで

DOM イベントの仕様

• HTMLの特定の要素・状況で起きるようなイベント

• HTML5仕様書の各要素のページにあります

•これは今日は扱いません

Page 7: DOMイベントの基礎から深淵まで

DOM イベントの仕様

• ユーザの入力や、他の仕様に左右されないイベント

• Document Object Model (DOM) Level 2 Events Specification

• W3C Recommendation 13 November, 2000

• Document Object Model (DOM) Level 3 Events Specification

• W3C Editor's Draft 04 September 2012

• DOM4 Events

• Unofficial Draft

Page 8: DOMイベントの基礎から深淵まで

DOM イベント処理の基礎

•フェイズと、プロパゲーション

• addEventListener()と、removeEventListener()

•複数のイベントリスナ

• stopPropagation()と、preventDefault()

• trustedイベントと、untrustedイベント

• init*Event()

• event constructor

Page 9: DOMイベントの基礎から深淵まで

フェイズとプロパゲーション

• DOM イベントには4つの状態がある

• Capture フェイズ: イベントが、イベントターゲットに向けてルートから順に発生していく状態

• Target フェイズ: イベントが、イベントターゲット上で発生している瞬間

• Bubble フェイズ: イベントが、イベントターゲットで発生した後、ルート要素まで順に遡ってイベントが再度発生していく状態

•状態無し: イベントをDispatchする前の状態や、ハンドラで捕まえたイベントを変数に保存している状態

Page 10: DOMイベントの基礎から深淵まで

Capture フェイズ

Page 11: DOMイベントの基礎から深淵まで

Target フェイズ

Page 12: DOMイベントの基礎から深淵まで

Bubble フェイズ

Page 13: DOMイベントの基礎から深淵まで

addEventLister()とremoveEventListener()

• on* 属性でもイベントはハンドリングできるけど、addEventListener()とremoveEventListener()の利用推奨

•一つのイベントターゲットに対して、複数のハンドラを登録できる

• Capture フェイズのイベントも捕まえられる

•コードが、なんとなくカッコイイ。

Page 14: DOMイベントの基礎から深淵まで

addEventLister()とremoveEventListener()

• 書式:

• EventTarget.addEventListener(

イベント名, ハンドラ, Capture?);

• EventTarget.removeEventListener(

イベント名, ハンドラ, Capture?);

• イベント名: イベントの名前 "click" とか、 "keydown"

• ハンドラ: function foo(aEvent) { /* something */ } がお手軽で十分

• Capture?: true なら、Capture フェイズと、Target フェイズ、false

なら、Target フェイズと、Bubble フェイズ

Page 15: DOMイベントの基礎から深淵まで

addEventLister()とremoveEventListener()

• 単純な例1:

• 全ての要素で発生するkeydownイベントを知りたい

• イベントターゲットより先に知りたい

• removeする必要はない

document.addEventListener("keydown", function (aEvent) { /* something */ }, true);

Page 16: DOMイベントの基礎から深淵まで

addEventLister()とremoveEventListener()

• 単純な例2:

• 全ての要素で発生するkeydownイベントを知りたい

• イベントターゲットより後に知りたい

• removeする必要がある

function keydownHandler(aEvent) { /* something */ } document.addEventListener("keydown", keydownHandler, false); … document.removeEventListener("keydown", keydownHandler, false);

Page 17: DOMイベントの基礎から深淵まで

複数のイベントリスナ

•一つのイベントターゲットに、複数のリスナを登録しちゃうとどうなる?

Page 18: DOMイベントの基礎から深淵まで

複数のイベントリスナ

•先にaddEventListener()を利用して登録されたものが優先される。

•もずはっく日記の実例でテストすると以下の結果に • 親要素のcaptureフェイズに最初に登録されたハンドラ

• 親要素のcaptureフェイズに二番目に登録されたハンドラ

• イベントターゲットのbubbleフェイズに最初に登録されたハンドラ (captureへの登録よりも前)

• イベントターゲットのbubbleフェイズに二番目に登録されたハンドラ (captureへの登録よりも前)

• イベントターゲットのcaptureフェイズに最初に登録されたハンドラ

• イベントターゲットのcaptureフェイズに二番目に登録されたハンドラ

• イベントターゲットのbubbleフェイズに三番目に登録されたハンドラ (captureへの登録より後)

• イベントターゲットのbubbleフェイズに四番目に登録されたハンドラ (captureへの登録より後)

• 親要素のbubbleフェイズに最初に登録されたハンドラ

• 親要素のbubbleフェイズに二番目に登録されたハンドラ

Page 19: DOMイベントの基礎から深淵まで

stopPropagation()と、preventDefault()

•複数のイベントリスナを、複数のイベントターゲットに登録して処理していると、特定のハンドリングを行った後、そのイベントを他のハンドラに処理されたくない場合が考えられる

• stopPropagation()と、stopImmediatePropagation()を呼ぶと、それ以降のイベント発生を中止できる

• stopPropagation()では、次のEventTargetから、発生を抑制できる

• stopImmediatePropagation()では、同じEventTarget

に登録されている他の未処理のイベントハンドラの呼び出しも抑制できる

Page 20: DOMイベントの基礎から深淵まで

stopPropagation()と、preventDefault()

•特定のイベントを処理した後、ブラウザにデフォルトの動作(default action)を実行してもらいたいくない場合が考えられる

• preventDefault()を呼ぶことで、そのデフォルトの動作を抑制できる(ただし、a11y上、ブラウザが無視することはある)

• preventDefault()を呼び出すと、defaultPrevented属性値がtrueになる

Page 21: DOMイベントの基礎から深淵まで

stopPropagation()と、preventDefault()

• A要素と、B要素があり、BはAの子孫で、共にbubble

フェイズでイベントリスナが登録されている場合に、B

のリスナが、Aのリスナに処理をして欲しくないと考えた場合、どうするのが適切か?

• stopPropagation()を呼ぶのがお手軽。A以外にイベントリスナが登録されても、その動作もメンテナ無しに抑制できる

• preventDefault()を呼び、AのハンドラでdefaultPreventedをチェックするのが理想的。ただし、全ハンドラでdefaultPreventedを丁寧にチェックする必要があるのでバグは産みやすいかも

Page 22: DOMイベントの基礎から深淵まで

trustedイベントと、untrustedイベント

• trusted イベントとは、ユーザの入力や、要素の状態の変化等で、自然に発生したイベントのこと。isTrusted属性がtrue。preventDefault()を呼ばない限り、ブラウザのデフォルトアクションが必ず発生する

• untrusted イベントとは、Webアプリケーションが、document.createEvent()で作り出した、人工的なイベント。isTrusted属性はfalse。セキュリティ等の問題から、trusted イベントでは発生する、ブラウザのデフォルトアクションの発生は期待できない。発生するかは実装依存

Page 23: DOMイベントの基礎から深淵まで

init*Event()

•イベントを作って、それを特定の要素をターゲットに発生させるには?

var myEvent = document.createEvent("MouseEvents"); /* ここで myEvent を初期化 */ document.getElementById("foo") .dispatchEvent(myEvent); •初期化にcreateEventで指定したイベントに適した、init*Event()を利用するのが従来の方法。

Page 24: DOMイベントの基礎から深淵まで

init*Event()

interface Event { void initEvent(DOMString typeArg, boolean canBubbleArg, boolean cancelableArg); }; interface UIEvent { void initUIEvent(DOMString typeArg, boolean canBubbleArg, boolean cancelableArg, AbstractView viewArg, long detailArg); };

Page 25: DOMイベントの基礎から深淵まで

init*Event()

interface MouseEvent { void initMouseEvent(DOMString typeArg, boolean canBubbleArg, boolean cancelableArg, AbstractView viewArg, long detailArg, long screenXArg, long screenYArg, long clientXArg, long clientYArg, boolean ctrlKeyArg, boolean altKeyArg, boolean shiftKeyArg, boolean metaKeyArg, unsigned short buttonArg, EventTarget relatedTargetArg); };

Page 26: DOMイベントの基礎から深淵まで

init*Event()

•はい、もう無茶苦茶感が出てきましたね。他のインターフェース紹介をやめて、MouseEvent.initMouseEvent()の実例を見てみましょう。 myEvent.initMouseEvent("click", true, true, null, 1, 300, 450, 200, 250, true, false, false false, 1, null);

•分かるかボケー、と思った方の感覚が普通です!

Page 27: DOMイベントの基礎から深淵まで

event constructor

• DOM 4 Events では、event constructorという初期化方法が提案されていて、実際、Geckoでは一部イベントですでに利用可能

• JSON形式の初期化方法で、イベントが定義する、デフォルト値と違う属性だけを初期化すれば済む var myEvent = new MouseEvent("click", { bubbles: true, cancelable: true, detail: 1, button: 1, screenX: 300, screenY: 450, clientX: 200, clientY: 250, ctrlKey: true });

Page 28: DOMイベントの基礎から深淵まで

DOM イベントマニアックス!

•ここまで、言うても、基礎でしたので、暇してた方にはここから本番です。ユーザ入力系のDOM イベントを実際に実装してる人間だからこそな内容をいってみましょう。

Page 29: DOMイベントの基礎から深淵まで

Synthesized mousemove イベント

• Synthesized mousemove イベントとは?

•正式に仕様書等に出てくる用語ではなく、Geckoのソースコードから名付けました

•名前の通り、カーソルの動きに関係無く、生成されたmousemove イベント

•マウスカーソルがドキュメントの上に有る状態で、スクロールした後に、新しい要素の上で発生するmousemoveイベント

• WebKitと、Operaで発生。Geckoは内部イベントとしては存在するものの、Webアプリからは検知できず

• WebKit、Opera共にモディファイアキーの状態は不適切なので要注意

Page 30: DOMイベントの基礎から深淵まで

"mouseenter", "mouseleave"

• "mouseover"や、"mouseout"とは違い、あくまでも、そのイベントターゲットからカーソルが外へ出た場合に"mouseleave"イベントが、中へ入った場合に"mousenter"

イベントが発生する

•つまり、マウスを動かす度に、要素の境界を移動したかどうか、大量の確認が必要になる

• Webアプリの負荷増大に繋がるので、Geckoではこれらのイベントリスナが登録された場合にのみ、確認処理が行われる

•同時に複数の要素から出た場合、"mouseleave"は子孫から、"mouseenter"は祖先から順に発生する

Page 31: DOMイベントの基礎から深淵まで

"mouseenter", "mouseleave"

Geckoでは、私がレビューしました

Page 32: DOMイベントの基礎から深淵まで

マウスホイールのイベント

•マウスホイールのイベント、実にカオスなのはご存知ですか?

•どのぐらいカオスかというと、天下のGoogle様も、きちんとGeckoの独自イベントは理解できてなくて、処理に失敗しています。

•各ブラウザが独自案で実装した結果、メチャクチャでしたが、D3Eでめでたく、WheelEventインターフェースが定義され、"wheel"イベントとしてイベントが発生するようになりました。

Page 33: DOMイベントの基礎から深淵まで

レガシーマウスホイールイベント

• "mousewheel" イベント

• Gecko以外のエンジンでは採用されているレガシーイベント。

• wheelDelta 属性にスクロールの値が入っているものの、その値については厳密な定義は見当たらず

• Windows版、IEとChromeは、ネイティブイベントのデルタ値を単純に設定してる模様

• Windows版、Operaは、1行スクロールするにあたり、40を設定する模様

• Mac等では未調査

• WebKit以外では縦方向のホイールイベントしか発生しない

• とにかく、定義不明で使いにくいのが問題

Page 34: DOMイベントの基礎から深淵まで

レガシーマウイホイールイベント

• "DOMMouseScroll" イベント

• Geckoのみが実装

• detail属性値が、スクロールする行数

•高解像度スクロールが後にWindowsとMacで採用されてしまったため、これをpreventDefault()するだけでは、スクロールを完全に抑制することができなくなっている

Page 35: DOMイベントの基礎から深淵まで

レガシーマウイホイールイベント

• "MozMousePixelScroll" イベント

• Geckoのみが実装

• detail属性値が、スクロールするピクセル数

• Geckoで、スクロールを完全に抑制するにはこちらのイベントでpreventDefault()を呼ばないと、1行未満のスクロールがすり抜けてしまう。

Page 36: DOMイベントの基礎から深淵まで

D3E "wheel" イベント

• D3E では、WheelEvent インターフェースの、"wheel" イベントが定義された

•現在、IEとGeckoが実装済み

•斜め方向へのスクロールも一つのイベントで表現できるようになった(ただし、ネイティブイベントの制約上、Macのみ)

• deltaX、deltaY、deltaZがそれぞれの軸にそったスクロール量で、longではなく、double

• deltaModeで、deltaX、deltaY、deltaZのスクロール量が、ページ単位、行単位、ピクセル単位のどれであるかを表現

Page 37: DOMイベントの基礎から深淵まで

D3E "wheel" イベント

•残念ながらWebKitでは実装が行われていない

• Operaでも実装されていない

• Geckoの場合、delta値が実際にデフォルトアクションでスクロールする量と必ずしも一致しない(あくまで、Webアプリ

ケーションに対する、スクロールして欲しい量の目安と考えて)

• Geckoの場合、ユーザ設定でスクロール量を増やすと、delta値も増えるので、ゲームの主要な入力要素としては使えない

Page 38: DOMイベントの基礎から深淵まで

D3E "wheel" イベント

Geckoでは、私が実装しました

Page 39: DOMイベントの基礎から深淵まで

MouseEvent.buttons

• D3Eで追加された新しい属性

• GeckoとIEが対応。残念ながらWebKitはまだ。

•ビットフラグで、そのマウスイベント発生時に押されていたボタン全てのフラグが入っている

• 1=左ボタン、2=右ボタン、4=中ボタン(ホイール)、8=4

番目のボタン、一般的には戻るボタン、16=5番目のボタン、一般的には進むボタン

•実際にボタンを押していても、Windowsでは、マウスユーティリティの設定によっては、検知できない

Page 40: DOMイベントの基礎から深淵まで

MouseEvent.buttons

Geckoでは、私が実装しました!

Page 41: DOMイベントの基礎から深淵まで

getModifierState()

• D3Eで、MouseEventと、KeyboardEvent、WheelEventに定義されているメソッド

• IEとGeckoが対応。WebKitはまだ?

• パラメータにモディファイアキー名を入力する

• そのイベント生成時に押されていれば、true、押されていなければfalse

• モディファイアキー名は、"Shift"、"Control"、"Alt"、"Meta"、"AltGraph"、"CapsLock"、"NumLock"、"ScrollLock"、"SymbolLock"、"Fn"、"OS"

• IEでは、"ScrollLock"の代わりに、"Scroll"、"OS"の代わりに、"Win"が使われている

• IEでは、MouseEventと、KeyboardEventで検出できるモディファイアキーの状態に差がある

Page 42: DOMイベントの基礎から深淵まで

getModifierState()

Geckoでは、

私が実装しました!!

Page 43: DOMイベントの基礎から深淵まで

CompositionEvent

• IMEの未確定文字列入力時、確定時に発生するイベント

• "compositionstart" 入力開始。data属性値は未確定文字列で置換される文字列

• "compositionupdate" 未確定文字列が変更された場合に発生。data値は最新の未確定文字列

• "compositionend" 確定。data値は確定された文字列

•仕様上は"compositionupdate"のdata値のみを監視すると、未確定文字列が分かるはずだが、WebKitは"compositionstart"のdata値が最初の未確定文字列の値なので、面倒なことになっている

• "compositionupdate"の発生タイミングは、IEはエディタの内容が更新された後、GeckoとWebKitは更新される前。input要素や、textarea要素のvalue値に注意

Page 44: DOMイベントの基礎から深淵まで

CompositionEvent

Geckoでは、

私が実装しました!!!

Page 45: DOMイベントの基礎から深淵まで

KeyboardEvent.key and .char

• KeyboardEvent、実は標準化されるのは、D3Eが初めて(予定)

• .keyCodeはブラウザ依存の値を持っていて、今更統一、国際化対応が困難

• .charCodeは入力された文字のUnicodeのcode pointが入っているものの、使いづらい

• これらを解決するために、 .keyと .charが新たに提案された

•どちらの値もDOMStringになっていて、扱い易くなっている

• IEが既に実装していて、その内容がほぼそのままドラフトに掲載されている

Page 46: DOMイベントの基礎から深淵まで

KeyboardEvent.key and .char

•文字入力用ではないキーでは、.charはおおむね、空文字列(例外あり)、 .keyは、仕様で定義済みのキーの名前か、UAが命名できる場合はそれでも良い

•後者の仕様はWebデザイナが地獄を見ることになるので大問題

•必要なのに定義済みキー名に無いものは、多々、W3C

のbugzillaへ登録しまくっている最中

Page 47: DOMイベントの基礎から深淵まで

KeyboardEvent.key and .char

•文字入力用のキーでは、.charは、文字が入力される場合の文字列、 .keyは、.charと同じ値

• .keyと .charが同じ値なら意味がない

• .charが、実際に文字が入力されない場合にも入っているのでは、Webアプリが独自のテキストエディタを作りにくい

Page 48: DOMイベントの基礎から深淵まで

KeyboardEvent.key and .char

•現在、Mozillaから提案している(予定含む)改善案

• .keyの定義済みキー名を増やす

•未定義の .key名を利用する場合はprefixをつけるべき

• .keyの動作はそのままに、.charは実際にそのイベントでテキストエディタで入力される値にする

•例えば、Ctrl+Cだと、.key = "c"、.char=""

Page 49: DOMイベントの基礎から深淵まで

KeyboardEvent.key and .char

Geckoでは、

私が実装

がんばってます、

なう!!!!

Page 50: DOMイベントの基礎から深淵まで

Text

Q&A?