第2回まで「擬似フォームUI」の作り方を解説してきましたが、第3回となる今回はちょっと方向性を変えて、チェックボックスやラジオボタンをタップしたときに、瞬時にレスポンスを返す方法を解説していきます。
皆さんは、スマートフォンでフォームのチェックボックスをタップしたときに、一瞬遅れてチェックが入るといった経験をしたことはないでしょうか?
次のサンプルを操作するとお分かりいただけると思います。
サンプル
※iPhone(iOS)だとより分かりやすいです
この時点でのサンプルを表示する
一瞬とはいえ、このラグがあることでタップできたのか不安になり、もう一度タップしてチェックを解除してしまうなど、サクサクと操作することができません。
このラグを減らすことで、心地良い操作感をユーザーに与えるフォームへとカスタマイズしていきます。
STEP09 clickイベントではなく、touchstart/move/endイベントを使う
ラグの原因ですが、これはclickイベントを使うと必ず発生してしまう仕様となっています。
そのため、clickイベントを使わずにtouchstart/move/endイベントを使い、clickイベントを擬似的に再現します。
処理の順序としては次のようになります。
- 特定要素の上でtouchstartが発生
- touchmoveが発生したどうかを監視
- touchendの発生時にtouchmoveが発生していなかったらチェックを入れる処理を実行する
2番でtouchmoveが発生したかどうかを監視することがポイントとなります。
touchmoveが発生した場合、ユーザーはタップが目的ではなく、スクロールのために指を置いただけですので、この場合はチェックを入れる処理を実行しないようにしなくてはいけません。
以上のことを踏まえてコードにするとこのようになります。
05 | $( '.pseudoUI-cb li' ).on( 'touchstart' , function (e){ |
10 | $( this ).addClass( 'tap' ); |
12 | }).on( 'touchmove' , function (e){ |
17 | $( this ).removeClass( 'tap' ); |
19 | }).on( 'touchend' , function (e){ |
25 | $checkbox = $( 'input' , this ); |
26 | if ($checkbox.is( ':checked' )){ |
27 | $checkbox.removeAttr( 'checked' ); |
28 | $( this ).removeClass( 'checked' ); |
30 | $checkbox.attr( 'checked' , 'checked' ); |
31 | $( this ).addClass( 'checked' ); |
39 | $( this ).removeClass( 'tap' ); |
Androidでは、touchstart/endイベントがうまく発生してくれない場合があるようです。touchstart/move発生時に「e.preventDefault()」を実行することで必ず発生するようになりますが、その代わりにスクロールができなくなります。
このバグをすべての機種で検証したわけではありませんが、4.0で発生するようで2.3.3/4.0.4/4.1.1では問題ありませんでした。
これを前回までに作った「$(‘.pseudoUI-cb li’).click(function(){ })」と置き換えたサンプルが次のものになります。
サンプル:サンプル
この時点でのサンプルを表示する
このサンプルの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 |
STEP09までに作成したコードに組み込むとこのようになります。
01 | var moved, start_y, start_x; |
04 | $( '.pseudoUI-cb li' ).on( 'touchstart' , function (e){ |
08 | var touch = e.originalEvent.touches[0]; |
09 | start_y = touch.pageY; |
10 | start_x = touch.pageX; |
12 | $( this ).addClass( 'tap' ); |
14 | }).on( 'touchmove' , function (e){ |
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){ |
24 | $( this ).removeClass( 'tap' ); |
27 | }).on( 'touchend' , function (e){ |
30 | $checkbox = $( 'input' , this ); |
31 | if ($checkbox.is( ':checked' )){ |
32 | $checkbox.removeAttr( 'checked' ); |
33 | $( this ).removeClass( 'checked' ); |
35 | $checkbox.attr( 'checked' , 'checked' ); |
36 | $( this ).addClass( 'checked' ); |
40 | $( this ).removeClass( 'tap' ); |
このように「あそび」を作ることによって、歩きながら操作するときなど、ほんの少し指が震えたり、指がズレてしまったときでも処理が実行されるため使いやすくなります。
サンプル
この時点でのサンプルを表示する
STEP11 Android4.0のバグ対応
前段でも述べた通り、標準ブラウザではtouchstart/endが発生しないバグがあり、対処するとスクロールができなくなるという更に致命的な問題が発生します。2.3.3/4.0.4/4.1.1では発生しないとはいえ、すべての機種で検証できないため、安全策としてAndroidでは通常のclickイベントで行なうようにします。
ユーザーエージェントで判別してiPhoneとAndroidを切り替えるコード
02 | var agent = navigator.userAgent; |
05 | if (agent.search(/iPhone/) != -1 || agent.search(/iPad/) != -1){ |
ユーザーエージェントを取得してif文で判別するだけなので特別難しいことはありません。
iOSの場合はtouchstart/move/endイベントを使った処理を実行し、そうでない場合はclickイベントを使う処理を実行します。
サンプル
この時点でのサンプルを表示する
以上で第3回 「タップ時のレスポンス向上」は終わりです。
次回はごちゃごちゃしてしまったコードを見直し、スッキリさせて仕上げる方法を解説する予定です。
画像を使わずに作る擬似フォームUIの作り方
- 第01回 「CSS3を使った装飾」
- 第02回 「jQueryを使った擬似フォームUIの操作」
- 第03回 「タップ時のレスポンスの向上」
- 第04回 「仕上げ」