リンクのようなボタンを作る ― 2010年12月15日 23時52分
こんばんは、JavaScript Advent Calendar 2010、15 日目担当の nanto_vi (なんと) です。12 月 15 日が何の日か調べてみると東北本線が宮城県に到達した日とのこと。当時は上野から仙台まで 12 時間 20 分かかったそうです。それから 123 年を経た現在では同じ時間で鹿児島中央から新青森まで行けるようになり、鉄道の速度にも JavaScript の実行速度にも日進月歩を感じる今日この頃です。
さて、アプリケーションを作っていると、見た目はリンクのようだがリンクでない UI 部品を使いたくなるときがあります。ここで「リンクでない」とは、クリックしてもページ遷移が発生しないということです。このような UI 部品は、ページ遷移の代わりにメニューの表示といった何らかのアクションを引き起こす、すなわちボタンとして振舞います。
ユーザーインターフェース記述言語として HTML を使っているとき、この「リンクのようなボタン」をどのように実現すればいいのでしょうか。
input、button 要素
ボタンとして振舞うものはボタンとして記述すべきです。HTML では汎用的なボタンとして <input type="button">
及び <button type="button">
が用意されています。スタイルシートを使えば見た目をリンクのようにもできるでしょうし、画像を使いたければ <input type="image">
もあります。
<input type="button" value="リンクのようなボタン" onclick="...">
<button type="button" onclick="...">リンクのようなボタン</button>
しかし、フォームコントロールに対してはスタイルシートが期待通り適用されないこともあり、ボタンをどうしてもインライン要素にしたいときにこの方法は取れません。
a 要素
リンクのように見えるならリンクにすればいいということで a 要素が使われることもあります。click イベントを処理するときにデフォルトアクションをキャンセルすれば、もともと指定してあったリンク先に飛ぶことはありません。あるいはリンク先に javascript:void(0);
と指定することでページ遷移が発生しないようにします。
<a href="#" onclick="...; return false;">リンクのようなボタン</a>
しかし、a 要素はあくまでハイパーリンク、すなわち他のリソースへ移動するためのものです。外見がどうあろうとリンクでないものの記述に使うの好ましくありません。
span 要素
click イベントに対する処理はどんな要素にも付加できるので、a 要素にこだわる必要はありません。「リンクのような」の部分はスタイルシートで実現できます。
<style>
.trigger {
color: #00f;
text-decoration: underline;
cursor: pointer;
}
</style>
<span class="trigger" onclick="...">リンクのようなボタン</span>
しかし、これではキーボードを用いてボタンにアクセスすることができません。多くのブラウザでは Tab キーでリンクやフォームコントロールへ移動できますが、span 要素はその対象から外れています。
span 要素 + tabindex 属性
tabindex 属性はフォーカス順を変更するものだと思っていたあなた、それはこの属性が持つパワーのほんの一部でしかありません。HTML5 において tabindex 属性は要素をフォーカス可能にする属性として生まれ変わったのです。この属性はどんな要素にもつけられ、値に 0 を指定すればその要素がキーボードアクセス可能になります。
<span class="trigger" tabindex="0" onclick="...">
リンクのようなボタン
</span>
しかし、Tab キーでこのボタンにフォーカスし、Enter キーを押しても何もおきません。a要素によるリンクなら Enter キーを押すと click イベントが発生するのにも関わらずです。なお、Opera なら span 要素でも Enter キーにより click イベントが発生し、指定したアクションが実行されます。
span 要素 + tabindex 属性 + onkeypress 属性
click イベントが発生しないなら自分で click イベントに対する処理を呼び出せばいいのです。onclick 属性に指定したコードは onclick プロパティから関数として取得できます。Enter キーを表すキーコードは 13 なので、そのときのみ処理を実行します。
<span class="trigger" tabindex="0" onclick="..."
onkeypress="if (event.keyCode === 13) this.onclick(event);">
リンクのようなボタン
</span>
しかし、click イベントに対する処理が onclick 属性に書かれているとは限りません。addEventListener や attachEvent メソッドでイベントリスナが追加されていることもあれば、祖先要素、文書ノードで click イベントが処理されていることもあります。また、Opera ではこの場合 Enter キーを押すと onclick 属性の内容が2回実行されてしまいます。
span 要素 + tabindex 属性 + キーイベント処理
click イベントに対する処理を直接実行できなくとも、click イベントを発生させれば自然とそれらが実行されます。イベントを発生させるのに、DOM イベントモデルでは dispatchEvent、IE のイベントモデルでは fireEvent メソッドを用います。Opera 対策に keypress イベントのデフォルトアクションをキャンセルしておきましょう。
<script>
function activate(event) {
event = event || window.event;
if (event.keyCode !== 13) return;
if (document.createEvent) {
var e = document.createEvent('MouseEvent');
e.initMouseEvent(
'click', true, true, event.view, 1,
event.screenX, event.screenY, event.clientX, event.clientY,
event.ctrlKey, event.altKey, event.shiftKey, event.metaKey, 0, null
);
event.target.dispatchEvent(e);
event.preventDefault();
} else if (document.createEventObject) {
var e = document.createEventObject(event);
event.srcElement.fireEvent('onclick', e);
event.returnValue = false;
}
}
</script>
<span class="trigger" tabindex="0"
onclick="..." onkeypress="activate(event);">
リンクのようなボタン
</span>
しかし、視覚的なブラウザであれば見た目からこの要素が何らかのアクションを引き起こすことがわかりますが、環境によってもそもそもこれが「押せる」ということすら伝わらないかもしれません。
span 要素 + tabindex 属性 + キーイベント処理 + role 属性
視覚によらず、機械的に UI 部品を認識するための仕様として WAI-ARIA があります。要素がボタンの「役割」を果たすことを表すには、role 属性の値に "button" を指定します。これで人の目からだけでなく、機械から見たときもボタンとして認識・操作できるようになりました。
<span class="trigger" role="button" tabindex="0"
onclick="..." onkeypress="activate(event);">
リンクのようなボタン
</span>
サンプル
リンクのようなボタンのサンプルで実際の挙動を確認できます。
終わりに
以上はあくまで JavaScript の使える環境が前提です。スクリプトが動かなくとも最低限の機能は利用できるよう気をつけましょう。スクリプトが動く場合でも、本当にリンクのようなボタンでなければいけないのか、通常のボタン、またはハイパーリンクでは実現できないのかよく検討した上で使うようにしてください。
参考資料
Firefox Developers Conference 2010 ― 2010年12月28日 00時18分
Firefox Developers Conference 2010 に行ってきた。全体のまとめとしては以下が詳しい。
- Firefox Developers Conference 2010 アウトラインメモ | Web scratch
- nakamura001 @ ウィキ - Firefox Developers Conference 2010
はてなブックマークで fxdevcon タグがつけられたエントリーを見てまわるのもいい。Firefox 4 ベータ版機能概要では新しいタブインターフェース "Panorama" の紹介動画を見られる。
内容に関しては上述のページを参考にしてもらうとして、個人的に感じたことををいくつか挙げる。
Beyond Firefox 4 (Jay Sullivan)
Mozilla はユーザーの選択肢を広めることを重視する、逆に言えば単一のプラットフォームを目指すわけではないということのようだ。
John Resig feat. Shibuya.js
10+1 Things you should know about JavaScript testing (t_wada)
私の場合、Web ブラウザ向け JavaScript のテストを書いていないのはさくっとテストできる環境を知らないからというのが大きい。Firefox 拡張に関しては UxU という素晴しい環境があったから部分的とはいえテストを書けた。
カスタムイベントで処理をつなぐというのは結構やる。イベントモデルの実装も何回かやったけど、jQuery が使えるなら jQuery の実装 (bind、trigger) を使うのが楽だ。カスタムイベント名には Progress Events 仕様で提案されているものを流用したほうがいいかと思ったが、trigger 時に独自引数を渡すならかえって紛らわしいかもしれない。
// 独自のオブジェクトに jQuery のイベントモデル実装を組み込む例
function Loader() {
this.$ = $({});
}
$.extend(Loader.prototype, {
method: function () {
this.$.trigger('load', args);
}
});
var loader = new Loader();
loader.$.bind('load', function (args) {});
Node.js にまつわる 7 つの誤解 (meso)
現在、Node.js の開発は個人の手を離れ Joyent 社主導で行われているそうだ。会社によってはライブラリ/フレームワークの採用基準に、それが一定規模の団体によって保守されているかどうかが含まれるだろうから、Node.js の普及を促進する上ではよいことだと思う。
トークセッション: HTML5 時代の技術で Web プラットフォームはどう変わるのか (矢倉眞隆、村岡正和、浅井智也)
W3C CSS working group ではレイアウト関係が要注目とのこと。Flexible Box Layout Module は Mozilla の XUL ボックスモデルに由来するもので、(Mozilla から Apple へ移籍した Dave Hyatt の手により) 類似のモデルが WebKit でも実装されている。ただし、現在の草案は display プロパティの値に "box" ではなく "flex" を採用するなど、両実装と異なる点が多い。Grid Alignment Module は Microsoft が中心となって策定を進めている (編者の一人 Alex Mogilevsky は Flexible Box Layout Module の編者も兼任)。両モジュールともアプリケーション UI の整形を念頭に置いているが、手軽さでは Flexible Box、柔軟性では Grid Alignment が勝っているように感じる。
まとめとして、矢倉氏は描画には canvas だけでなく SVG、通信には WebSocket だけでなく Server-Sent Events など、あることを実現するのにさまざまな手段があるのを知ってほしいと、村岡氏は積極的に HTML5 を書き、関連 API を使って開発し、そして仕様策定者側にフィードバック (提案) しようと述べていた。
大ライトニングトーク
HTML 2.0 (TAKESAKO)
"2.0" というのはバズワード。HTML パーサのエラー処理方法の違いを利用してブラウザを判別する試みである。HTML5 では HTML 構文の解析方法がエラー処理も含めて規定されるので、今後ブラウザが HTML5 HTML 構文に対応していく中でこのような試みは困難になっていくと思われる。
placeholder 実装マニアックス
テキスト入力欄に何らかのメッセージを表示し、入力欄にフォーカスするとそれが消えるという UI は以前から見られた。HTML5 では placeholder 属性でこのメッセージを指定できるが、未対応ブラウザでこの挙動を再現しようとすればスクリプトを使う必要がある。
まず考えられるのは入力欄の value プロパティにメッセージの文面を設定し、フォーカス移動に際してこれを消去することである。しかしこの場合、フォーム送信時のメッセージ消去や、ブラウザがフォームコントロールの入力値を記憶することへの対処が必要となる。
別の方法として、CSS を使いメッセージと入力欄を重ね合わせることも可能だ。メッセージ部分を絶対配置の要素とし、文書木上では入力欄の直前に挿入する。left 及び top プロパティを指定しなければ絶対配置の要素は「その要素が絶対配置でなかったときの位置」に置かれる。この絶対配置要素の初期位置は意外と便利で、top、left、right、bottom プロパティのいずれかひとつのみ指定すれば、水平方向または垂直方向にだけずらすこともできる。
なお、placeholder に指定するのは入力欄に関する補助的な情報 (なくても問題ないもの) であり、入力欄に対するラベルではない。ラベルは label 要素で指定する。中には入力中に参照したい情報もあるだろうし、状況によっては placeholder の内容が提示されないこともあるようなので、そもそも placeholder を使うべき場面なのか検討したほうがいい。
懇親会
食事があっという間になくなってしまったのが残念だった。
ブラウザの進化速度は速い。できることはどんどん多くなるが、それらをどう組み立てていくかというパターンはまだ不安定に思える。その部分を探っていきたい。
最近のコメント