低価格・高品質・最速のHTMLコーディングはクロノドライブへ

画像を使わない擬似フォームUIの作り方 第03回 「タップ時のレスポンス向上」

第2回まで「擬似フォームUI」の作り方を解説してきましたが、第3回となる今回はちょっと方向性を変えて、チェックボックスやラジオボタンをタップしたときに、瞬時にレスポンスを返す方法を解説していきます。

皆さんは、スマートフォンでフォームのチェックボックスをタップしたときに、一瞬遅れてチェックが入るといった経験をしたことはないでしょうか?

次のサンプルを操作するとお分かりいただけると思います。
サンプル
※iPhone(iOS)だとより分かりやすいです

image001.png
この時点でのサンプルを表示する

一瞬とはいえ、このラグがあることでタップできたのか不安になり、もう一度タップしてチェックを解除してしまうなど、サクサクと操作することができません。

このラグを減らすことで、心地良い操作感をユーザーに与えるフォームへとカスタマイズしていきます。

STEP09 clickイベントではなく、touchstart/move/endイベントを使う

ラグの原因ですが、これはclickイベントを使うと必ず発生してしまう仕様となっています。
そのため、clickイベントを使わずにtouchstart/move/endイベントを使い、clickイベントを擬似的に再現します。

処理の順序としては次のようになります。

  1. 特定要素の上でtouchstartが発生
  2. touchmoveが発生したどうかを監視
  3. touchendの発生時にtouchmoveが発生していなかったらチェックを入れる処理を実行する

2番でtouchmoveが発生したかどうかを監視することがポイントとなります。
touchmoveが発生した場合、ユーザーはタップが目的ではなく、スクロールのために指を置いただけですので、この場合はチェックを入れる処理を実行しないようにしなくてはいけません。

以上のことを踏まえてコードにするとこのようになります。

01// touchmoveを監視する変数
02var moved = false;
03 
04// チェックボックス
05$('.pseudoUI-cb li').on('touchstart', function(e){
06    // 初期値に戻す
07    moved = false;
08 
09    // タップが開始されたことを示すため、クラスを付与する
10    $(this).addClass('tap');
11 
12}).on('touchmove', function(e){
13    // touchmoveが発生したらmovedをtrueにする
14    moved = true;
15 
16    // クラスも削除する
17    $(this).removeClass('tap');
18 
19}).on('touchend', function(e){
20    // ブラウザのデフォルトイベントをストップ(a要素の上にいた場合にはページ遷移のキャンセルなど)
21    e.preventDefault();
22 
23    // movedがfalseだったら実行
24    if(!moved){
25        $checkbox = $('input', this);
26        if($checkbox.is(':checked')){
27            $checkbox.removeAttr('checked');
28            $(this).removeClass('checked');
29        }else{
30            $checkbox.attr('checked', 'checked');
31            $(this).addClass('checked');
32        }
33    }
34 
35    // 初期値に戻す
36    moved = false;
37 
38    // タップが終了するため、クラスも削除する
39    $(this).removeClass('tap');
40 
41});

Androidでは、touchstart/endイベントがうまく発生してくれない場合があるようです。touchstart/move発生時に「e.preventDefault()」を実行することで必ず発生するようになりますが、その代わりにスクロールができなくなります。
このバグをすべての機種で検証したわけではありませんが、4.0で発生するようで2.3.3/4.0.4/4.1.1では問題ありませんでした。

これを前回までに作った「$(‘.pseudoUI-cb li’).click(function(){ })」と置き換えたサンプルが次のものになります。
サンプル:サンプル

image003.png
この時点でのサンプルを表示する

このサンプルの1つ目は何も手を加えていない標準のもので、2つ目が今回の処理を加えたものなります。タッチした瞬間に背景のグラデーションが逆転し離した瞬間にチェックが入る動作になっていることがわかるかと思います。

このように、ユーザーのアクションに合わせてレスポンスを素早く返すことによって『ちゃんと押せたかな?』という不安を取り除き、心地良い操作感を与えます。

更にtouchmoveに若干の「あそび」を加えてもう少し使いやすくし、Android4.0のバグに対応するため、ユーザーエージェントによる処理の切り替えを行っていきます。

STEP10 touchmoveに「あそび」を作る

touchmoveが発生した場合にはチェックを入れる処理を実行しないという話しをしましたが、「あそび」が全くない場合は、タップの判定がシビアになり操作がしにくいものとなってしまいます。そのため、ここではtouchstartが発生した座標とtouchmove後の座標を比較し、5px以上の差がなければ許容範囲とする「あそび」を作ります。

タッチしている座標を取得するコードは次のようになります。
これを変数に入れておき、touchmoveの発生時に座標を比べます。

1$('.pseudoUI-cb li').on('touchstart', function(e){
2    y = e.originalEvent.touches[0].pageY
3    x = e.originalEvent.touches[0].pageY
4});

STEP09までに作成したコードに組み込むとこのようになります。

01var moved, start_y, start_x;
02 
03// チェックボックス
04$('.pseudoUI-cb li').on('touchstart', function(e){
05    moved = false;
06     
07    // タッチを開始した座標を取得
08    var touch = e.originalEvent.touches[0];
09    start_y = touch.pageY;
10    start_x = touch.pageX;
11 
12    $(this).addClass('tap');
13 
14}).on('touchmove', function(e){
15 
16    // タッチを開始した座標と動いた先の座標を比較し、差が5px以上ある場合はmovedをtrueにする
17    var touch = e.originalEvent.touches[0];
18    diff_y = start_y - touch.pageY;
19    diff_y = Math.sqrt(Math.pow(diff_y, 2));
20    diff_x = start_x - touch.pageX;
21    diff_x = Math.sqrt(Math.pow(diff_x, 2));
22    if(diff_y > 5 || diff_x > 5){
23        moved = true;
24        $(this).removeClass('tap');
25    }
26 
27}).on('touchend', function(e){
28    e.preventDefault();
29    if(!moved){
30        $checkbox = $('input', this);
31        if($checkbox.is(':checked')){
32            $checkbox.removeAttr('checked');
33            $(this).removeClass('checked');
34        }else{
35            $checkbox.attr('checked', 'checked');
36            $(this).addClass('checked');
37        }
38    }
39    moved = false;
40    $(this).removeClass('tap');
41 
42});

このように「あそび」を作ることによって、歩きながら操作するときなど、ほんの少し指が震えたり、指がズレてしまったときでも処理が実行されるため使いやすくなります。

サンプル

image005.png
この時点でのサンプルを表示する

STEP11 Android4.0のバグ対応

前段でも述べた通り、標準ブラウザではtouchstart/endが発生しないバグがあり、対処するとスクロールができなくなるという更に致命的な問題が発生します。2.3.3/4.0.4/4.1.1では発生しないとはいえ、すべての機種で検証できないため、安全策としてAndroidでは通常のclickイベントで行なうようにします。

ユーザーエージェントで判別してiPhoneとAndroidを切り替えるコード

01// ユーザーエージェントの取得
02var agent = navigator.userAgent;
03 
04// iOSの場合
05if(agent.search(/iPhone/) != -1 || agent.search(/iPad/) != -1){
06 
07// iOS以外の場合
08}else{
09 
10}

ユーザーエージェントを取得してif文で判別するだけなので特別難しいことはありません。
iOSの場合はtouchstart/move/endイベントを使った処理を実行し、そうでない場合はclickイベントを使う処理を実行します。
サンプル

image007.png
この時点でのサンプルを表示する

以上で第3回 「タップ時のレスポンス向上」は終わりです。
次回はごちゃごちゃしてしまったコードを見直し、スッキリさせて仕上げる方法を解説する予定です。

画像を使わずに作る擬似フォームUIの作り方

  1. 第01回 「CSS3を使った装飾」
  2. 第02回 「jQueryを使った擬似フォームUIの操作」
  3. 第03回 「タップ時のレスポンスの向上」
  4. 第04回 「仕上げ」

「フロントエンドエンジニアの教科書」を出版しました!HTML・CSS・JavaScript+α 次世代コーダーのための仕事術

HTMLコーダーからフロントエンドエンジニアにステップするために必要な知識と技術を解説。
現場で求められる人材となるためには、何を知っていて、何ができなければいけないのか。
Web制作の最先端にいるクロノドライブだからこそ教えられるノウハウが満載です。

出版社名:ソフトバンククリエイティブ
著者:クロノドライブ

Amazon.co.jp詳細ページへ