ネイティブアプリのようなWEBアプリ「Sun」がどういう技術・方法で作られているか解析してみた。
#読み込み時の挙動
sunはアクセスした環境別(PCブラウザ・モバイルブラウザ・WEBアプリモード)の画面が表示される。
※WEBアプリモードとはナビゲーションバーやアドレスバーの無い状態。
##アクセス環境をチェック
まずwindow.navigator.standalone
でWEBアプリモードか否かを評価し、真の場合はWEBアプリモードの画面を表示する。
偽の場合はさらにユーザーエージェントを調べモバイルかPCかを評価して表示内容を切り替えている。
if (!window.navigator.standalone) {
if (navigator.userAgent.match(/like Mac OS X/i)) {
//モバイルブラウザ
}else {
//PCブラウザ
}
}else {
//WEBアプリモード
}
##body内のHTMLを丸々差し替え
モバイルブラウザの場合とWEBアプリモードの場合はbody以下のHTMLを丸々差し替えている
PCブラウザの場合はそのまま表示。
//モバイルブラウザ
$('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>');
##オフラインの場合はお知らせを出す
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
が使えない場合の処理は無いっぽい。
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ヘッダがアスタリスクなのですべてネットワークリソースにアクセスしている!?
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面を作る
<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
を指定する
#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
が設定されているので、回転アニメーションによって画面が切り替わる。
<div id="layer" style="-webkit-transform: translate3d(0px, 0px, -160px) rotate3d(0, 1, 0, -90deg);">
#グラフ表現
highcharts.jsを使用して実装している
http://www.highcharts.com
var chartTemp = new Highcharts.Chart({
//parameters
});
#エリアスクロール
サイドメニュー&週間天気ともにoverflow: scrollを使っている
-webkit-overflow-scrolling: touch;
を使ってスムーズなスクロールを実現している
#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;
が使えない環境ではスムーズなスクロールを実現できない。