SPA (Single Page Application) のアクセシビリティ (Part2) : 文書構造、ナビゲーション、キーボードのアクセシビリティ

Single page applications, Angular.js and accessibility の翻訳 Part2 です。

SPA (Single Page Application) のアクセシビリティ (Part1) ではイントロダクションを翻訳しました。今回から実装の説明にはいります。

文書構造、ナビゲーション、キーボードのアクセシビリティ

文書構造

アプリケーションのアクセシビリティを考慮する際には、文書構造とキーボードナビゲーションから始めます。なぜなら「たどり着くことができなければ使うことはできない」という単純な事実があるからです。
SPAのナビゲーションにおいて第1のチャレンジとなるのが、正しい見出し(heading)レベルでマークアップしたセマンティックな文書構造の実現です。

支援技術(スクリーンリーダーなど)の利用者の多くは、ドキュメントやアプリケーションの中の重要なパート間の移動を文書構造に頼っています。
たとえば、見出しと見出しの間を行き来するような操作が含まれます。

文書構造化の利点

  • ソリッドで構造化されたウェブページにより、単にタブキーと矢印キーを使うよりすぐれたキーボードアクセスが可能になります。
  • 正しい見出しレベルでマークアップしたドキュメントは同時に、アプリケーションのビュー間を移動する際にフォーカスの対象となるすぐれたアンカーポイントも提供します。(これについては後で触れます)。

コード : 文書構造

以下がサンプルアプリケーションの文書構造です。

<!DOCTYPE HTML>
<html ng-app="a11yTicketApp" lang="en">
  <head>
    <meta charset="UTF-8" />
    <title>Accessibility Ticket Tracker</title>
  </head>
  <body>
    <div class="container">
      <header class="header">
        <nav>
          ...
        </nav>
      </header>
      <main class="main" id="main">
        ...
        <h1>Accessibility Ticket Tracker</h1>
        ...
        <h2>Tickets</h2>
        ...
      </main>
      <footer class="footer">
        <p>Copyright</p>
      </footer>
    </div>
  </body>
</html>

ページとビューのタイトル

文書構造においてナビゲーションのために重要だが忘れがちなのがページタイトルです。「シングルページアプリケーションであればタイトルも単一」と考えるのは自然な発想です。しかしSPAではビューからビューへと状態が変わります。これらのビューの変更を、Webのページ遷移に置き換えて考える必要があります。
ユーザーが今どこにいるのかを常にわかるようにするために、Webページと同様にビューが切り替わるたびにページタイトルも変わるべきです。
Angularのすばらしいルーティングの仕組みを使えば、ページタイトルは簡単にアップデートできます。見てみましょう。

コード : Angularのルーティングとページタイトル

Webブラウザーのタブに Accessible Ticket Tracker のタイトルを表示

Angularのビューにページタイトルを設定する第1のステップはデフォルトの index.html の titleタグに root title プロパティをバインドすることです。
htmlドキュメントのheadにバインドするには、htmlタグでアプリケーションを定義する必要があります。
これによりAngularの$routeProviderで使われる$rootScope変数にアクセスできるようになります。
以下が例です。

<html  class="no-js" ng-app="a11yTicketApp" lang="en">
<title ng-bind="title">Accessibility Ticket Tracker</title>

上で定義したアプリケーションのルートにタイトル属性を設定します。

.config(function ($routeProvider) {
    $routeProvider
        .when('/main', {
            templateUrl: 'views/main.html',
            controller: 'MainCtrl',
            title: 'Accessibility Ticket Tracker'
        })
        .when('/edit/:id', {
            templateUrl: 'views/edit.html',
            controller: 'EditCtrl',
            title: 'Edit Ticket'
        })
        ...  
        .otherwise({
            redirectTo: '/main'
        });
})

$on('$routeChangeSuccess') を使ってルート変更のイベントを登録し、変更があれば $rootScope の title 属性をアップデートします。

myApp.run(['$location', '$rootScope', function($location, $rootScope) {
    $rootScope.$on('$routeChangeSuccess', function (event, current, previous) {
        // test for current route
        if(current.$$route) {
            // Set current page title
            $rootScope.title = current.$$route.title;
        }
    });
}]);

キーボードナビゲーション

文書構造に続いて、ng-click をすべてのインターフェースに紐付ける作業に進みます。このディレクティブはアプリケーションの構築において大変便利なしくみですが、あらゆるエレメントに対して ng-click を付加できるという点で、アクセシビリティの観点では危険でもあります。
残念ながらこれらのエレメントはブラウザーのネイティブなフォーカスを受け取ることができず、これはキーボード利用者に対する障壁になってしまいます。
ここは、Web標準に準拠したベストプラクティスのインターフェースを作製するには避けて通れない部分です。
アクションがナビゲーション要素(つまり送信フォーム)と関連付けられたところではボタンを、アプリケーション内を動き回るにはリンクを使いましょう。
私たちの Angularアプリからいくつか例を見てみましょう。

コード : ボタンとリンク

はじめに、最近よく見かけるアプローチです。ネイティブや要素を使うべきところで、クリッカブルな要素として divを使用しています。

<div ng-click="doSomething(id);" class="im-a-button">I'm  not a button</div>

こちらが私たちのアプリケーションからの例です。クリッカブルなインターフェース要素に対して buttonを使用します。ネイティブな要素に固執するのは、アクセシビリティに関する利点のすべてを享受することができるからです。

<input type="button" value="Cancel" class="btn btn-primary" ng-click="go('main')">

次にリンクをどのようにセットアップしたかを見ていきます。このリンクをクリックするとチケットの詳細が表われます。このとき ng-clickが href 属性を上書きしています。しかしながら、ここではまだ href 属性が保持されているのでそれぞれのリンクに固有の行き先を付与することができ、これがスクリーンリーダーに登録されます。またここでは、リンクの状態をスクリーンリーダーに伝えるために、リーダーがアクセスできるが画面に表示されない、視覚的に隠されたテキスト(visually hidden text)を使用しています。Angularの式(expressions)を使えばテキストの有効化/無効化を大変簡単に設定することができます。

<a href="#expandDetails-{{ ticket.$id }}" ng-click="toggleTicketDetails($event,$index)">
  {{ ticket.summary  }}
  <span class="visuallyhidden">{{ index==$index ? " - click to hide details" : " - click to show details"}}</span>
</a>

CSS

.visuallyhidden {
  border: 0;
  clip: rect(0 0 0 0);
  height: 1px;
  width: 1px;
  margin: -1px;
  padding: 0;
  overflow: hidden;
  position: absolute !important;
}

注記: これは視覚的に隠された(visually hidden)テキストで、隠された(hidden)テキストではありません。このためCSSの display:none は使いません。


※順次更新の予定です

コメントは受け付けていません。