divタグのグループをonfocus/onblurイベントでラジオボタン風に見せる

JavaScriptを使っていろんなdivエリアをクリックで選択状態にできたらいいだろうな、と思って(というか必要に迫られてですが)作ってみました。 2通りありますが、動作確認を見て分かるように、最初のほうは少々強引です…

画像をクリックするとサンプルデモが表示されます。
onfocus_onblur_div.jpg
動作確認: IE7 ○ IE6 ○ Firefox2 ○ Safari3 × Opera9 ×

フォーカスされるタグたち

フォームの部品(input, select, textareaなど)には、そのフォーム部品が選択された瞬間にonfocusイベント、フォーム部品が非選択状態になった瞬間にonblurイベントが発生します。 現在、以下のHTMLタグ要素がonfocusイベントとonblurイベントをサポートしています。

onfocus/onblurをサポートしているタグ要素

  • a要素
  • area要素
  • button要素
  • input要素
  • label要素
  • select要素
  • textarea要素

onfocusやonblurの便利なところは、ページの中で必ず1つだけに発生するので、多くの要素から1つだけに焦点を当てるといった、ラジオボタングループみたいな効果を簡単に与えることができる点です。

フォーカスされないタグたちをなんとかしたい

一方<p>タグや<div>タグなど、本来情報を表示したりエリアを定義したりするだけのタグには当然このようなものはありません。 しかし、実際に制作していると「タググループの中でそのタグの領域が現在選択されている」といったラジオボタングループのような状態を表現したい場合もあり「どうすればいいのかな…」と思っていました。

そこでいろいろ調査と試行錯誤した結果、以下の2通りの方法を発見。

  • タグのグループにtabindex属性を付加することでonfocus/onblurイベントを受信できるようして動かす方法
  • JavaScriptでdocumentにmousedownイベントのリスナーになってもらってタググループを管理する方法

とりあえず、1番目の方法から。

HTML準備

まずHTMLのほうですが、簡単にこんな感じ。 3つのdivエリアをそれぞれ「area1」「area2」「area3」というid属性をつけて記述します。

<body>
<h1>divタグがonfocus/onblurできているように見せるサンプル</h1>
<div id="area1" tabindex="1000" class="not-selected"><h2>area1</h2></div>
<div id="area2" tabindex="1001" class="not-selected"><h2>area2</h2></div>
<div id="area3" tabindex="1002" class="not-selected"><h2>area3</h2></div>
</body>

tabindex属性は0~32767までの数字を指定できますが、他の要素の邪魔にならないくらいを想定して1000ぐらいからで十分かと思います。

で、このHTMLをCSSでスタイリングしてfloat:leftを使った3カラムレイアウトにしておきます。

#area1,#area2,#area3 {
width:200px;
height:300px;
margin-right: 20px;
float:left;
}

選択/非選択っぽいデザインスタイルを作る

もうひとつスタイルの定義ですが、今度は状態を表すためのクラススタイル定義です。 まず3つのエリアを見やすく背景色を別々に作り、これをそれぞれ「not-selected」クラス属性のスタイルにしておきます。

次に、選択された状態のスタイルとして「selected」クラスを定義して背景を黒にし、いかにも「選択されましたよ」というスタイルにしておきます。

#area1.not-selected {
background-color:#99CC33;
}
#area2.not-selected {
background-color:#00CC99;
}
#area3.not-selected {
background-color:#FFCC33;
}
div.selected {
background-color:#000000;
color:#FFFFFF;
}

方法1:div要素をonfocus/onblurイベントに反応させる

で、JavaScriptのほうですが、またまたprototype.jsのお世話になります。 div要素のIDを配列変数に入れておいてループさせながらEvent.observeでonfocus/onblurイベントに割り当てるイベント関数を設定します。 onfocusイベントによってfocusMe()が呼ばれると、対象のdiv要素から「not-selected」クラスが削除され、「selected」クラスが追加されます。 逆にonblurイベントによってblurMe()が呼ばれると、「selected」クラスが削除され、「not-selected」クラスが追加されるようになっています。

function focusMe(evt) {
var elem = Event.findElement(evt, 'div');
Element.removeClassName(elem, 'not-selected');
Element.addClassName(elem, 'selected');
}
function blurMe(evt) {
var elem = Event.findElement(evt, 'div');
Element.removeClassName(elem, 'selected');
Element.addClassName(elem, 'not-selected');
}
function init() {
this.ids = ['area1', 'area2', 'area3'];
for (var i=0; i<this.ids.length; i++) {
Event.observe(this.ids[i], 'focus', focusMe.bindAsEventListener(this));
Event.observe(this.ids[i], 'blur', blurMe.bindAsEventListener(this));
}
}
window.onload = init;

実際、選択したときにフォーカスの特徴である枠線(うすい点線)がつくので、それがイヤな人は次の方法をお試しください。

方法2:documentをmousedownイベントに反応させる

動作確認: IE8 ○ IE7 ○ IE6 ○ Firefox3 ○ Firefox2 ○ Safari4 ○ Safari3 ○ Opera9 ○

2番目の方法は、tabindexを使わない方法です。 したがって、onfocus/onblurイベントを使わず、mousedownイベントを使ってタグのグループのスタイルを変更します。

上の見出しに注目すると「あれっ?」ていう感じですが、方法2ではタググループの状態をmousedownイベントで管理するのはdocumentの役目です。 というのも、クリックされるのが3つのdiv要素の中だけであれば、わざわざ最上位のdocumentに割り当てなくてもいいんですが、クリックされるのはdivの中だけとは限りません。 フォーム要素でも、フォーカスされていた要素は、フォームの外側がクリックされるとonblurイベントが発生し、フォーカスがなくなります。 これも同じように再現させなければいけません。 ですので、divの外側をクリックすると選択状態ではなくなるように、ちゃんとイベントを受け取ることができるdocumentに割り当てることにしたわけです。

HTMLは「方法1」とほとんど同じ。 tabindex属性が省かれているだけです。

<body>
<h1>divタグがonfocus/onblurできているように見せるサンプル</h1>
<div id="area1" class="not-selected"><h2>area1</h2></div>
<div id="area2" class="not-selected"><h2>area2</h2></div>
<div id="area3" class="not-selected"><h2>area3</h2></div>
</body>

CSSスタイルはまったく同じですので省略。 で、JavaScript部分。 3つのdiv要素をチェックするcheckFocusArea()を定義して、この関数をdocumentのmousedownイベントに対するイベント関数としてinit()内で登録します。

function checkFocusArea(evt) {
var elem = Event.findElement(evt, 'div');
for (var i=0; i<this.ids.length; i++) {
var id = ids[i];
if (elem && elem.id == id) {
Element.removeClassName(id, 'not-selected');
Element.addClassName(id, 'selected');
} else {
Element.removeClassName(id, 'selected');
Element.addClassName(id, 'not-selected');
}
}
}
function init() {
this.ids = ['area1', 'area2', 'area3'];
Event.observe(document,'mousedown',checkFocusArea.bindAsEventListener(this));
}
window.onload = init;

このように、イベントオブジェクトを受け取ったあと、クリックされたdiv要素を取得し、事前に配列に入れておいたdivグループのIDリストとIDを比較して判断するようにしてあります。 当然、クリックされた箇所がdivではなかった場合、elemにはundefinedが入りますので、全て「not-selected」になります。

この方法の欠点としては、documentがmousedownイベントを受け取りつつも、別要素のdivの状態をコントロールするのであまりオブジェクト指向的に美しくない点と、TABキーによる切り替えをサポートしていないことでしょうか。

でも、TABキーによる切り替えをさせたくない場合は2番目の方法がいいと思います。 グループの選択状態をブラウザによるフォーカスと独立した構造にしたいってこともありますしね。

反対に、「方法1」のdiv内にtabindex属性を付加する方法はHTML的にはイレギュラーな方法ですので、ちゃんとしたHTMLでやりたい場合には「方法2」のほうがおすすめです。

2種類の方法を掲載してみましたがどうでしょうか。 状況に合わせて使い分けてみてください。 あと、もっといい方法や修正箇所があったらご指摘くださいね。

※2009/12/17 document.body → document にmousedownを受け取らせる記事に修正しました。

このページをシェアする

2008-03-14