スマホ向けにサイトを作っていると、ブラウザの挙動が遅くてUXを低下させてしまうことがあります。例えば、画面をタッチしたときの:hoverの挙動。あと、jQueryのanimate()の挙動。とか。

スマホブラウザはJavaScriptの実行速度が遅いので、animate()の挙動とかはCSSで書くのがベストです。これはよく知られているので、実装されているサイトも多いのではないでしょうか。(あ、具体的な実装はまた次回ご紹介したいと思います。)

今回はですね、あまり話題にならない:hoverのことです。

:hover がおかしい?

マウスカーソルを乗せたときのスタイルを設定するのが:hoverです。スマホだとタッチした瞬間に適用されると思いますよね。でも例えばiPhoneで:hoverを使うと、指を離したときにスタイルが適用されます。ですので、「もう押してないのに…」という場所が「押されたスタイル」のままになってしまいます。また、タッチした瞬間にはなにも反応がないため、タイムラグがあるようにも見えます。「画面の動き」が「実際の指の動き」と違うのは、操作している実感を鈍らせて、使う人を不安にさせます。『ちゃんと押せてるのかな?』という不安がストレスにつながるので、UX的によろしくないですね。

解決するにはtouchstart/touchend

もともと:hoverはマウスカーソル(=画面内のどこかが常にポインティングされている)を前提にしたものなので、タッチ操作のスマホでは使い勝手が悪いようです。そこで、JavaScriptのtouchstarttouchendというイベントハンドラを使います。

名前のままですが、タッチした瞬間と、指を離した瞬間にイベントが発生します。これを使って、タッチした瞬間DOMにクラスを追加して、指を離したらクラスを削除してあげれば擬似的に:hoverを表現できます。JavaScriptなので、CSSにはできない「気遣い」も、これだと表現できます。例えばjQueryを使うと、以下のようなコードでネイティブアプリと同じくらいスムーズに反応してくれます。

/* まず関数を定義 */
var linkTouchStart = function(){
	thisAnchor = $(this);
	touchPos = thisAnchor.offset().top;
	//タッチした瞬間のa要素の、上からの位置を取得。
	moveCheck = function(){
		nowPos = thisAnchor.offset().top;
		if(touchPos == nowPos){
			thisAnchor.addClass("hover");
			//タッチした瞬間と0.1秒後のa要素の位置が変わっていなければ hover クラスを追加。
			//リストなどでa要素が並んでいるときに、スクロールのためにタッチした部分にまでhover効果がついてしまうのを防止している。
		}
	}
	setTimeout(moveCheck,100);
	//0.1秒後にmoveCheck()を実行。
}
var linkTouchEnd = function(){
	thisAnchor = $(this);
	hoverRemove = function(){
		thisAnchor.removeClass("hover");
	}
	setTimeout(hoverRemove,500);
	//0.5秒後にhoverRemove()を実行。
	//指を離した瞬間にhover効果を切るよりも、要素が変化した手応えを表現できる。
}

/* タッチイベントで上記の関数を呼び出す */
$(document).on('touchstart mousedown','a',linkTouchStart);
//a要素をタッチ、もしくはマウスクリックしたらlinkTouchStart()を実行。
$(document).on('touchend mouseup','a',linkTouchEnd);
//a要素から指を離す、もしくはクリックを終えたらlinkTouchEnd()を実行。

↑なんかコメントが多くて見づらいのでコメント無しバージョンも下に置きます(笑)

var linkTouchStart = function(){
	thisAnchor = $(this);
	touchPos = thisAnchor.offset().top;
	moveCheck = function(){
		nowPos = thisAnchor.offset().top;
		if(touchPos == nowPos){
			thisAnchor.addClass("hover");
		}
	}
	setTimeout(moveCheck,100);
}
var linkTouchEnd = function(){
	thisAnchor = $(this);
	hoverRemove = function(){
		thisAnchor.removeClass("hover");
	}
	setTimeout(hoverRemove,500);
}

$(document).on('touchstart mousedown','a',linkTouchStart);
$(document).on('touchend mouseup','a',linkTouchEnd);

あとは、CSSに.hoverのスタイルを追加すればOKですね。

いかがでしょう。参考になれば幸いです!