もう一度一緒に過ごす
はじめに
30 年近くにわたり、デスクトップ コンピューティング エクスペリエンスは、キーボードとマウスまたはトラックパッドを中心に展開されてきました。しかし、この 10 年間でスマートフォンとタブレットは、タッチという新しいインタラクション パラダイムをもたらしました。タッチ対応の Windows 8 マシンの登場、そしてタッチ対応の Chromebook Pixel のリリースにより、タッチはパソコンの標準的な操作方法になりつつあります。最大の課題の 1 つは、タッチデバイスとマウスデバイスだけでなく、ユーザーが両方の入力方法(場合によっては同時に使用)を使用するデバイスでも機能するエクスペリエンスを構築することです。
この記事では、タップ機能がブラウザに組み込まれている仕組み、この新しいインターフェース メカニズムを既存のアプリに統合する方法、タップとマウス入力を連携させる方法について説明します。
ウェブ プラットフォームにおける接触の現状
iPhone は、ウェブブラウザに専用のタップ API が組み込まれた最初の一般的なプラットフォームでした。他の複数のブラウザ ベンダーも、iOS の実装と互換性のある同様の API インターフェースを作成しています。これは、「タッチイベント バージョン 1」の仕様で説明されています。タッチイベントは、パソコンの Chrome と Firefox、iOS の Safari、Android の Chrome と Android ブラウザ、BlackBerry ブラウザなどのモバイル ブラウザでサポートされています。
同僚の Boris Smus が、タッチイベントに関する HTML5Rocks の優れたチュートリアルを作成しました。これまでタッチイベントをご覧になったことがない方には、この Codelab から始めることをおすすめします。実際に、タッチイベントをこれまでに扱ったことがない場合は、先に進む前にこの記事をお読みください。よろしければ、お待ちしております。
準備ができていることを確認したら、タップイベントの基本を理解したところで、タップ対応のインタラクションを記述する際の課題について説明します。タップ操作はマウス(およびマウスをエミュレートするトラックパッドとトラックボール)のイベントとは大きく異なる場合があります。タッチ インターフェースは通常、マウスをエミュレートしようとしますが、そのエミュレーションは完全ではありません。両方の操作スタイルを扱う必要があり、各インターフェースを個別にサポートしなければならない場合もあります。
最も重要な点: ユーザーがタップとマウスを使用している可能性がある
多くのデベロッパーは、環境がタップ イベントをサポートしているかどうかを静的に検出し、タップ イベント(マウス イベントではない)のみをサポートする必要があるという前提でサイトを構築しています。ただ、この仮定は誤りです。タッチイベントが存在しているからといって、ユーザーが主にそのタッチ入力デバイスを使用しているわけではありません。Chromebook Pixel や一部の Windows 8 ノートパソコンなどのデバイスでは、マウスとタップ入力の両方の方法がサポートされています。今後、さらに多くのデバイスでサポートされる予定です。このようなデバイスでは、ユーザーがマウスとタッチスクリーンの両方を使用してアプリを操作するのはごく自然なことです。そのため、「タッチをサポート」は「マウスのサポートは不要」とは異なります。「2 つの異なるインタラクション スタイルを記述して切り替える必要がある」という問題ではなく、両方のインタラクションが連携して機能する方法と、独立して機能する方法について考える必要があります。Chromebook Pixel では、タッチパッドを頻繁に使用しますが、画面をタップすることもあります。同じアプリやページで、そのとき最も自然に感じる方法で操作しています。一方、タッチスクリーン ノートパソコンのユーザーの中には、タッチスクリーンを使用しない、またはほとんど使用しない人もいます。そのため、タッチ入力があっても、マウス操作を無効にしたり、妨げたりしない必要があります。
残念ながら、ユーザーのブラウザ環境がタッチ入力をサポートしているかどうかを判断するのは難しい場合があります。理想的には、タッチイベントのサポートが常に示されるように、デスクトップ マシン上のブラウザでタッチスクリーン ディスプレイをいつでも接続できるようにする必要があります(KVM を介して接続されたタッチスクリーンが利用可能になった場合など)。以上の理由から、アプリケーションでタップとマウスを切り替えようとせず、両方をサポートするようにしてください。
マウスとタッチの同時サポート
1 - クリックとタップ - 自然な順序
最初の問題は、タッチ インターフェースは通常、マウスクリックをエミュレートしようとすることです。これは、タッチ インターフェースは、これまでマウスイベントのみを操作していたアプリで動作する必要があるためです。これはショートカットとして使用できます。ユーザーがマウスでクリックしたか、画面上で指をタップしたかにかかわらず、「クリック」イベントは引き続き発生します。ただし、このショートカットにはいくつかの問題があります。
まず、高度なタップ操作を設計する際には注意が必要です。ユーザーがマウスを使用すると、クリック イベントを介して応答しますが、ユーザーが画面にタップすると、タップ イベントとクリック イベントの両方が発生します。1 回のクリックのイベントの順序は次のようになります。
- touchstart
- touchmove
- touchend
- マウスオーバー
- mousemove
- mousedown
- mouseup
- クリック
つまり、touchstart などのタッチイベントを処理する場合は、対応する mousedown イベントや click イベントも処理しないようにする必要があります。タッチイベントをキャンセルできる場合(イベント ハンドラ内で preventDefault() を呼び出す場合)、タッチに対してマウスイベントは生成されません。タッチハンドラの最も重要なルールの 1 つは、次のとおりです。
ただし、これにより、他のデフォルトのブラウザ動作(スクロールなど)も妨げられます。通常、タッチイベントはハンドラで完全に処理し、デフォルトのアクションを無効にします。通常は、すべてのタップイベントを処理してキャンセルするか、そのイベントのハンドラを作成しないかのいずれかになります。
2 つ目の理由は、モバイル デバイスのウェブページでユーザーが要素をタップしたときに、モバイル操作用に設計されていないページでは、タッチ開始イベントとマウスイベント(mousedown)の処理の間に 300 ミリ秒以上の遅延が生じることです。Chrome を使用して行うことができます。Chrome デベロッパー ツールで [タッチイベントをエミュレート] をオンにすると、タッチ非対応のシステムでタッチ インターフェースをテストできます。
この遅延は、ユーザーが別の操作(特にダブルタップによるズーム)を行っているかどうかをブラウザが判断するための時間です。指の操作に即座に応答する必要がある場合は、これは問題になる可能性があります。この遅延が自動的に発生するシナリオを制限するための作業が進行中です。
この遅延を回避する最も簡単な方法は、ページでズームが必要ないことをモバイル ブラウザに「伝える」ことです。これは、固定ビューポートを使用して行うことができます。たとえば、ページに次のように挿入します。
<meta name="viewport" content="width=device-width,user-scalable=no">
もちろん、これは必ずしも適切とは限りません。ピンチ操作によるズームはユーザー補助の観点から必要となる場合があるため、この方法は必要に応じて慎重に使用してください(ユーザーによるスケーリングを無効にする場合は、アプリ内のテキストの読みやすさを高めるための他の方法を用意することをおすすめします)。また、タッチをサポートするデスクトップ クラスのデバイス上の Chrome や、ページにスケーリング不可能なビューポートがあるモバイル プラットフォーム上の他のブラウザでは、この遅延は適用されません。
#2: タッチでマウス移動イベントがトリガーされない
ここで注意すべき点は、タッチ インターフェースでのマウスイベントのエミュレーションは、通常、mousemove イベントのエミュレーションには拡張されないことです。そのため、mousemove イベントを使用する美しいマウス駆動型コントロールを構築した場合、touchmove ハンドラも明示的に追加しない限り、タッチデバイスでは動作しない可能性があります。
通常、ブラウザは HTML コントロールのタップ操作に適した操作を自動的に実装します。たとえば、HTML5 の範囲コントロールは、タップ操作を使用すると機能します。ただし、独自のコントロールを実装している場合、クリック&ドラッグ型の操作では機能しない可能性があります。実際、よく使用されるライブラリ(jQueryUI など)では、このようなタッチ操作がネイティブにサポートされていません(ただし、jQueryUI には、この問題に対するモンキーパッチによる修正がいくつかあります)。これは、Web Audio Playground アプリケーションをタップ操作に対応するようにアップグレードしたときに最初に直面した問題の 1 つです。スライダーは jQueryUI ベースであるため、クリック&ドラッグ操作では機能しませんでした。HTML5 の範囲コントロールに切り替えたところ、問題なく動作しました。もちろん、単に touchmove ハンドラを追加してスライダーを更新することもできますが、これには 1 つの問題があります。
#3: Touchmove と MouseMove は同じではない
私が何人か経験した落とし穴は、touchmove ハンドラと mousemove ハンドラーで同じコードパスを呼び出すことです。これらのイベントの動作は非常に似ていますが、微妙に異なります。特に、タップ イベントは常にタップが開始された要素をターゲットにしますが、マウスイベントは現在マウスカーソルの下にある要素をターゲットにします。そのため、mouseover イベントと mouseout イベントはありますが、対応する touchover イベントと touchout イベントはなく、touchend イベントのみがあります。
最も一般的な問題は、ユーザーがタップし始めた要素を削除(または移動)した場合です。たとえば、カルーセル全体にタップ ハンドラがあり、カスタムのスクロール動作をサポートする画像カルーセルについて考えてみましょう。使用可能な画像が変更されたら、一部の <img>
要素を削除し、他の要素を追加します。ユーザーが画像のいずれかをタップし始め、その画像を削除した場合、(img 要素の祖先にある)ハンドラはタップイベントの受信を停止します(イベントは、ツリーに存在しないターゲットにディスパッチされるためです)。ユーザーが指を動かして画像を削除したとしても、指を 1 か所に置いたままにしているように見えます。
もちろん、タップがアクティブなときにタップ ハンドラを持つ要素(またはタップ ハンドラを持つ祖先を持つ要素)を削除しないようにすれば、この問題を回避できます。または、静的な touchend/touchmove ハンドラを登録するのではなく、touchstart イベントを受け取るまで待ってから、touchmove/touchend/touchcancel ハンドラをタッチスタート イベントのターゲットに追加する(終了/キャンセル時に削除することをおすすめします)。これにより、ターゲット要素が移動または削除されても、引き続きタップのイベントを受信できます。こちらで試すことができます。赤いボックスをタップし、押したまま Esc キーを押して DOM から削除します。
#4: タップしてカーソルを合わせる
マウス ポインタのメタファーは、カーソルの位置とアクティブな選択を分離しました。これにより、デベロッパーはホバー状態を使用して、ユーザーに関連する情報を非表示または表示できるようになりました。ただし、現在のところ、ほとんどのタッチ インターフェースでは、ターゲットに指が「カーソルを合わせた」ことを検出しません。そのため、セマンティックに重要な情報(たとえば、「このコントロールについて?」ポップアップの表示など)は、ホバーに基づいて提供しても問題ありません。ただし、タップ操作でその情報にアクセスできる方法も提供する必要があります。ホバーを使用してユーザーに情報を伝える際は、その方法に注意する必要があります。
興味深いことに、CSS の :hover 擬似クラスは、タッチ インターフェースによってトリガーされる場合があります。要素をタップすると、指が離れるまで :active になり、:hover 状態も取得されます。(Internet Explorer では、:hover はユーザーが指を置いている間のみ有効です。他のブラウザでは、:hover は次のタップまたはマウスの移動まで有効です)。これは、タッチ インターフェースでポップアウト メニューを機能させるための優れた方法です。要素をアクティブにすると、:hover 状態も適用されるという副作用があります。例:
<style>
img ~ .content {
display:none;
}
img:hover ~ .content {
display:block;
}
</style>
<img src="/awesome.png">
<div class="content">This is an awesome picture of me</div>
別の要素がタップされると、その要素はアクティブではなくなり、ホバー状態は消えます。これは、ユーザーがマウスポインタを使用して要素から移動したときと同じです。コンテンツを <a>
要素でラップしてタブストップにすることもできます。これにより、ユーザーはマウスのホバーやクリック、タップ、キープレスで追加情報を切り替えることができ、JavaScript は必要ありません。ウェブ オーディオ プレイグラウンドをタッチ インターフェースで適切に動作させる作業に着手したところ、嬉しい驚きでした。ポップアウト メニューではすでにこのような構造を使用していたため、タップ操作でうまく機能していました。
上記の方法は、マウス ポインタ ベースのインターフェースとタッチ インターフェースの両方に適しています。これは、ホバー時に「title」属性を使用する場合とは対照的です。この場合、要素がアクティブになっても表示されません。
<img src="/awesome.png" title="this doesn't show up in touch">
#5: タップとマウスの精度
マウスは概念的には現実とは関連性がありませんが、基盤となるオペレーティング システムは通常、カーソルの正確なピクセル単位で追跡するため、非常に正確です。一方、モバイル デベロッパーは、タッチスクリーンでの指タップは精度が低いことを知っています。これは主に、画面に接触する指の表面積が原因です(指が画面を遮ることも原因の一つです)。
多くの個人や企業が、指による操作に対応したアプリやサイトを設計する方法について、広範なユーザー調査を実施しており、このトピックに関する書籍も多数出版されています。基本的なアドバイスとしては、パディングを増やすことでタッチ ターゲットのサイズを大きくし、要素間のマージンを増やすことで誤タップが発生する可能性を減らすことです。(ただし、タッチイベントとクリック イベントのヒット検出処理には、マージンは含まれず、パディングは含まれます)。Web Audio Playground に加える主な改善策の 1 つは、接続ポイントのサイズを大きくして、正確にタップできるようにすることでした。
タップベースのインターフェースを扱う多くのブラウザ ベンダーは、ユーザーが画面をタップしたときに正しい要素をターゲットにし、誤ったクリックの発生を減らすためのロジックをブラウザに導入しています。ただし、通常はクリック イベントのみが修正され、移動イベントは修正されません(ただし、Internet Explorer は mousedown/mousemove/mouseup イベントも変更しているようです)。
#6: タッチハンドラを残さないとスクロールがジャンクしてしまう
また、タップ ハンドラは必要な要素にのみ限定することも重要です。タップ要素は帯域幅が非常に高くなる可能性があるため、スクロール要素にタップ ハンドラを配置しないことが重要です(処理が、ジャンクのない高速なタップ スクロールを実現するためのブラウザの最適化を妨げる可能性があるためです。最新のブラウザは GPU スレッドでスクロールしようとしますが、各タップ イベントがアプリによって処理されるかどうかを最初に JavaScript で確認する必要がある場合、これは不可能です)。この動作の例を確認できます。
この問題を回避するためのガイダンスとして、UI のごく一部でのみタッチイベントを処理する場合は、その部分にのみタッチハンドラをアタッチします(ページの <body>
にはアタッチしません)。つまり、タッチハンドラのスコープを可能な限り制限します。
#7: マルチタッチ
最後に、興味深い課題として、このインターフェースは「タッチ」ユーザー インターフェースと呼ばれていますが、実際にはほぼすべてマルチタッチに対応しています。つまり、API は一度に複数のタッチ入力を提供します。アプリケーションでのタッチのサポートを開始する際は、複数のタッチがアプリケーションにどのように影響するかを考慮する必要があります。
主にマウスで操作するアプリを作成してきた場合、最大 1 つのカーソル ポイントで作成することに慣れています。通常、システムは複数のマウスカーソルをサポートしていません。多くのアプリケーションでは、タッチイベントを単一のカーソル インターフェースにマッピングするだけですが、デスクトップのタッチ入力で使用されているほとんどのハードウェアは、少なくとも 2 つの入力を同時に処理できます。また、新しいハードウェアのほとんどは、少なくとも 5 つの入力を同時にサポートしているようです。画面上のピアノ キーボードを開発する場合は、複数の同時タップ入力をサポートする必要があります。
現在実装されている W3C Touch API には、ハードウェアがサポートするタッチポイントの数を判断する API がないため、ユーザーが望むタッチポイントの数をできるだけ正確に推定する必要があります。また、実際に表示されるタッチポイントの数に注意を払い、適宜調整することも必要です。たとえば、ピアノ アプリで、タップポイントが 2 つを超えない場合は、「コード」UI を追加することをおすすめします。PointerEvents API には、デバイスの機能を判断する API があります。
タッチアップ
この記事が、マウス操作とともにタッチ操作を実装する際の一般的な課題について、いくつかのガイダンスを提供できたのではないでしょうか。他のアドバイスよりも重要なのは、モバイル、タブレット、マウスとタッチの両方に対応したパソコン環境でアプリをテストすることです。タップとマウスのハードウェアがない場合は、Chrome の [タップイベントをエミュレート] を使用して、さまざまなシナリオをテストします。
これらのガイダンスに従うことで、タップ入力、マウス入力、さらには両方のスタイルで同時に操作できる魅力的なインタラクティブ エクスペリエンスを構築できるだけでなく、比較的簡単に構築できます。