ネイティブアプリのような超軽快ボタンをWebで実現してみた

hosikiti2013-11-06

iOSAndroidのモバイルアプリをWebアプリで作り始めて自分が我慢ならなかったのが「ボタン」だ。Aタグを使っても、clickイベントをバインドしても、反応が遅い、トロいボタンになってしまう。ネイティブにはやはり勝てないのか。クイックなボタンはWebで作れないのか…ダメ元で試行錯誤した結果、、、
ネイティブアプリのボタンと見間違うほど機敏なボタンが出来上がった!
Webでもここまで動くのか!と感動してしまった。

ルック&フィール

見ての通り。「タップ」を押すと1ずつカウントアップする。「リセット」を押すと0に戻る。

「タップ」ボタンを押した時の感じがとても気持ちいいのでぜひ試してほしい。二本指でトリルのように超高速でタップしても完全に追随するのは驚愕だ。早押しゲームのような耐えうるレスポンスだろう。なお、このアプリはタッチデバイススマホタブレット)でしか動作しない。動作検証した端末はiPhone 4S, Xperia Acro HD, iPad mini。どれも快適にカウントアップが行えた。

動作デモ:http://jsbin.com/iNOnAWU/3
スマートフォンタブレットで開いてください。

レスポンスを高めるポイント

  • aタグは使わないで、イベントリスナを使う(今回はZeptoライブラリを使用)
  • タップ時のスタイルを必ず定義しておく(今回は.hoverというスタイル)
  • clickイベントは絶対に使わない
  • 代わりにtouchstartイベントで押下時のスタイル(.hover)を付与
  • touchendイベントで押下時につけたスタイル(.hover)を除去

clickイベントがなぜ遅いか

Googleによれば、モバイルブラウザではclickイベント時には約300msの待ち時間が発生する。
なぜなら、300ms以内にもう一度タップされればダブルタップと認識しないといけないからだ。
touchstart, touchendイベントにはこれらの無駄な「待ち」は存在しない。だから速い。

参考: https://developers.google.com/mobile/articles/fast_buttons?hl=de-DE.

押された感の演出

touchstartイベントをclickイベントの代わりに使う、というテクニックはそれなりに知られている。ただ、touchstartを使えば、それがイコール気持ちのよいボタンにはならない。「押された」感を演出することで初めて、ユーザに「あ、押されたってわかってるね。キモチイー」と感じさせる要因となる。touchstartイベントが起きたら即座に「押下された状態」のスタイルに変更することが大切なのだ。

ソース全文

<!DOCTYPE html>
<html lang="ja">
<head>
	<meta charset="utf-8">
	<meta name="viewport"
	      content="width=device-width, initial-scale=1.0, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0">
	<meta name="apple-mobile-web-app-capable" content="yes"/>
	<style type="text/css">
		body {
			margin: 0;
			padding: 0;
			width: 100%;
			font-family: "メイリオ", "MS Pゴシック", sans-serif;
		}
		.container > * {
			width: 100%;
			margin: 0 auto;
		}
		.center {
			text-align: center;
		}
		span.btn {
			display: inline-block;
			height: 40px;
			width: 80%;
			padding-top: 11px;
			background-color: #EEE;
			color: #555;
			border-radius: 8px;
			text-align: center;
			margin-bottom: 10px;
			font-size: 140%;
			font-weight: bold;
		}
		span.btn.tall {
			height: 80px;
			padding-top: 45px;
		}
		span.btn.primary {
			background-color: #c65840;
			color: #fff;
		}
		span.btn.hover {
			background-color: #ccc;
		}
		span.btn.primary.hover {
			background-color: #B34F37;
		}
		.footer {
			position: fixed;
			bottom: 5px;
		}

		#disp {
			font-size: 500%;
			padding-top: 13%;
		}
	</style>
</head>
<body>
<div class="container">
	<div id="disp" class="center"></div>
	<div class="footer center">
		<span class="btn primary tall" id="countUp">タップ</span>
		<span class="btn" id="reset">リセット</span>
	</div>
</div>
<script type="text/javascript" src="//cdnjs.cloudflare.com/ajax/libs/zepto/1.0/zepto.min.js"></script>
<script type="text/javascript">
	var c = 0;
	$("#disp").text(c);
	$(".btn").on('touchstart', function(){
		$(this).addClass('hover');
	});
	$(".btn").on('touchend', function(){
		$(this).removeClass('hover');
		$(this).trigger('touched');
	});
	$("#countUp").on('touched',function(){
		$("#disp").text(++c);
	});
	$("#reset").on('touched',function(){
		c = 0;
		$("#disp").html(c);
	});
</script>
</body>
</html>