WEBアプリ「Sun」を解析してみた

ネイティブアプリのようなWEBアプリ「[Sun](http://pattern.dk/sun/)」がどういう技術・方法で作られているか解析してみた。

#読み込み時の挙動
sunはアクセスした環境別（PCブラウザ・モバイルブラウザ・WEBアプリモード)の画面が表示される。
※WEBアプリモードとはナビゲーションバーやアドレスバーの無い状態。

##アクセス環境をチェック
まず`window.navigator.standalone`でWEBアプリモードか否かを評価し、真の場合はWEBアプリモードの画面を表示する。
偽の場合はさらにユーザーエージェントを調べモバイルかPCかを評価して表示内容を切り替えている。

```:javascript
if (!window.navigator.standalone) {
	if (navigator.userAgent.match(/like Mac OS X/i)) {
		//モバイルブラウザ
	}else {
		//PCブラウザ
	}
}else {
	//WEBアプリモード
}
```
##body内のHTMLを丸々差し替え
モバイルブラウザの場合とWEBアプリモードの場合はbody以下のHTMLを丸々差し替えている
PCブラウザの場合はそのまま表示。

```:javascript
//モバイルブラウザ
$('body').addClass('install').html('<div id="install"><div id="homescreen"><span></span><h2 id="add">Add to your <strong>Home Screen</strong></h2></div></div>');

//WEBアプリモード
$('body').removeClass('preview').addClass('initialize').html('<div id="app"><div id="box"><div id="layer"><div class="slide"><div class="background"></div><a class="in">In</a><div class="pagination"><span class="active"></span><span></span><span></span><span></span></div></div></div></div></div>');
```

##オフラインの場合はお知らせを出す

```:javascript
if(!window.navigator.onLine) {
	$('.pagination').hide();
	$(document).bind('touchstart', function(e) {
		e.preventDefault();
	});
	notification("Sun requires a working 3G or WiFi connection.");
} else {
	//オンライン時の処理
}
```

##座標を取得して天気情報を取得する
`navigator.geolocation`が使えない場合の処理は無いっぽい。

```:javascript
if (navigator.geolocation) {  
	navigator.geolocation.getCurrentPosition(
		function(position) {  
			var lat = position.coords.latitude;
			var lon = position.coords.longitude;
		
			$.ajax({ url:'http://maps.googleapis.com/maps/api/geocode/json?latlng='+lat+','+lon+'&sensor=true',
					success: function(data){
						//data使ってごにょごにょ
					}
			});
			
  		}
  	);
}
```

#アプリケーションキャッシュ

##マニフェストファイル
マニフェストファイルによりほぼすべてのリソースがキャッシュされている。
が、NETWORKヘッダがアスタリスクなのですべてネットワークリソースにアクセスしている!?

```:text
CACHE MANIFEST
# Sun 2.1.0.5

NETWORK:
*

CACHE:
# style
resources/sun.css

# scripts
resources/library.js
resources/charts.js
resources/sunny.js

# images
resources/icon_adrift.png
resources/icon_envious.png
resources/icon_goldfish.png
resources/icon_jour.png
resources/icon_looper.png
resources/icon_moon.png
resources/icon_pastel.png
resources/icon_stardust.png
resources/icon@2x.jpg
resources/canvas.png
resources/canvas@2x.png
resources/ipad-landscape.png
resources/ipad-landscape@2x.png
resources/ipad-portrait.png
resources/ipad-portrait@2x.png
resources/iphone.png
resources/iphone5.png
resources/texture.jpg
resources/iphone.jpg
resources/iphone@2x.jpg
```

#ローカルストレージ
以下の情報をローカルストレージで管理している

- 都市情報（都市名・都市ID・座標情報）
- テーマカラー
- 摂氏 or 華氏(navigator.language == 'en-US'だと華氏)
- 風速単位（m/s or mph）


#3D表現を使った画面切り替え
##rotate3dでY軸に対して回転して立方体の4面を作る

```:html
<div id="box">
	<div id="layer" style="-webkit-transform: translate3d(0px, 0px, -160px) rotate3d(0, 1, 0, 0deg);">
		<div class="slide" style="-webkit-transform: rotate3d(0, 1, 0, 0deg) translate3d(0px, 0px, 160px);">
			/* 要素 */
		</div>
		<div class="slide" style="-webkit-transform: rotate3d(0, 1, 0, 90deg) translate3d(0px, 0px, 160px);">
			/* 要素 */
		</div>
		<div class="slide" style="-webkit-transform: rotate3d(0, 1, 0, 180deg) translate3d(0px, 0px, 160px);">
			/* 要素 */
		</div>
		<div class ="slide" style="-webkit-transform: rotate3d(0, 1, 0, 270deg) translate3d(0px, 0px, 160px);">
			/* 要素 */
		</div>
	</div>
</div>
```
**slide要素にtranslate3d(0, 0, 160px)している理由**
translate3d(0, 0, 0)のままだと、それぞれの面が中心でかさなり四角形にならないため、それぞれ面の半分のpxだけオフセットしている。
ただしその分拡大して見えてしまうため、親ボックス（layer要素）にtranslate3d(0, 0, -160px)をいれて見た目を最適化している。

##CSSで3D表現を指定する
layer要素に`transform-style: preserve-3d`、その親のbox要素に`perspective: 1200px`を指定する

```:css
#box {
	width: 100%;
	height: 100%;
	position: relative; 
	-webkit-perspective: 1200px; 
}
#layer {
	width: 100%; 
	height: 100%; 
	position: absolute; 
	-webkit-transform-style: preserve-3d; 
	-webkit-transition-property: -webkit-transform; 
	-webkit-transition-duration: 550ms; 
	-webkit-transition-timing-function: ease-out; 
	-webkit-transition-delay: initial;
	top: 0px; 
	left: 0px;
}
```


##JSからlayer要素のrotate3dを変更して回転する
layer要素のrotate3dプロパティのZ軸を変更して回転している。
layer要素には`-webkit-transition-property: -webkit-transform`が設定されているので、回転アニメーションによって画面が切り替わる。

```:html
<div id="layer" style="-webkit-transform: translate3d(0px, 0px, -160px) rotate3d(0, 1, 0, -90deg);">
```

#グラフ表現
highcharts.jsを使用して実装している
http://www.highcharts.com

```:javascript
var chartTemp = new Highcharts.Chart({
	//parameters
});
```

#エリアスクロール
サイドメニュー&週間天気ともにoverflow: scrollを使っている
`-webkit-overflow-scrolling: touch;`を使ってスムーズなスクロールを実現している

#HTMLのヘッダ

```:html
<!-- WEBアプリモードをONにする -->
<meta name="apple-mobile-web-app-capable" content="yes">
<!-- ステータスバーの表示を制御する -->
<meta name="apple-mobile-web-app-status-bar-style" content="black">
<!-- ホームアイコン下に表示されるアプリタイトルを指定する -->
<meta name="apple-mobile-web-app-title" content="Sun" />
<!-- アプリを開いた時に表示される画像（スプラッシュ画像）をサイズ別に指定する -->
<link rel="apple-touch-startup-image" href="resources/iphone.png" />
<link rel="apple-touch-startup-image" href="resources/ipad-portrait.png" media="screen and (min-device-width: 481px) and (max-device-width: 1024px) and (orientation:portrait)" />
<link rel="apple-touch-startup-image" href="resources/ipad-landscape.png"  media="screen and (min-device-width: 481px) and (max-device-width: 1024px) and (orientation:landscape)" />
<link rel="apple-touch-startup-image" href="resources/ipad-portrait@2x.png" media="screen and (min-device-width: 481px) and (max-device-width: 1024px) and (orientation:portrait) and (-webkit-min-device-ixel-ratio: 2)" />
<link rel="apple-touch-startup-image" href="resources/ipad-landscape@2x.png" media="screen and (min-device-width: 481px) and (max-device-width: 1024px) and (orientation:landscape) and (-webkit-min-device-ixel-ratio: 2)" />
<link rel="apple-touch-startup-image" href="resources/iphone5.png" media="screen and (device-width: 320px) and (device-height: 568px) and (-webkit-device-pixel-ratio: 2)" />

```



#雑感
普段WEBアプリモードの制作をしないので、WEBアプリの場合のメタ情報の書き方やデバイス毎の指定方法等、勉強になった。実際その機会が来た時に参考にしようと思う。
UI面だとスクロールがoverflow:scrollということが以外だった。
overflow:scrollだとタップしないとスクロールバーが出ないので、悪しき方法というイメージだったが、実際このアプリを使うと「スクロールできそうな場所」で使っているからか、迷うこと無くスクロールを使っていた。
今後エリアスクロールが必要な場面ではoverflow:scrollでもUI的に表現可能かを意識しようと思う。
追記：`-webkit-overflow-scrolling: touch;`が使えない環境ではスムーズなスクロールを実現できない。