この日記はMozillaのプロダクトへの貢献者としての私の成果を中心に、気になったバグやWeb界隈の話題について書いていますが、 断り書きがある場合を除き、いかなる団体のオフィシャルな見解ではありません。あくまでも個人的なものです。 Mozilla Foundation、Mozilla Corporation、及び関連企業の公式情報ではないことに注意してください。

現在、XHTML 1.0 (もどき)から、HTML5なコンテンツに修正中です。古い日記は修正が完了していませんので表示が崩れます。 順次、修正していく予定ですのでしばらくお待ちください。

もずはっく日記(2015年1月)

2015年1月1日

KeyboardEvent.keyを利用する前に知っておくべき事
初回投稿日時: 2015年01月01日12時32分46秒
最終更新日時: 2015年01月01日12時33分46秒
カテゴリ: Events Javascript
SNS: (list)

Firefox (Gecko)では、昨年、D3Eで定義しているKeyboardEvent.keyの実装を大きく前進させました。ですが、この新しい、便利な属性を使う前に色々と知っておいた方が良いことがあります。

まず、KeyboardEvent.keyとはどのようなものなのか、おさらいしておきましょう。これは、過去との互換性から、KeyboardEvent.keyCodeの値を標準化したくてもできないために考案されたものです。KeyboardEvent.keyKeyboardEvent.keyCodeが抱えていた、不明確な(意味不明な)数値という問題を取り除き、コードを読みやすくするため、文字列型になっています。

文字列型だと何が良くなっているのかというと、やはり、一番大きなポイントは、同じ値で別のキーを示すことが少なくなるという点につきます。

例えば、ブラウザAが、64という数字をキーAにマッピングしていたとします。この場合、そのブラウザで、キーAが押されたのか確認するためには、if (event.keyCode == 64) {といった形で検査できます。しかし、ブラウザBが、64という数字をキーBにマッピングしているようなケースが多々あります。このため、先の例は、ブラウザBでは別のキーBが押されたかどうかを検査していることになります。これを、ベタに修正すると、if ((isBrowserA && event.keyCode == 64) || (isBrowserB && event.keyCode == 32)) {という感じになり、非常に扱いづらいです。

ですが、文字列型だと、従来、あるブラウザで使われていた値を回避して、適切な値を新規に簡単に定義できるという利点があります。ですので、ブラウザAが、キーAに"Foo"というキー名を定義し、ブラウザBが、キーAに"Bar"というキー名を定義している場合は、if (event.key == "Foo" || event.key == "Bar") {と単純に検査できますし、文字列という特性上、ブラウザBが、キーBに"Foo"という値を付けている可能性は、本来、ゼロに近いはずなので、ブラウザの検査が不要になってきます(実際には理想通りには行きづらいですが、かなりマシになったのは事実です)。

また、KeyboardEvent.keyが何を意味するのかもおさらいしておきましょう。KeyboardEvent.keyの値は、そのキーが押された状態で、何が起きるべきかを示す値になります。モディファイアキーや、現在選択しているキーボードレイアウト(言語)に依存した値になります。文字を入力するキー(printable key)では、入力される文字がその値となります。それ以外のキーは、事前に仕様で定義されているキー名になります。仕様で定義されていないキー名の場合、"Unidentified"となります。

ちなみに、Mozillaでは、Firefox OSの内部処理等で、仕様で定義されていないキー名がどうしても必要な場合は、"MozFooBar"といった形で、"Moz"プレフィックスを付けていく方針ですので、将来の仕様や、その時点で既に存在しているWebアプリに悪影響を与えることは皆無と思います。この柔軟さが文字列型の偉大なところです。

なお、ゲーム等で、モディファイアキーや、選択されているキーボードレイアウトに関わらず、押されたキーが知りたいという用途もあるでしょう。こちらのために、D3Eでは、KeyboardEvent.codeという属性も定義しているので、興味のある方は仕様書を参照してみてください。

このように、KeyboardEvent.keyは文字列という贅沢な値を利用することで、非常に理想的なものになっているかのように見えます。しかし、現実はそう甘くありませんでした。

実際にKeyboardEvent.keyを利用しようとすると、メンテナンスを行っている時に、すぐに気付く大問題が出てきます。それは、検索のしにくさです。まず、文字入力キー以外のキー名は、単純に、[A-Z][a-zA-Z0-9]+というもので、プリフィックス等がありません。このため、ブラウザが仕様の変更やバグ修正に伴い、キー名を変更してしまうと、そのキー名を利用している箇所を探すのが大変になります。

さらに良くないのは、この簡潔な属性名です。.keyでソースコードを検索してみても、.keyCodeにもマッチしてしまいますので、参照箇所すら簡単にリストアップすることはできません。

私がこの問題に気付いた時には、「時既に遅し」で、定義済みキー名にプリフィックスをつける等の改善は却下されざるをえない状況でした。

ですが、このような欠点があらかじめ分かっていたら、コーディング時に、メンテナンスしやすいコードというのは簡単に書けるはずです。私がお勧めしたいのは、検査したいキー名をグローバル変数に格納してしまうという手法です。本当はconstが使えると良いのですが、IE 10以下に対応するためにはvarを利用するしかありません。

大きなWebアプリの場合、キー名の定義を独立したファイルで行うと、管理がより簡単に済むでしょうから、例えば、d3e-keynames.jsというファイルを作成することをお勧めします。その内容は以下のような感じになるでしょう(ブラウザ判定はいい加減ですので、ちゃんとしたものを調べてください、というか、私よりも実際にWebアプリを書かれている方の方が詳しそう)。

var _isIE = navigator.userAgent.indexOf("MSIE") >= 0;
var _isFx = navigator.userAgent.indexOf("Gecko/") >= 0;
var _isFx25 = isFx && ...;
var _isFx29 = isFx25 && ...;
var _isFx37 = isFx29 && ...;

var KEYNAME_ArrowLeft  = _isIE || (_isFx && !_isFx37) ? "Left" : "ArrowLeft";
var KEYNAME_ArrowRight = _isIE || (_isFx && !_isFx37) ? "Right" : "ArrowRight";
var KEYNAME_ArrowUp    = _isIE || (_isFx && !_isFx37) ? "Up" : "ArrowUp";
var KEYNAME_ArrowDown  = _isIE || (_isFx && !_isFx37) ? "Down" : "ArrowDown";

このように、KEYNAME_というプリフィックスを与えてあげることで、検索が容易になります(この手法だと、キー名自体を検索することは希になると思いますが、KeyboardEvent.keyを利用している箇所を探すには相変わらず必要です)。

また、ブラウザにとって適切な値を予め設定しておくことで、肝心の実装部分が非常にシンプルになります。例えば、上の例のように、最新の仕様案で名前が変化してしまったキー名であっても、if (event.key == KEYNAME_ArrowLeft) {のようにシンプルに検査できます。

KeyboardEvent.keyは、元々、IE 9が実験実装を行って、W3Cに提案したものです。その後、名前の適正化や、ブラッシュアップがW3Cで行われ、Firefox 37以降では、ほぼ、最新の仕様書案に沿った実装になっています。これにより、一部のキー名では例にあるように、既に、非互換が発生しています。

現時点では、Firefox (Gecko)がKeyboardEvent.keyの実装では大きく先行させています。その理由は、オープンソースで実装が誰にでも(他のブラウザ開発者にも)参照できるという形である必要が望ましいという点、もうひとつは、主要なプラットフォームで同時に実装していくので、問題の洗い出しが行いやすい、という点です。

特に、ブラウザ間の互換性を確実なものにするには、前者の要素が不可欠です。また、ブラウザ開発者のみではなく、Web開発者の方向けにも、MDNで、キー名の完全な一覧表を公開しています。他のブラウザ開発者もこの表を参照してくれれば、将来的には完全に同じマッピングが行われたKeyboardEvent.keyが実現するのではないか、という目論見ですし、Web開発者の方は比較的容易にクロスブラウザなスクリプトを書くことができるでしょう。

今後、これを他のブラウザがどのようなスタンスで実装してくるのか、これを観察してみれば、各ブラウザベンダの、Web標準への「意識」がどのようなものなのかを知る、良い機会になるかもしれません。ある意味、ならないことを祈るのみですが。

関連するかもしれないエントリ

関連するかもしれないエントリを発見できませんでしたが、無いとは限りません。