タグ: web

  • スクリーンリーダーはどうやってライブリージョンを読み上げるのか?

    いま話題の ChatGPT に質問したらこういう答えが返ってきました。

    スクリーンリーダーは、画面上に表示されている文字列や画像を解析し、それらを音声として読み上げることができます。また、ライブリージョンと呼ばれる特定のタグや属性を持つ要素を検索し、優先的に読み上げることも可能です。これにより、スクリーンリーダーは目的のコンテンツを効率的かつ正確に読み上げることができます。

    なるほど。。今年はアドベントカレンダーの記事を Qiita に書きました。私が書いてみた解説をそちらでどうぞ。

  • ふつう=ユニバーサルでアクセシブル

    この記事は ふつうの広島 Advent Calendar 2015 の8日目の記事です。
    昨日はkamera25さんの記事でした。
    冒頭から脱線しますが「普通の人にはない技術の持ち主」といえば「職人」ですね。
    今日から「職人」がテーマのイベントが広島で開催されますよ。
    広島職人博覧会
    12月8日(火曜)から14日(月曜)まで
    旧日本銀行広島支店(広島市中区袋町)

    ふつうとはなんだろう

    普通じゃない人にとって「普通に生きろ」という言葉は「苦痛」の一歩手前だったりするかも知れません。
    「普通の人」=「健常者」「定型発達の人」でしょうか?
    「普通の人」=「リモートワークじゃない人」でしょうか?
    もっといろんな「普通」があっていいような気がするので、グーグル先生に訊いてみました。
    普=「あまねく」「広く」
    通=「通用する」
    「あまねく」は「ユニバーサル」、「通用」は「アクセス」じゃないですか。
    「ふつう」=「ユニバーサル」で「アクセシブル」ということですね!!

    イベントの「ふつう」

    「ふつうの広島」=「どんな人でも学んだり働いたり活動したりできる広島」にしましょう。
    そういうつもりで2015年に私が取り組んだのが Python のイベント、PyCon mini Hiroshima 2015 でした。
    開催直前の告知と終了後の報告は PyCon JP のブログに書いたので、下記をどうぞ。
    PyCon mini Hiroshima 2015 を開催します!
    PyCon mini Hiroshima 2015 を開催しました!
    報告の中でも書いたことですが、カジュアルな勉強会は行動力のある人たちに任せて、私は「アクセシビリティを切り口に質を保障できるイベント」を、今後も続けていくためにお手伝いできたらいいなあと思っています。

    ソフトウェアの「ふつう」

    こないだ Web アクセシビリティのアドベントカレンダーに寄稿した NVDA 日本語版の現状の記事 ですが、途中から支離滅裂で勢いだけの駄文になってしまったので、もうすこし具体的に「アクセシビリティから考える本質とか品質とか」について書いてみます。
    NVDA の UI ウィジェットに wxPython が使われていることは PyCon mini でお話しましたが、おそらく NVDA の開発者たちはこのツールキット選びをかなり慎重に行ったはずです。
    wxPython のさらに土台になっている wxWidgets のレベルで、すでに Microsoft のアクセシビリティ API に対応する作り込みが行われていることが、ちゃんとドキュメントになっています
    マルチプラットフォーム開発のための UI ツールキットはいろいろありますが、それぞれのプラットフォームのネイティブなアクセシビリティ API に対応した実装が施されているものはなかなかありません。
    画面を Windows っぽく描画したり Mac っぽく描画したりすることだけできても、それが「ボタン」なのか「テキストフィールド」なのか、といった情報を OS のアクセシビリティ API に正しく渡すことができないフレームワークは山ほどあるのです。。
    モバイル開発についてはどうでしょうか。
    マルチプラットフォームといえば HTML で UI を作る「いわゆる PhoneGap」アプリを真っ先に思いつきます。
    あまりよいユーザー体験を実現できない、ということで、ここ数年は敬遠されてきたような印象ですが、最近あらためて調べてみたら、Apache Cordova と Angular.js という組み合わせの新しいツールがいくつか登場していました。
    ここでは Ionic Framework について最近私が調べたことをちょっと紹介しておきます。
    この Ionic フレームワークは単なるライブラリではなくコマンドラインインタフェースのツールが用意されています。
    ionic start コマンドで生成されるひな形の「タブ UI」アプリを Xcode でビルドして iPad にデプロイしてみたら、かなり VoiceOver でちゃんと使える、ということに気づきました。
    フォーカスの移動順序にやや不自然なところはあったもののの、多くの要素が理解可能、アクセス可能でした。
    Web技術はちゃんと「ユニバーサルにアクセシブル」に向かって進歩してるんですね。。
    残念ながら完全に VoiceOver に無視されてしまったのは ion-toggle でした。
    調べてみたらこのトグルスイッチの記述方法が他にも見つかったので、いわゆる WAI-ARIA の勉強がてら書き直してみたら、VoiceOver でちゃんと操作できて状態を読み上げられるトグルスイッチになりました。
    修正前:

     <ion-view view-title="Account">
       <ion-content>
         <ion-list>
    -    <ion-toggle ng-model="settings.enableFriends">
    -        Enable Friends
    -    </ion-toggle>
         </ion-list>
       </ion-content>
     </ion-view>
    

     
    修正後:

     <ion-view view-title="Account">
       <ion-content>
         <ion-list>
    +    <li class="item item-toggle">
    +      <span role="label" id="enableFriendsLabel">Enable Friends</span>
    +      <label class="toggle">
    +        <input type="checkbox" ng-model="settings.enableFriends" />
    +        <div class="track">
    +          <div tabindex="0" class="handle" aria-labelledby="enableFriendsLabel" role="checkbox" aria-checked="{{ settings.enableFriends }}"></div>
    +        </div>
    +      </label>
    +    </li>
        </ion-list>
       </ion-content>
     </ion-view>
    

     
    role とか aria-checked とかこうやって使うんですね。勉強になりました。
    これ Web 標準を使ってアクセシビリティを確保したので、たぶんプラットフォームごとの実装さえちゃんとしていれば「普(ユニバーサル)」に「通(アクセシブル)」なモバイルアプリになるはずですね。。
    Ionic のテンプレートは Visual Studio 2015 にも入ってます。
    Universal Windows Platform でもいけそうです。
    アクセシビリティを「品質」と捉えると、技術を見極めたり、新しいことを勉強したりできますね。
    「ふつうの広島」を目指して、開発者向けのアクセシビリティ勉強会をやったらいいですね、と提案しつつ、この記事を締めくくりたいと思います。
    以下、参考書:
    コーディング Web アクセシビリティ

    わかりやすい「WAI-ARIA 1.0」仕様解説書 Kindle版

  • AngularJS と NVDA

    <htmlday> 2013 ということで AngularJS で遊ぼう という勉強会に参加しました。
    AngularJS は Web アプリケーション開発のフレームワークということで、簡潔に書けて保守性が高いのが特長、らしいです。
    しかし、生産性の高い開発手法という話を聞くと「それってアクセシビリティは大丈夫なの?」とつい思ってしまいます。
    そこで、angularjs.org のトップページのビデオで紹介されていた TODO アプリケーションのサンプルをスクリーンリーダー NVDA 日本語版 と Firefox の組み合わせで確認しました。
    書かれたマークアップは内部で標準的なHTMLに変換されているので、フォーカス移動などはちゃんとできるのですが、チェックボックスにフォーカスしたときに、その右側にある項目名を読み上げないので、何の操作をするチェックボックスなのかわかりません。
    ふと aria-labelledby でやればいいんじゃない? と思ったので、下記のように直したところ、うまくチェックボックスの項目名を読み上げるようになりました。
    最初は id をつけるために todos モデルに通し番号のプロパティを追加したのですが、後で $index という特殊プロパティがあることを教わったので、todo.js 側のコントローラーとモデルは書き換えないで実装できました。アクセシビリティのための処理が View だけで完結するのは素晴らしい。AngularJS なかなかよさそうなのでもうちょっと勉強してみたいと思います。
    <htmlday>とは、日本全国でWeb制作者/開発者向けのイベントを同日に開催することで、日本のWebを一層盛り上げようという「お祭り」だったそうで、私も貢献できればと思いブログを書きました。
    ちなみに広島市中心部は「とうかさん」というお祭りの季節です。。
    以下のソースでは bootstrap.cssUnderscore.js を使っています:

    <!doctype html>
    <html ng-app>
      <head>
        <script src="angular.js"></script>
        <script src="underscore.js"></script>
        <script src="todo.js"></script>
        <link rel="stylesheet" href="bootstrap/css/bootstrap.css">
        <link rel="stylesheet" href="todo.css">
      </head>
      <body>
        <h2>Todo</h2>
        <div ng-controller="TodoCtrl">
          <span>{{remaining()}} of {{todos.length}} remaining</span>
          [ <a href="" ng-click="archive()">archive</a> ]
          <ul>
            <li ng-repeat="todo in todos">
              <input aria-labelledby="label{{$index}}" type="checkbox" ng-model="todo.done">
              <span id="label{{$index}}">{{todo.text}}</span>
            </li>
          </ul>
          <form>
            <input type="text" ng-model="todoText"  size="30"
                   placeholder="add new todo here">
            <button ng-click="addTodo()"><i></i>Add</button>
          </form>
          <button ng-click="clearCompleted()"><i></i>Clear Completed</button>
        </div>
      </body>
    </html>
    // todo.js
    function TodoCtrl($scope) {
        $scope.todos = [
    	{text:'learn angular', done:true},
    	{text:'build an angular app', done:false}];
        $scope.addTodo = function() {
    	$scope.todos.push({text:$scope.todoText, done:false});
    	$scope.todoText = '';
        };
        $scope.remaining = function() {
    	var count = 0;
    	angular.forEach($scope.todos, function(todo) {
    	    count += todo.done ? 0 : 1;
    	});
    	return count;
        };
        $scope.archive = function() {
    	var oldTodos = $scope.todos;
    	$scope.todos = [];
    	angular.forEach(oldTodos, function(todo) {
    	    if (!todo.done) $scope.todos.push(todo);
    	});
        };
        $scope.clearCompleted = function() {
    	$scope.todos = _.filter($scope.todos, function(todo) {
    	    return !todo.done;
    	})
        };
    }