iPhoneでjavascriptグリグリの「i4U」の技術的な話

1ヶ月ほどまでにiPhoneのsafariでどこまでリッチなサービスが作れるのかを試してみたくて、女の子画像ブックマークサービス「4U」のiPhone用ビューアを作ってみました。

このサービス、扱っているコンテンツが最高なのは勿論なのですが、safariでリッチなインターフェースを実現するために使っているテクニックがなかなか面白い、というか苦労の結晶なのでとりあえず解説用の記事を書いておこうと思う。

iPhone用のwebサイトを作ろうとするとiui.jsとか使うのが多いみたいですが、これだとアプリっぽいインターフェースになるので、果たしてサイトとして使いやすいのかは疑問です。

画像のビューアとしては次々とスライドショーできるのがいいなと思うし、iPhoneらしさを出すとしたら指でスクロールさせながら閲覧できるのがベストだと考えました。

それで参考となったサイトが

です。基本的な操作で、javascriptでタッチイベントを取得する部分は上のサイトを参考にしました。このサイトではアニメーションの動作にcss3の機能を使っていてそれ自体は大変スムーズに動作して美しいのですが、一枚一枚次に進んでいくという動きが気に入らず多少手直しすることにしました。

開発時に期待したポイント

  • 指でスクロールさせる時の強弱で画像スライドの速度が変わる
  • スムーズなスクロール・画像のロード
  • ページの遷移が発生しないようにする

はじめ上記のサイトを参考にcss3のアニメーションをカスタマイズして指で現行と同じようなスクロールを実装しようとしたのですが、css3が難しいことと数十ミリ秒単位でcssのパラメータの値を変えようとするのでサイト全体が重くスムーズなアニメーションとはいきませんでした。

そこでjavascriptによるアニメーションで実装することにしました。jsでアニメーションとなるとtweeenerとかライブラリがありますが、これらもiphoneのsafari上で動作させるには重いことと、私が実装したい動きとなると自由度に問題がありました。結局アニメーションの部分は自分で実装しました。これが結局シンプルでスクリプトも軽く期待の動作に特化できるので良かったですね。

//document全体にイベントを張るとリンクのクリックが出来なくなるので、画像の部分だけにイベントをセットする
	document.getElementById("container").addEventListener('touchstart', touchHandler, false);
	document.getElementById("container").addEventListener('touchmove', touchHandler, false);
	document.getElementById("container").addEventListener('touchend', touchHandler, false);
	document.getElementById("container").addEventListener('touchcancel', touchHandler, false);

スムーズなスクロールを実施するのも苦労しました。同時にたくさんのdomオブジェクトのスタイルを変更しようとする(今回の場合x方向の座標)とそれだけ重くなります。今回のサービスは横に長くスクロールできるので多くの画像が同時にスクロールされているように見えますが、同時に動いているオブジェクトは3つです。今画面上にあるものとその前後の画像だけがユーザの操作によって動きます。それ以外のものは、画面の見えない部分に放置されています。巻物みたいな感じに今見えている部分だけが見えていて、他の部分は折りたたまれています。

よくよく考えると、同時に2枚だけでもいけそうです。

画像のローディングについては、ユーザが見ようとした瞬間にローディング(create)をするようにしています。スクリプト的には取得しようとしたelementが無かったらhtmlを生成して張り付ける。貼り付けられた瞬間に画像が生成されるという単純な仕組みです。特に高度な管理はしていませんが、シンプルですがスクリプト的にも簡潔で良い感じです。

iPhone.image.prototype.x = function(){
	if(!this.elm)this.create();//画像を移動させようとしたのにそのエレメントが無かったので作る。

	if(arguments.length > 0){
		var p = arguments[0];
		this.elm.css('left',(p - 160) + "px");
	}else{
		var left = parseInt(this.elm.css('left'));
		return left + 160;
	}
}

画像のリストは10枚ごとにサーバに問い合わせています。レスポンスが返ってきたらそれまでのリストに追加。後は同様に動作します。このあたりのデータの管理方法も面白いのですが、スクリプトを見ていただければとおもいます。

javascriptのライブラリとしてjqueryを利用しているのですが、ここはいただけませんね。このライブラリの読み込みが無くなればもっと軽くなるかもしれません。

その他のTips

//アニメーションさせるイベントを扱う場合は下記のような設定がお勧め
<meta name="viewport" content="width=device-width; initial-scale=1.0; minimum-scale=1.0; maximum-scale=1.0; user-scalable=0;" />

//上のアドレスバーを消す方法
window.onload = function(){
	setTimeout(scrollTo,500,0,1);
}