スマートフォンサイトで要素タップ時の挙動を頑張った

2013年06月24日

スマートフォンで要素をタップした時の挙動が何かイマイチだなぁとよく思うので何とか対応してみようと思ったら意外な方向に話が進んだのでちょっとまとめてみるだけです。

ちなみに意外な方向に話が進んだってのは全部僕一人で進めた話。誰かと話したわけじゃないよ、ぼっちだよ。僕の中の他の6人と話したの。どうも、普段出てくるプライドが高くておしゃべりなミノルです。

hover疑似クラスが変

スマートフォンってさ、hover疑似クラス効かなくね?というか、変じゃね?タップして指話した時に効いたりしない?これおかしいと思うのは僕だけですか?こんなんじゃおちおちマルチデバイス対応時に :hover 書けねーよ。

サンプル

下のボタンは :hover になってます。

test

ホバーでちょっと押したっぽい感じになります。下に1pxずらして背景色と文字色を暗くしてますよ。

これをスマートフォンで見ると、

  • タップしたら :hover が適用される
  • タップして指を離したら :hover が適用される

って感じです。ただし、元に戻りません!

これちょっと意味分かんないですよね。

なので、ちょっと対応方法を探してみたら割と簡単に方法が見つかりました。みんな同じように感じてたんですね。

hover疑似クラスを疑似的に再現

やりたいことは、タップした際には :hover と同様の動きをして、指を離したら元に戻る、という動作。今回はjQueryを使って擬似的に再現してみましょう。

■Javascript
$(".btn")
  // タッチしたとき
  .bind('touchstart', function() {
    // .touch を付与
    $(this).addClass('touch');
  })
  // 指を離したとき
  .bind('touchend', function() {
    // .touch を削除
    $(this).removeClass('touch');
  });

■CSS
.btn:hover, .btn.touch {
  /* 変えたいスタイル */
}

インデントが妙なのは気にしない( ´ ▽ ` )

これで良い感じに対応できますね。

ただしこれは、見ての通りcssに .touch を追記しないといけません。あんまりスマートじゃないですね。本当は 対象の要素の :hover を全部拾ってきて touchstart の時にくっつけたいんですけどやり方が分かりませんでしたorz

サンプル

test

この対応は通常のリンクによる画面遷移であれば問題ないのですが、実は touchend で発動させるJavascriptのイベントの場合にちょと困ったことになってしまいます。

キャンセルが出来ない

touchend で発動させる Javascript とはどんなものでしょうか。例えば以下のようなボタンですがそうです。これは、ボタンを押すと画像が表示/非表示をトグル切り替えします。

マーティ
「お嬢ちゃんちょっとそれ貸してくれるかい?」

「ホバーボード?」

いいね!

普通にタップしてる分には問題ないんです。では次は、上のボタンをちょっとタップしてから、そのままスクロールしてもらっても良いですか?

やっていただきました?これダメですよね。

PCだと .touch 状態のままになるし、スマートフォンだと スクロールしても touchend イベントはキャンセルされずに発動してしまうんですね。てことは、ユーザーの意図とは関係なく、たとえばスクロールしたかっただけなのにマーティの画像が表示されることになってしまうわけです。トラップですね。

※ただこれは実は、 click イベントだったらスクロールした時点でキャンセルしてくれます。click イベントは優秀です。結構空気を読んだ対応をしてくれますよ。

でも、click イベントは touch 系のイベントに比べて体感ではっきりと違いが分かるくらいにレスポンスが遅いので出来る限りアクションを伴う事には使いたくありません。

なので touch イベントでもスクロールしたら無視してくれるようにするべく、何とか対応したいと思います。

ちなみに上のボタンのコードはこれです

■html
<p><a class="btntest01">ホバーボード?</a></p>
<p class="imgtest01"><img ... /></p>

■Javascript
$(".imgtest01").hide(); // 画像隠す
$(".btntest01")
  // クリックした時
  .bind( "click", function(e){
    e.preventDefault(); // デフォルトの動きをキャンセル
  })
  // タッチしたとき
  .bind('touchstart', function() {
    // .touch を付与
    $(this).addClass('touch');
  })
  // 指を離したとき
  .bind('touchend', function() {
    // .touch を削除
    $(this).removeClass('touch');

    // ↓↓ここからが画像のトグル処理
    $target = $(this).parent().next();

    if ( $target.css("display") === "none" ) {
      // 消えてるときは表示させる
      $target.show("slow");
    } else {
      // 表示されてるときは消す
      $target.hide("slow"); }
    });

何とかしてスクロールにも対応する

さて、ではトグル切り替えボタンをタップした後にスクロールしても大丈夫なように対応しましょう。スクロールしても大丈夫なようにというか、むしろ「タップしたけどその後指をスライドした場合はイベントを無効とする」って感じの対応になります。

サンプル

グリフの子分
「Hey!マクフライのおまぬけめ!」

そのボードは水の上じゃ動かねんだよ!

■html
<p><a class="btntest02">そのボードは水の上じゃ
動かねんだよ!</a></p> <p class="imgtest02"><img ... /></p> ■Javascript // タップのフラグ // 0 = 触ってないか、タップして移動せずに指を離した // 1 = タップした時 // 2 = 移動(スクロールとか) // 3 = 移動した上で指を離した var touchFlg = 0; $(".imgtest02").hide(); // 画像隠す $(".btntest02") // タッチしたとき .bind('touchstart', function() { $(this).addClass('touch'); // .touch を付与 touchFlg = 1; // フラグ変更 }) // スクロール(というか移動)した場合 .bind(touchMove, function() { $(this).removeClass('touch'); // .touch を削除 touchFlg = 2; // フラグ変更 }) // 指を離したとき .bind('touchend', function() { $(this).removeClass('touch'); // .touch を削除 if ( touchFlg === 2 ) { // フラグ変更 touchFlg = 3; // スクロールしてたら3に } else { touchFlg = 0; // スクロールしてなかったら0に } // 画像のトグル処理 $target = $(this).parent().next(); // スクロールしてたら無視 if ( touchFlg >= 2 ) { return false; } if ( $target.css("display") === "none" ) { // 消えてるときは表示させる $target.show("slow"); } else { // 表示されてるときは消す $target.hide("slow"); } });

touchFlgというフラグを使って、タップの状態を覚えておくことで「スクロールしたからtouchendイベントは無視!」みたいなことが出来るようになりました。

フラグの種類を

  • タップしてない
  • タップしてる
  • 移動してる

の3種類じゃなくて

  • タップしてない
  • タップしてる
  • 移動してる
  • 移動した上で指を離した

の4種類にしているところがポイントですね。

3種類だと指を離した時点でスクロールしようがしまいが「タップしてない」になってしまって結局判定できなくなってしまいます。

汎用性をちょっと高くする

どうでしょうか。僕はさほどJavascriptに明るいわけじゃないので、もしかして「そんな面倒な事しなくても良い方法あるじゃんか」ってような事なのかもしれないですが、考えて試して作ってみるのは楽しかったのでとりあえず公開する感じです。

ただ、これだと用途が限定されるかなぁって気がするので、ちょっと汎用性を高くしてみようと思います。

タップ状況の取得と.hover だけを抽出

さっきの例だと、実際にやっていることは

  • .hover の付与、削除
  • タップ状況の保持
  • touchend イベント

の3種類の動作について書かれています。 touchend イベントは個別に作るだろうとおもいますが、そのほかの2つについては大体どういう処理でも使えそうなのでそこだけ切り出すとしましょう。

■Javascript
function hoverboard( ele ) {
  // イベントのラッパー
  var supportTouch = 'ontouchstart' in window;
  var touchStart = supportTouch ? 'touchstart' : 'mousedown';
  var touchMove = supportTouch ? 'touchmove' : 'mousemove';
  var touchEnd = supportTouch ? 'touchend' : 'mouseup';

  // タップのフラグ
  // 0 = 触ってないか、タップして移動せずに指を離した
  // 1 = タップした時
  // 2 = 移動(スクロールとか)
  // 3 = 移動した上で指を離した
  var touchFlg = 0;
 
  // タッチした場合
  ele.bind(touchStart, function() {
    // .touch を付与
    $(this).addClass('touch');
    touchFlg = 1;
  })
  // スクロール(というか移動)した場合
  .bind(touchMove, function() {
    // .touch を削除
    $(this).removeClass('touch');
    touchFlg = 2;
  })
  // 指を離した場合
  .bind(touchEnd, function(e) {
    // .touch を削除
    $(this).removeClass('touch');

    if ( touchFlg === 2 ) {
      touchFlg = 3; // スクロールしてたら3に
    } else {
      touchFlg = 0; // スクロールしてなかったら0に
    }
  });
} 

使い方としては、

  • cssでタップした際に適用させたいスタイルを.hoverに記述する
  • 要素を hoverboard() に渡す

これでホバーが動くと思います。

hoverboard( $("#submit") ); 

サンプル

パワーが十分なら別だがよ!

■Javascript
hoverboard($(".btntest03"));
$(".imgtest03").hide(); // 画像隠す
$(".btntest03")
  // クリックした時
  .bind( "click", function(e){
    e.preventDefault(); // デフォルトの動きをキャンセル
  })
  // 指を離したとき
  .bind(touchEnd, function() {
    // 画像のトグル処理
    var $target = $(this).parent().next();

    // スクロールしてたら無視
    if ( touchFlg >= 2 ) { return false; }

    if ( $target.css("display") === "none" ) {
      // 消えてるときは表示させる
      $target.show("slow");
    } else {
      // 表示されてるときは消す
      $target.hide("slow");
    }
  });

おわりに

やっとここで最初に言った「意外な方向に話が進んだ」兼なんですが、そもそもなんでマウスホバーしたときにあしらいを変えるんでしょうね。

マウスホバーってどういう行動かというと、単なる「移動」であって、しいて言ったところで「今まさにこのボタンを押さんとしている」という状態かなぁと思います。

とりあえずボタンを例にして考えると、マウスホバーすることでユーザーが「あぁ、これはボタンなんだな」って思うからですか?それとも「ボタンってのはへこむもんだろ?」って考えからですか?

マウスホバーしないと「あ、押せるんだ」って思わないボタンをデザインすること自体が思想としてダメじゃないですかね。

ボタンはそりゃ押せばへこみますが、それは「押したとき」であって、「ボタンに指をかけた状態」ではへこみませんよね。

ホバーについてじっくり考えれば考えるほど何でホバー時に変化させるんだろうともやもや考えてしまいます。

まぁ、元々のwebページはボタンないしリンクをクリックすればページを遷移するものだから、押した時じゃなくて押す直前で変化させないと変化する前に遷移してしまう、という事情とかもあったのかもしれませんね。

でも、今はもう「クリック(タップ)=画面遷移」ではなくなってると思います。

ホバーさせなくてもちゃんと押せることが分かるデザイン、ユーザーの行動に合わせた自然なあしらい、正しいのはこれだ!!って思ってる分けじゃないですが、「ホバー時は変化させるものだ」という何というか、お約束みたいなものからそろそろはみ出してもいいんじゃないかなぁと思いました。

というわけで、以上、2015年よりお伝えしました。

って、え、2015年ってもうすぐじゃないですか。こんなんで本当に空飛ぶ自動車とかちりよけペーパーとか上からフルーツが出るシステムダイニングとかホバーボードとかタイムマシンとか80’sカフェとか自動サイズ調節機能付きジャケットとか秒単位の正確さの天気予報とか実現すんのか!?

他にこんな事も書いてます

FBでコメント