読者です 読者をやめる 読者になる 読者になる

Indie UI: Eventsについて妄想してみる

この記事はWeb Accessibility Advent Calendar 2013の17日目の記事です。

いいたいこと

Indie UI: Eventsはユーザーの操作を抽象化したDOMイベントを定義する仕様です。この仕様によってユーザーがどんなデバイスをどう使っているかにかかわらず、操作の意図をWebページが認識できるようになります。これにより、現在はアクセシブルにすることが難しいインタフェース(のいくつか)をアクセシブルにしやすくなる(Operableにしやすくなる)、私は期待しています。

様々なデバイスにおける様々な状況

Webにアクセスするために使われるデバイスは多様です。ということは今更書くほどのことでもないのですが、多様化によって、自分が作ったUIインタフェースを様々な環境で操作できる(Operable)ようにするために考えるべきことは増えていっています。

キーボード操作のサポートは重要ですが、キーボードを持たないデバイスも多くあります。例えば、スマートフォンやタブレットにはハードウェアキーボードを持たないものが多いですし、ノートPCだと思っていたらタブレットになっているデバイスもあります。こういったデバイス向けにWebページがジェスチャーを使ったインタラクションを実装することが多くあります。

一方、iOSAndroidWindows 8では(標準で組み込まれている)スクリーン・リーダー*1を有効にするとジェスチャー操作の体系がかわります。

例えば、シングルタップは画面上のアイテムを選択(とアイテムの情報の読み上げ)に対応します。選択してもアクションは実行しません。選択したアイテムのアクションを実行するには(画面のどこかを)ダブルタップします。また、1本指で左右にスワイプすると、前後のアイテムに移動します。

AndroidWindows 8では2本指で上下にスワイプするとページを上下にスクロールすることができ、2本指で左右にスワイプすると左右のページに移動する(ホームスクリーンなど)ことができます。iOSではスクロール/ページの移動は3本指での上下スワイプ、左右スワイプで行います。

ということで、ユーザーがどんな操作をするかはデバイスによって異なり、デバイスの使い方によっても異なります。そのため、自分が使っているジェスチャーにだけ対応すれば様々なデバイスで操作できるようになるかというと、そうではありません。そもそも、スクリーン・リーダーを起動している場合にはTouch Eventsが発生しない状況がほとんどです(Accessible JavaScript Gestures「VoiceOver ジェスチャについて」の「ダブルタップして押したまま(1 秒間)にし、標準のジェスチャ」)。このため、タッチデバイスでスクリーン・リーダーを使っている場合にジェスチャーをサポートすることはかなり難しいです*2

Firefox OSでのトリッキーな対応例

ここでは、トリッキーながらも現在の技術を使って対応している例を見てみましょう。Firefox OSのホームスクリーンです*3

Firefox OSのホームスクリーンはスクリーン・リーダーを起動していない場合には1本指で左右にスワイプして切り替えることができますが、スクリーン・リーダーを起動している場合には2本指で左右にスワイプしてページを切り替えることができます。*4

これを実現するために、Firefox OSではスクリーン・リーダーが2本指によるジェスチャーに応じてWheelEvent(deltaModeがDOM_DLETA_PAGE)を発生させています(AccessFu.jsm(Firefox OS 1.2)AccessFu.jsm(mozilla-central))。

wu.sendWheelEvent(p.x, p.y,
                  horizontal ? page : 0, horizontal ? 0 : page, 0,
                  Utils.win.WheelEvent.DOM_DELTA_PAGE, 0, 0, 0, 0);

そしてUI側ではタッチ操作の処理に加えて、deltaModeがDOM_DELTA_PAGEであるWheelEventを特別に処理しています(grid.js (Firefox OS 1.2)grid.js (master))。

function handleEvent(evt) {
  switch(evt.type) {
    // タッチ対応のコードが続くので略

    case 'wheel':
      if (evt.deltaMode === evt.DOM_DELTA_PAGE && evt.deltaX) {
        // XXX: Scroll one page at a time
        if (evt.deltaX > 0 && currentPage < pages.length - 1) {
          GridManager.goToNextPage();
        } else if (evt.deltaX < 0 && currentPage > 0) {
          GridManager.goToPreviousPage();
        }
        evt.stopPropagation();
        evt.preventDefault();
      }
      break;
  }
}

ここから、2つのことを感じます。

  • 結局DOMイベントがないとユーザーの操作を認識しようがない
  • WheelEventを処理することでFirefox OSのスクリーン・リーダーには対応はできるが、別の入力デバイス・操作方法への対応はつど書き足す必要がある

後者について補足すると、例えば、Firefox OSがBluetooth HIDに対応してキーボードやゲームパッドがつなげるようになったら、それらに対応したコードが必要になるということです*5

なお、2本指のスクロールでWheelEventを発するのは実装依存レベルだと思いますので、現状、普通のWebページでWheelEventにだけ依存するコードを書くのは避けたほうが良いと思います。

Indie UI: Events

だいぶ長くなりましたが、まとめると、ユーザーの操作を認識できるDOMイベントが足りない、そして個々のデバイスや個々の使い方について実装していくのではなく統一して扱えると良い、となります。そして、Indie UI: Eventsはユーザーの物理的な操作を抽象化したDOMイベントを定義している仕様です。

例えば、Indie UI: Eventsにはユーザーがスクロールをしたがっていることを表すイベントがあります。これはユーザーがどんな入力デバイスを使っていても、どんな操作方法を使っていても、ブラウザー(か、それよりも下のレイヤー)がスクロールのリクエストであると認識すれば、scrollrequestイベント*6が発生します。Webアプリケーションは個々の操作ではなくscrollrequestイベントだけを監視していれば、スクロールのリクエストを受け取ることができます。

これによって

  • 今はDOMイベントとして受け取れないユーザーの操作でもDOMイベントを受け取れるようになる
  • ユーザーが具体的にどんな操作をしているかによらずイベントを受け取れるようになるのでコードが簡潔になる

ことを私は期待しています。

このIndie UI: EventsはW3CIndie UI Working Groupが標準化しており、すでにIndie UI: Events 1.0のWorking Drafgが公開されています。Indie UI Working Groupとしては来年早々にもLast Call Working Draftを出したいようです。

Indie UI: Events 1.0で検討されているイベント

さて、Indie UI: Events 1.0で現在検討されているイベントはたくさんあります。Indie UI: Events 1.0のEditor's Draftから拾ってくると次のようになります。

イベント 操作の意図 対応する操作例*7
undorequest 操作を取り消す
  • Control+Z
  • Command+Z
  • シェイク(端末を振る)
redorequest 操作をやり直す
  • Control+Shift+Z
  • Command+Shift+Z
expandrequest トグルUIやツリーを開く(展開する)
collapserequest トグルUIやツリーを閉じる
dismissrequest ダイアログをキャンセルする、あるいはポップアップメニューを閉じる
  • ESC
deleterequest オブジェクトを削除する
linearfocusrequest フォーカスを前後に動かす
  • Tab
  • Shift+Tab
directionalfocusrequest フォーカスを上下左右などに動かす
  • 矢印キー?
palettefocusrequest (テキストなどを編集中に)フォーカスをパレットに動かす
toolbarfocusrequest (テキストなどを編集中に)フォーカスをツールバーに動かす
moverequest オブジェクトを移動する
  • マウスによるドラッグ?
  • タップジェスチャー?
panrequest 表示をパンする
  • タッチジェスチャー?
rotationrequest 回転する
  • タッチジェスチャー?
zoomrequest ズームする
  • Control++?、Control+-?
  • Command++?、Command+-?
  • マウスやトラックパッドによるスクロール?
  • ピンチズーム?
  • ダブルタップして上下にスワイプ(Android)?
scrollrequest スクロールする
  • 矢印キー
  • PageDown、PageUp
  • Home、End
  • マウスやトラックパッドによるスクロール?
  • スクロールバーでホイールボタンクリック(GTK)?
  • タッチジェスチャー?
  • スクリーンの上端をダブルタップ(iOS)?
valuechangerequest スライダーのようなウィジットの値を変更する
  • 矢印キー
  • PageDown、PageUp
  • Home、End
  • タッチジェスチャー
  • 1本指で上下のスワイプ(iOSのVoiceOver)?

これらのイベントを使って、より効率よくカスタムUIが実装できたら良いと思いませんか?

Indie UI: Eventsを使った例を妄想してみる

では、これぐらい楽にできたらいいなと思う例を妄想してみたいと思います。ホームスクリーンのような、左右に切り替わるUIの例です。

これまで:それぞれのデバイスごとに対応している例

ページが左右に切り替わるUIのHTMLの断片として次のようなものを考えてみました。

<div class="pageContainer">
  <div class="page"></div>
  <div class="page"></div>
  <div class="page"></div>
</div>

なんで全部divなんだよという突っ込みはさておき、これまでのイベントを使う場合を考えてみましょう。キーボードとポインティングディバイスとWheelEventをそれぞれ登録します。

var el = document.querySelector('.pageContainer');
var manager = new PageManager(el);

function PageManager(element) {
  this.element = element;
  this.element.addEventListener('keypress', this);
  this.element.addEventListener('wheel', this);
  this.element.addEventListener('pointerdown', this);
}

そしてイベントリスナーの中でそれぞれのイベントを処理していきます。

PageManager.prototype = {
  handleEvent: function(e) {
    switch (e.type) {
    // キーボードの処理
    case "keypress":
      switch (e.keyCode) {
      case e.DOM_VK_PAGE_UP:
        this.goToNextPage();
        break;
      case e.DOM_VK_PAGE_DOWN:
        this.goToPreviousPage();
        break;
      }
      evt.stopPropagation();
      evt.preventDefault();
      break;
    // wheelイベント用の処理
    case "wheel":
      if (e.deltaMode == e.DOM_DELTA_PAGE) {
        if (e.deltaX == 1) {
          this.goToNextPage();
        } else if (e.deltaX == -1) {
          this.goToNextPage();
        }
        evt.stopPropagation();
        evt.preventDefault();
      }
      break;
    // ポインティングデバイス用の処理
    case "pointerdown":
      // ポインティングデバイス(タッチなど)の処理が続く
      break;
    }
}

Android 4.4のアプリ一覧画面はPageUpPageDownで前後(画面上は左右)のページに移動することができるので、この例でもPageUpPageDownを処理しています。

Indie UI: Eventsを使った場合の例(妄想)

ではIndie UI: Eventsを使った場合の例を考えてみましょう。

現在のEditor's Draftによると、Indie UI: Eventsを使う場合には、まず、イベントを受け取りたい要素にuiactions属性を設定します。これは、スクロールのリクエストを受け取らないページに対して、ブラウザーがイベントを生成しなくても済むようにするためのようです。今回は各ページを格納している.pageContainerにuiactions属性を設定します。uiactionsにはイベントの名前を設定することが検討されていますので、スクロールの場合にはscrllrequestを設定します。

<div uiactions="scrollrequest" class="pageContainer">
  <div class="page"></div>
  <div class="page"></div>
  <div class="page"></div>
</div>

次に要素に対してイベントリスナーを追加します。Indie UI: Eventsのイベントを1つだけ設定しました。

var el = document.querySelector('.pageContainer');
var manager = new PageManager(el);

function PageManager(element) {
  this.element = element;
  this.element.addEventListener('scrollrequest', this);
}

最後にイベントを処理します。

PageManager.prototype = {
  handleEvent: function(e) {
    switch (e.type) {
    // スクロールのリクエストに対する処理
    case "scrollrequest":
      switch (e.scrollType) {
      case "pageRight":
        this.goToNextPage();
        break;
      case "pageLeft":
        this.goToPreviousPage();
        break;
      }
    }
  }
}

あくまでこれは、これぐらい楽に書けたら良いよねという妄想であり、実際にこう書けるかどうかはまだわかりません。ただ、デバイスやユーザーの使い方によらず操作の意図をWebページに伝えたいという仕様の目的は変わらないと思いますので、何らかのレベルで楽に書けるようになると信じています。

まとめ

Indie UI: Eventsはユーザーの操作を抽象化したDOMイベントを定義する仕様です。この仕様によってユーザーがどんなデバイスをどう使っているかにかかわらず、操作の意図をWebページが認識できるようになります。これにより、現在はアクセシブルにすることが難しいインタフェース(のいくつか)をアクセシブルにしやすくなる(Operableにしやすくなる)、私は期待しています。

*1:iOSのスクリーン・リーダーはVoiceOver、Androidのスクリーン・リーダーはTalkback、Windows 8のスクリーン・リーダーはNarratorです

*2:現状では、ジェスチャー以外の操作方法(ボタンなど)を提供することでアクセシブルにする、とするのが現実的です

*3:Firefox OSのUIはHTMLとCSSJavaScriptで書かれています。

*4:Firefox OS 1.2以降でのスクリーン・リーダーの起動方法:

  1. 「Settings」から「Device information」を選ぶ
  2. 「Device information」パネルの「More Information」を選ぶ
  3. 「More Information」パネルの「Developer settings」にある「Developer」を選ぶ
  4. 「Developer」パネルの「Accessibility」にある「Screen reader」にチェックを入れる

*5:さすがに、ホームスクリーンをゲームパッドに対応させるためにGamepad APIを使うことはないと思いますが

*6:正式な勧告までにscrollrequestイベントの名前が変更される可能性、あるいは、仕様から削除される可能性がありますのでご注意ください

*7:対応する操作例はIndie UI: Events 1.0の参考情報を私の想像で補って記載しています。ブラウザーなどがこの通りに実装する保証、といったものはありません