MathMLの2つのマークアップ

MathMLマークアップは大きく2種類に分かれます。プレゼンテーションとコンテントです。

プレゼンテーションマークアップ

プレゼンテーションマークアップは数式のレイアウトを表現します。数式のレイアウトは数式の書き方と言っても良いと思います。例えば1を4で割る、1÷4とも書けますし1/4とも書けますし、分数で書くこともできます。

<math>
<mn>1</mn>
<mo>÷</mo>
<mn>4</mn>
</math>

1÷4

<math>
<mn>1</mn>
<mo>/</mo>
<mn>4</mn>
</math>

1/4

<math>
<mfrac>
<mn>1</mn>
<mn>4</mn>
</mfrac>
</math>

14

また、同じ数式の書き方をしても、表すものが同じとも限りません。xの対数logxと書いた場合に、暗黙的な底を何とするかは文脈なり分野によって異なるでしょうし、3+4iのiも虚数単位なのか、ただの変数なのか、それとも別の何かは文脈によります。

<math>
<mi>log</mi>
<mn>x</mn>
</math>

xの対数

<math>
<mn>3</mn>
<mo>+</mo>
<mrow><mn>4</mn><mi>i</mi></mrow>
</math>

3+4i

コンテントマークアップ

それに対して、コンテントマークアップはより抽象的・厳密に数式を表現します。1を4で割るなら、演算子「割る」をオペランド「1」と「4」に適用する、と表現します。それが最終的にどのような数式になるかは表現しません。また、対数虚数単位にも専用の要素があるため、より厳密に表現できます。

<math>
<apply>
<divide />
<cn>1</cn>
<cn>4</cn>
</apply>
</math>

1を4で割る

<math>
<apply>
<log /><!-- 暗黙的な底は10 -->
<ci>x</ci>
</apply>
</math>

xの自然対数

<math>
<apply>
  <plus />
  <cn>3</cn>
  <apply>
    <times />
    <cn>4</cn>
    <imaginaryi /><!-- 虚数単位 -->
  </apply>
</apply>
</math>

複素数3+4i

実際のマークアップ

MathMLとしてよく使われているのはプレゼンテーションマークアップで、一部のブラウザーによるサポートも進んでいます。EPUB 3.0で埋め込めるMathMLも基本的にはプレゼンテーションマークアップです(EPUB Content Documents 3.0)。

なお、MathMLではプレゼンテーションマークアップアノテーションとしてコンテントマークアップを埋め込むこともできます。また、コンテントマークアップアノテーションとしてプレゼンテーションマークアップを指定することもできます。

outline-color: invert

端的にいってFirefoxのoutlineはわかりにくいです。1pxなので目立たないというのもありますが、そもそも視認できない状況がいくつかあります。

currentColor

例えば、outlineを表示する要素の前景色が、その要素の外側の領域の色と同じ場合、ユーザーはoutlineを視認できません。Geckoのoutline-colorのデフォルトがcurrentColor相当になっているためです。具体的には、「白背景のページ」に「灰色背景で白文字のリンク」がある場合を考えてみましょう。outlineは要素の外側に描画されるので、白背景の上に描画されます。outlineの色は要素の前景色、白です。つまり、白背景の上に白いoutlineが描画されるので視認できません(OS Xでは違うかもしれません)。

Firefoxで「白背景のページ」にある「灰色背景で白文字のリンク」にフォーカスを合わせた場合(a)。outlineは視認できない。スクリーンショットArs Technicaのもの。

一応、例もおいておきます。

例:outlineを表示する要素の前景色が、その要素の外側の領域の色と同じ場合

invert

だいぶ昔のGeckoはinvert(周囲の色を反転させる)を初期値としていましたが、cairoに移行する段階でinvertをやめ、currentColor相当になりました。当時のcairoではinvert相当のことができなかったためです。CSS Basic User Interface Module Level 3 (CSS3 UI)のinvertにも次のようにあります。

Conformant UAs may ignore the ‘invert’ value on platforms that do not support color inversion of the pixels on the screen. If the UA does not support the ‘invert’ value then the initial value of the ‘outline-color’ property is the ‘currentColor’ [CSS3COLOR] keyword.

さて、この前Compositing and Blending Level 1の勧告候補をつらつらと眺めていたら、differenceにつぎのように書かれていることに気が付きました。

Painting with white inverts the backdrop color; painting with black produces no change.

GeckoCanvas 2D Contextなどですでdifferenceをにサポートしています。ということは、今のGeckoではinvertを再実装できる余地があるということです。

ということで実装してみたのが以下です。

invertを実装したFirefoxで「白背景のページ」にある「灰色背景で白文字のリンク」にフォーカスを合わせた場合(b)。outlineは視認できる。スクリーンショットArs Technicaのもの。

Differenceの問題

ただdifferenceを使ったinvertの実装にも問題があります。それは50%灰色は反転しても50%灰色のままなので、視認できないという問題です。differenceは次のような処理を行っています。

画面上の色 = | 背景色 − 入力色 |

なので入力色(のRGB値)が背景色(のRGB値)の2倍の関係になっていると、画面上の色は背景色のままになります。

画面上の色 = | 背景色 − (2 × 背景色) | = | − 背景色 | = 背景色

differenceを使って色を反転させるには白(#FFFFFF)を入力色として使うので、背景が50%灰色(#808080)の場合には反転しても色が変わりません。この問題を解決するにはdifferenceをあきらめるしかありません。

あるいは、outline-style: autoとして、常に視認できるoutlineを実装することでしょうか。

関連情報

少しアクセシブルな、列や行を固定した表

大きな表では見出しになる列や行を固定して、残りの行や列をスクロールさせた方がわかりやすい場合があります。しかし、行や列を固定した表をアクセシブルに実装することが難しいことも事実です。

現状では、固定部分とスクロール可能部分をそれぞれ別のtable要素にするのが一般的だと思います。その場合、固定部分のセルとスクロール可能部分のセルの関係性は崩れてしまいます。td要素にはheaders属性がありますが、同じtable要素内のth要素しか参照できません(HTML5など)。そのため、複数の表をまたいで見出しセルとデータセルを関連づけることもできません。何より、複数の表を1つの表に見えるように、行の高さや列の幅をJavaScriptで調整していることが、関係性が崩れていることをあらわしています。

しかし、position:stickyを使えば、表を分割せずに行や列を固定できます。

position:stickyを使って列や行を固定した表の例

例えば、行を固定するには次のように書くことができるでしょう。

<div class="scrollable sticky-rows">
<table>
 <thead class="sticky row">
 <tr><th>Name<th>Description<th>Legacy code exception field value (if any)
 <tbody>
 <tr>
  <td>"IndexSizeError"
  <td>The index is not in the allowed range.
  <td><code>INDEX_SIZE_ERR (1)
  <!-- 略 -->
</table>
</div>

.scrollable {
    width: -moz-fit-content;
    width: -webkit-fit-content;
    width: fit-content;
}

.scrollable.sticky-rows {
    overflow-y: scroll;
    max-height: 100%;/* 幅は成り行き */
}

.scrollable .sticky {
    position: sticky;
}

.scrollable .sticky.row {
    top: 0;
}

列を固定するには次のように書けるでしょう。

<div class="scrollable sticky-cols">
<table>
 <thead>
 <tr><th class="sticky col">Name<th>Description<th>Legacy code exception field value (if any)
 <tbody>
 <tr>
  <td class="sticky col">"IndexSizeError"
  <td>The index is not in the allowed range.
  <td><code>INDEX_SIZE_ERR (1)
  <!-- 略 -->
</table>
</div>

.scrollable.sticky-cols {
    overflow-x: scroll;
    max-width: 100%;/* 高さは成り行き */
}

.scrollable.sticky-cols > table {
    width: -moz-max-content;
    width: -webkit-max-content;
    width: max-content;
}

.scrollable .sticky {
    position: sticky;
}

.scrollable .sticky.col {
    left: 0;
}

両方固定するなら、次のようになります。

<div class="scrollable sticky-rows sticky-cols">
<table>
 <thead class="sticky row">
 <tr><th class="sticky col">Name<th>Description<th>Legacy code exception field value (if any)
 <tbody>
 <tr>
  <td class="sticky col">"IndexSizeError"
  <td>The index is not in the allowed range.
  <td><code>INDEX_SIZE_ERR (1)
  <!-- 略 -->
</table>
</div>

.scrollable {
    width: -moz-fit-content;
    width: -webkit-fit-content;
    width: fit-content;
}

.scrollable.sticky-rows {
    overflow-y: scroll;
    max-height: 100%;
}

.scrollable.sticky-cols {
    overflow-x: scroll;
    max-width: 100%;
}

.scrollable.sticky-cols > table {
    width: -moz-max-content;
    width: -webkit-max-content;
    width: max-content;
}

.scrollable .sticky {
    position: sticky;
}

.scrollable .sticky.row {
    top: 0;
    z-index: 1;/* 列よりも行を優先して表示 */
}

.scrollable .sticky.col {
    left: 0;
}

この例は、Chrome Canary(33.0.1750.117)でposition:stickyを有効にすると、先頭行や先頭列を固定した表として動作します。Firefox Nightly(30.0a1 (2014-02-21))では動作しません。

overflowプロパティなど

この例ではスクロールできる領域をつくるためにdiv要素を追加しています。これは、displayがtableの要素にoverflowプロパティなどを指定しても効果がないためです。このことはCSS 2.1では明言されていませんでしたが、CSS 2.1に対するErrata(s.11.1.1b)で明言されています。scrllとautoに対して次の文言が追加されています。

When used on table boxes, this value has the same meaning as 'visible'.

来たるCSS 2.2にも反映されることでしょう。

表関連の要素に対するposition

また、この例はChromeでは動作し、Firefoxでは動作しません。

CSS Positioned Layout Module Level 3(2012年2月の草案)では、position:stickyの表関連に関する効果はposition:relativeと同じであるとされています。CSS Positioned Layout Module Level 3のposition:relativeでは次のように述べられています。

table-row-group, table-header-group, table-footer-group and table-row offset relative to its normal position within the table. If table-cells span multiple rows, only the cells originating in the relative positioned row is offset.

日本語にすると次のようになるでしょうか。

table-row-group、table-header-group、table-footer-groupおよびtable-rowは、表の範囲内で通常の位置から相対的に移動する。もしtable-cellが複数の行にわたっている場合、相対配置されている行に由来するセルだけが移動する。

しかし、CSS 2.1では表関連の要素に対するposition:relativeの効果は未定義とされています(CSS 2.1におけるpositionプロパティ)。

The effect of 'position:relative' on table-row-group, table-header-group, table-footer-group, table-row, table-column-group, table-column, table-cell, and table-caption elements is undefined.

今のところ、Firefoxではposition:relativeは表関連の要素には効果がありません。position:stickyも同様です。これはCSS 2.1を前提にしていれば問題とならない挙動ですが、CSS Positioned Layout Module Level 3への対応を進めてもらいたいものです。

Chromeではposition:relativeを指定したtable-cellはtopプロパティなどで位置を変更できます。しかし、table-row-groupやtable-rowにposition:relativeを指定してもtopプロパティなどで位置を変更できません。

一方、position:sitckyをtable-cellに指定するとtopプロパティなどで位置を変更できて、かつ、位置を固定できます。table-row-groupやtable-rowにposition:relativeを指定すると、topプロパティなどで位置を変更することはできませんが、位置を固定できます。

なお、CSS Positioned Layout Module Level 3のpositionプロパティはtable-column-groupやtable-columnに適用されないとあります。

まとめ

position:stcikyが広く実装されれば、表の行や列を固定するためにtable要素を分割せずに済むようになるでしょう。

関連情報

Internet Explorerはimg要素に対応してください

名前

ブラウザーはスクリーン・リーダーなどの支援技術向けに、ページの構造を提供しています。支援技術はブラウザーが提供するページの構造を読んで、ユーザーに情報を伝えています。このやりとりで使われるのが、アクセシビリティAPIで、やりとりされる情報は木構造であらわされるためアクセシビリティ・ツリーと呼ばれています(アクセシビリティAPIで表現される情報はWebページに限りません。ブラウザーのUIやその他のアプリケーションも、アクセシビリティAPIを通して支援技術と情報をやりとりしています)。

やりとりする情報の中には、オブジェクトの名前があります。WAI-ARIAから「アクセシブルな名前*1の定義から前半を拾ってくると次のようなものになります(日本語訳は筆者)。

アクセシブルな名前はユーザーインタフェース構成要素の名前である。各プラットフォームのアクセシビリティAPIは、アクセシブルな名前のプロパティを提供する。アクセシブルな名前の値は、表示されているプロパティ(例:ボタン上に表示されているテキスト)もしくは表示されていないプロパティ(例:アイコンを説明している代替テキスト)から得られるだろう。

正直わかりにくいですね。たぶん、こういうことが言いたいのだと思います。

アクセシビリティ・ツリーではオブジェクト(ボタンやリンク)に基本的に名前がついています。名前がないと、例えばキャンセルボタンとOKボタンが並んでいるときに区別がつきません。また、名前は、画面に表示されているテキストや代替テキストから良しなに計算されます。

Internet Explorerの問題

ですが、Internet Explorerはあまり良しなに名前を計算しません。例として、Making accessible icon buttons | NCZOnlineが取り上げている、button要素の子供にimg要素を置いた場合を見てみましょう。この場合、img要素のalt属性に適切な値を設定していても、Internet Explorerではボタンの名前は空のままです。

<button><img alt="画像"></button>

Narratorを使っても「ボタン」としか読み上げません。何ボタンでしょうか…。しかし、画像ではなくテキストにすると、ボタンの名前は指定されたテキストとなり、仮にテキストが「テキスト」の場合、Narratorは「テキスト ボタン」と読み上げます。

Inspect.exeを使ってWindows 8.1Internet Explorer 11(デスクトップモード)が提供しているアクセシビリティ・ツリーを調査すると、Nameプロパティ(名前)が空文字列になっていました。しかしbutton要素の中身を画像ではなくテキストにすると、Nameプロパティにはテキストが入っていました。

Inspect.exeでIE11のアクセシビリティ・ツリーを調査。<label><img alt="画像"><input></label>に対応する部分を調査すると、input要素に対応するオブジェクトのNameが空文字列("")になっている。

せっかくなので、画像とテキストの実例をおいてみます。

同様のことが、ラベルでも起こります。

<label><input type="radio"><img alt="画像"></label>
<label><img alt="画像"><input></label>

これもラジオボタンや入力欄の名前は空のままです。

もちろん、リンクでも名前は空です。

<a href="#"><img alt="画像"></a>

スクリーン・リーダーの中には、不足している情報を自分で補ってユーザーに伝えているものもあるようですが、この情報は基本的にブラウザーが提供すべきだと思います。IE11もテキストの場合には名前を提供しているのですから、画像の場合にも適切に名前を提供すべきでしょう。Internet Explorerも早くimg要素に対応してほしいものです。

なお、IE11とNarratorを組み合わせた場合の読み上げ結果は次の通りでした。

IE11とNarratorを組み合わせた場合の読み上げ結果(Windows 8.1
読み上げ対象 読み上げ結果
<button><img alt="画像"></button>ボタン
<button>テキスト</button>テキスト ボタン
<label><input type="radio"><img alt="画像"></label>のinput要素オプション ボタン
<label><input type="radio">テキスト</label>のinput要素テキスト オプション ボタン
<label><img alt="画像"><input></label>のinput要素編集可能なテキスト
<label>テキスト<input></label>のinput要素テキスト 編集可能なテキスト
<a href="#"><img alt="画像"></a>リンク
<a href="#">テキスト</a>テキスト リンク

参考

*1:アクセシブルな名前は「アクセシビリティ・ツリーのオブジェクトの名前」程度の意味だと思っています

Firefox Nightlyにおける分数用字形

しばらく前に、Firefox Nightly(Windows)がハングルのシェイピング*1harfbuzzで行うようになりました。それにともなって、Firefoxが使っているharfbuzzが更新されています(964240 – update harfbuzz to pick up Hangul shaping improvements and other fixes)。

その際、ハングルのシェイピングとは関係なくついてきた更新の中に、自動的に分数形式で表示する機能の追加(Bug 72698 - Automatically support frac / numr / dnom · 3aeee51 · behdad/harfbuzz · GitHub)があります。これは、分数用スラッシュ「⁄」(U+2044 FRACTION SLASH)がテキストに出現した場合、前後に数字*2があるか探し、あれば分数用の字形を選択するというものです。例えば

3&frasl;4

は、スラッシュの左上に3が小さく表示され、右下に4が小さく表示される、といったことが自動的に行われます。もっとも、どういう字形になるかはフォント次第でしょうし、右とか左とか書きましたが、RTLの場合は左右が反転しているのかもしれません。また、harfbuzzは通常のスラッシュ「/」(U+002F SOLIDUS)では分数用字形を適用しません。

もちろん、対応していないフォントでは効果はありませんが、Windowsに最初からインストールされているフォントでは、少なくともCambria、Candara、Consolas、Constantia、Corbel、Gabriolaは分数用の字形を持っているそうです(Bug 72698 – Automatically support frac / numr / dnom)。游ゴシックと游明朝も持っていましたし、Fira Sansも少なくともOpenType版は分数用の字形を持っていました。

Firefox Nightlyにおける分数用字形の自動選択(Fira Sans)。
上下二段の上は「Platform 9 3&frasl;4」で、分数用字形で描画されている(スラッシュの左上に3が小さく表示され、右下に4が小さく表示)。下は「Platform 9 3&x2f;4」で、通常の字形で描画されている。(f:takenspc:20140210201804)。

この機能は明示的に指定することもできます。[CSS Fonts Level 3](http://www.w3.org/TR/css-fonts-3/では、次のように書くと分数用字形を選択できます。

.frac {
    /* http://www.w3.org/TR/css-fonts-3/#diagonal-fractions */
    font-variant-numeric: diagonal-fractions;
}

<span class="frac">3&frasl;4</span>
<span class="frac">3/4</span>

低レベル構文では次のようになります。

.frac {
    /* https://developer.mozilla.org/docs/Web/CSS/font-feature-settings */
    -moz-font-feature-settings: 'frac';
    -webkit-font-feature-settings: 'frac';

    /* http://msdn.microsoft.com/library/windows/apps/hh868498.aspx */
    -ms-font-feature-settings: 'frac';

    /* http://www.w3.org/TR/css-fonts-3/#font-feature-settings-prop */
    font-feature-settings: 'frac';
}

<span class="frac">3&frasl;4</span>
<span class="frac">3/4</span>

参考

*1:文字データからレンダリングにつかうグリフを計算する処理

*2:Bug 72698 - Automatically support frac / numr / dnom · 3aeee51 · behdad/harfbuzz · GitHubによると数字とは0から9だけではなく、Unicodeで十進数字(Number, Deciman_Digit)に分類されるものとのこと

暗黙のARIAセマンティック

暗黙のARIAセマンティックと強いネイティヴセマンティックについて」で、暗黙のARIAセマンティックをHTMLに指定するべきかどうか、に絡んだ話がされていましたので、この記事では私の考えを書きます。

私の考え

  • 本来的には、HTMLのセマンティックは単独で伝わるべきもので、暗黙のARIAのセマンティックをHTMLに記述する必要はない
  • 現実には、HTMLのセマンティックを支援技術に提供しないブラウザーが存在する
    • それらのブラウザーの中にはWAI-ARIAをサポートしているものが存在する
  • WAI-ARIAをサポートしHTML5をサポートしていないブラウザーの救済策としてWAI-ARIAを重複指定することが行われている
  • Using ARIA in HTMLの推奨表は現時点での情報であり、将来的には変わるだろう

はじめに

WAI-ARIAのセマンティックは主に支援技術(スクリーン・リーダーなど)で利用されます。また、モダンなブラウザーと支援技術は主にアクセシビリティAPIを使って情報のやりとりや操作を行っています。同様に、HTMLの要素や属性がもつセマンティックも(アクセシビリティAPIを通して)支援技術に提供されています(セマンティックは支援技術のためだけのものではありませんが、この記事では支援技術との関係に絞って議論します)。

理想

HTMLのセマンティックを支援技術に提供することは、理想的・本来的にはブラウザーが行うべきことです。main要素は

<main>…</main>

と書くだけで、セマンティックが支援技術に伝わりランドマークとして認識される。これがあるべき姿でしょう(もちろんmain要素に限った話ではなく、HTMLの要素や属性が持っているセマンティックは、HTMLとして記述するだけで支援技術に伝わるべきでしょう)。

現実

しかし、現実にはHTML5(の一部)のセマンティックを支援技術に提供していないブラウザーがいます。そして、それらのブラウザーを救うすべが全くないかというと、そうではありません。

それらのブラウザーの中にはWAI-ARIAに対応したものもあり、WAI-ARIAで指定したセマンティックは支援技術に提供しているからです。そのため、WAI-ARIAに対応したセマティックをもつHTMLの要素については、WAI-ARIAで重複してセマンティックを指定することで、中途半端なブラウザーたちを救済できる余地がうまれます。

例えば、Internet Explorer 11やWindows版Google Chrome<main>...</main>という記述に対して、セマンティック(ランドマーク)をアクセシビリティAPI経由で提供しません。一方で<main role="main">…</main><div role="main">…</div>であれば、ランドマークとしてのセマンティックを提供します。nav要素やaside要素、article要素に対しても同様です。

なお、ブラウザーが支援技術にどのような情報を提供すべきかは、HTML to Platform Accessibility APIs Implementation Guideにまとめられつつあります。また、ブラウザーごとの対応情報は少し古いですがHTML5 accessibilityにまとまっています。

理想と現実のはざまで

この理想と現実のはざまで、次のような、過渡期にありがちな問いが生まれます。

「中途半端なブラウザーたちを見捨てるべきか、それともコンテンツ側がWAI-ARIAの属性を重複指定して救うべきか。」

私には救うべき派が多いように感じられます。例えば、A Rock n Roll Guide to HTML5 & ARIA (2013)で紹介されているパターンの多くも救済策としてのWAI-ARIAの指定(重複指定)です。また、HTML5のmain要素でも、次のように、コンテンツ側の対応を要請しています。

Authors are advised to use ARIA role="main" attribute on the main element until user agents implement the required role mapping.

さて、実際に救おうとすると、さらなる問いが生まれます。

「コンテンツ側はどれだけ重複指定すれば良いのか。」

例えば、a[href]の暗黙のARIAセマンティックはlinkロールなので、a要素でリンクを作るたびにrole="link"と書かなくてはならないか、というと、現実はそこまでひどくありません。中途半端なブラウザーたちもHTML4/XHTML1までに存在した要素については、おおむねアクセシビリティAPIにセマンティックを提供しています。とすると、中途半端なブラウザーたち(のメジャーなもの)を救うためにコンテンツ側はどれだけ重複指定すれば良いのでしょうか。

この問いに答えようとしているのがUsing ARIA in HTML(のいくつかの部分)というのが私の認識です。Using ARIA in HTMLには「Notes on ARIA use in HTML」(HTMLでARIAを使うための注意点)があります。その「First rule of ARIA use」(第1の原則)はHTMLに存在している機能を実現するにはHTMLの要素を使え(大意)、ですが、この原則を守ることができない場合として次があげられています。

最後の「実装されていないか、実装されていてもアクセシビリティ・サポーテッドでない場合」が、中途半端なブラウザーの救済策にあたります。

そのため、Using ARIA in HTMLの推奨表は「ある時点でのベストプラクティス」というのが、私の認識です。現時点ではベストプラクティスかもしれませんが、今の内容が将来にわたってベストプラクティスであり続けるとは認識していません。ブラウザーのサポートが進めばWAI-ARIAの記述が推奨される要素・状況は減るでしょう。

まとめ

私の考えは次の通りです。

  • 本来的には、HTMLのセマンティックは単独で伝わるべきもので、暗黙のARIAのセマンティックをHTMLに記述する必要はない
  • 現実には、HTMLのセマンティックを支援技術に提供しないブラウザーが存在する
    • それらのブラウザーの中にはWAI-ARIAをサポートしているものが存在する
  • WAI-ARIAをサポートしHTML5をサポートしていないブラウザーの救済策としてWAI-ARIAを重複指定することが行われている
  • Using ARIA in HTMLの推奨表は現時点での情報であり、将来的には変わるだろう

おわりに

私はHTMLにWAI-ARIAの属性を重複して書くのは好きではありません。ブラウザーがやるべきことだと思うからです。が、過渡期的な状況においては重複指定したほうが良いということも認識しています。

来年はブラウザーの対応がさらに進むことを望みます。そして、HTML5のセマンティックを支援技術に提供するブラウザーへの移行が進み、重複指定時代の終わりの始まりが来ることを期待しています。

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の参考情報を私の想像で補って記載しています。ブラウザーなどがこの通りに実装する保証、といったものはありません