スマホ用のSingle Page Applicationサイトを作る上での問題点と対応

# はじめに

スマートフォン向けFX情報サイト[SmartFX](http://smartfx.minkabu.jp)をBackbone.jsを使ったSPA(Single Page Application)で運用しています。
前回は[SinglePageApplicationにおける問題点と対応](http://qiita.com/takeshy/items/b7e1b6bd30d12ecb02c6)として全般的なSPAの問題を書きましたが、今回はスマホに特化したスマホサイトをSPAで構築するにあたって気づいた問題点とその対応方法を共有したいと思います。

# 画面遷移に思いのほか時間がかかる！

SPAで構築しているのに、次の画面の遷移が思っているより遅い感じがします。
サーバにアクセスしていないハズなのに。。
## 原因
clickイベントの発動にスマホの場合は時間がかかるためです。
スマホの場合はタッチがリンクを押したいのか、それともスクロールしたいだけなのかを判定するために一定時間経過しないとクリックと判定しません。
が、その判定時間がかなり長いものと思われます。

## 対策
clickイベントを使わず、touchstart,touchmove,touchendに対してイベントハンドラを用意し、標準よりも短い時間でclick判定をすることにより、画面遷移をより速く体験してもらえるようにします。
Touchしにくいところにあり、toche即そのリンクを押したいと判定できる場合は、touchstartの瞬間にnavigateしてしまうのもありだと思います。(ヘッダのロゴ押下によるTopページ遷移 etc.)

```coffeescript:router.coffee
$(document).on('touchstart',"a",(e)=>
  @startTimes[$(e.currentTarget).attr("href")] = new Date()
)

$(document).on('touchmove',"a",(e)=>
  @moves[$(e.currentTarget).attr("href")] = true
)

$(document).on('touchend',"a",(e)=>
  href = $(e.currentTarget).attr("href")
  if @startTimes[href] && !@moves[href] && new Date().getTime() - @startTimes[href].getTime() >= 120
    e.preventDefault()
    e.stopPropagation()
    href = href.replace("/smart","")
    @navigate(href, {trigger: true})
  @moves = {}
  @startTimes = {}
)
```

# 画面がちらつく
スマホで画面をtouchすると画面がちらつき、うっとうしいことがあります。

## 原因
1. 存在しないエレメントを指定してイベントハンドラを登録している場合
2. クリックイベントに対してイベントハンドラを登録している場合

## 対策
1.はそうならないよう、気をつけましょうw

2.なぜか、clickイベントを使って処理を登録してしまうと、画面がちらついてしまうようです。
なので、スマホの場合はclickイベントを使わず、touchstart,touchemove,touchendのイベントのみを使うことで、ちらつきを防げます。
実装は上記の遷移の処理と同じです。

# コピーのDialogが出てきてうっとうしい。

## 原因
+や-ボタンを長押ししつづけると値が更新されるようにしたところ、長押し判定によりSafariのコピー、辞書、ユーザ辞書のDialogが出力されるため

## 対策
下記のcssを記述すると防げるようになります。

```css
body {
  -webkit-touch-callout:none;
  -webkit-user-select:none;
}
```

# スライドメニューがカクつく

メニューボタンを押すとcssのrightを設定することでメニューをスライド表示させるようにしていますが、その動作がカクつきます。

## 対策
3DのCSSを使うことで強制的にGPUを使わせることで滑らかにします。
常にZ軸は0でもtranslate3dでx,yを操作するとGPUを使ってくれるようです。
某cosmeさんのスマホサイトと[SmartFX](http://smartfx.minkabu.jp)のヘッダのメニューをiPhoneで押し比べると、GPUの威力がわかります。


# スクロールがカクつく

バーチャルトレード機能で、ネイティブ風にヘッダとフッターを固定するために、ヘッダー部、コンテンツ部、フッター部と分けて、各部位のheightを設定し、コンテンツ部のcssにoverflow:auto;を指定することでコンテンツ部のみをスクロールさせるようにしたところ、スクロールがカクカクします。

## 対策
下記のようにscrollを無効にし、スライドメニューと同じように3DのCSSを使って自前でスクロール処理を実装

```js
$("body").bind("touchmove.scrollContents",(e)->
  e.preventDefault()
)
```

# フッターのメニューが使いにくい。(iOS)
バーチャルトレード機能で、ネイティブ風に画面下部にメニューボタンを置いているが、そのメニューボタンを押すつもりがSafariのツールバーが呼び出されて簡単に押せない。
また、URLバーとツールバーが表示されると画面領域がせまい。

## 対策
iOS7.1から下記のmetaタグを指定することでURL領域を小さくしてツールバーの出力を
抑制することが可能となりました。

```html
<meta name="viewport" content="minimal-ui width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no">
```
![ツールバーなし](https://qiita-image-store.s3.amazonaws.com/0/10645/dcd56090-4d07-2126-efda-b21d8b224b3f.png)

# Cookieが読めない

保存したはずのcookieの値が参照できない

## 原因
pushStateでパスを変えても、cookieを読み込み直してくれる訳ではないため。
つまり最初にアクセスしたURLのパスのcookieしか読めない。

## 対策
毎回Pathに"/"を指定すれば使えるかもしれないが、うっかりパスを指定せずに保存すると詰むので、データを保存したかったらcookieを使わず、localStorageを使う。

# localStorageへの書き込みがエラーになる

iPhoneのsafariなのにも関わらず、書き込みエラーになる。

## 原因
プライベートモードの場合はlocalStorageの書き込みがエラーになる。

## 対策
localStorageのsetItemがエラーになるとそこで処理が止まってしまうので、setItemは処理の一番最後など止ってもいい箇所で行う。
またプライベートモードでも動作させたい場合は、グローバル変数に保存して、変数→localStorageの順で参照することで、spaが動いている間は状態を保持できるようにする。

# まとめ
これらのことを前もって知っていれば、どれだけ助かったか。。。