Rails 4には、ページ遷移を高速化するためのTurbolinksという仕組みが組み込まれています。以前に、これらの枠組みについておおまかに触れましたが、今回はJavaScriptの動作に絞って考えていきます。
いきなりまとめ
- ページ遷移ごとに
<body>とその中身は入れ替わる(それ以外のものはそのまま残る) - ページ遷移ごとに
readyが動くかは選べる - リンク段階で止めることも可能
Turbolinksの動作条件
まず最初に動作条件を示しておきますが、Turbolinksは、以下のように動作します。
- まず、リンクの段階でTurbolinksに適するかチェック
- Ajaxでリンク先をロード
- ロードしたデータを見て、JSやCSSが合わないなど、うまく行かなければ普通の遷移にフォールバック
- ロードしたデータから
<body>と<title>を抽出して、ページに適用
除外されるリンク
Turbolinksの動作から除外されるリンクは以下のようなものです。
- (リンク自体の性質じゃないけど)Shift+クリック、センタークリックなど、修飾付きでリンクが選択された場合
- サイト外へのリンク1
- POST、DELETEなど、GETメソッド以外の動作を指定してあるリンク
-
.html以外の拡張子がついたファイル(設定で変更可能です) - そのリンク自身、あるいは上位要素に
data-no-turbolink属性を指定してあるリンク
除外すべきリンク
サイト内のリンクの場合、上で書いたようにいったんAjaxロードした上で、不適切なら普通の遷移に切り替えるという動作をします。そういうわけで、そもそもJSやCSSを違えたページへリンクを貼る場合、最初から除外しておいたほうがいいでしょう。
また、AngularやReactなど、フロントエンドフレームワークを動かす場合、Turbolinksは干渉してしまう危険が大きいし、もともとページ遷移も少ないのでそこまで役に立ちません。サイト全体をフロントエンドフレームワークで組む場合には、Turbolinksを止めてしまったほうが適当でしょう。
JavaScriptの動作に与える影響
変わるもの、変わらないもの
Turbolinksのページ遷移の場合、body要素を新しいページのものに入れ替えてしまいます。そういうわけで、body自体やその下にある要素へ割り当てたイベントは、ページ遷移でクリアされます。一方で、
-
window、document、htmlに割り当てたイベント - JavaScriptのグローバルオブジェクト
- CSS
- タイマー(
setInterval、setTimeout)
といったものは、Turbolinksのページ遷移でリセットされません。
イベント委譲
$(window).on('click','.selector')のように、バブリングするイベントをwindowへ割り当ててしまうと、ページ遷移のあとにも残ってしまいます。全ページで稼働させるものならそれでいいのですが、このページでだけ適用したいという場合には、$('body').on('click','.selector')のように、bodyあるいはそれ以下に割り当てましょう。
外部スクリプト
TwitterボタンやGoogle Analyticsのように外部のスクリプトを読み込む機会も多いと思いますが、これらを動かす場合にも、Turbolinksの効果を最大限に発揮するために、
- 読み込みコードは、それぞれが持つグローバルオブジェクト(Twitterだったら
twttr)がない場合にのみ駆動させる - ページ遷移のたびに、適用させるためのメソッド(Twitterなら
twttr.widgets.load())を呼ぶ
のような手順をたどりましょう。
イベントを付ける場所、コードの実行タイミング
そのままだと、jQueryのreadyは最初のロード時にしか実行されません。これを回避するには2つの方法があります。
-
ready以外にpage:loadのタイミングでも実行するようにハンドラをセットする -
jquery.turbolinksを使う
それぞれの場合に、どこでどんな処理をするか分類してみました。
| 全体ロード時に一度だけの処理 | ページ遷移のたびの処理 | |
|---|---|---|
| 使い道 | windowのイベントやタイマーなど、変化しない部分の処理 | ページごとに各エレメントへ割り当てるイベントなど |
jquery.turbolinksなしの場合の書き方 |
jQuery(function(){...}) |
$(document).on('ready page:load',function(){...}) |
jquery.turbolinksありの場合の書き方 |
(function(){...})()2
|
jQuery(function(){...}) |
windowのイベントを、ページ固有に使う
scrollやresizeなど、ウインドウ単位でないと取れないイベントもありますが、このようなものをある特定のページ内で使いたい場合、
- 全体で、
windowで拾いたいイベントに、bodyのオリジナルイベントを呼び出すような形のハンドラを入れておく - ページ単位のところで、
bodyのオリジナルイベントにハンドラをセットする
というような流れにすることができます(各ページ単位のスクリプトからwindowにイベントをセットすると、ページを超えて残ってしまいます)。
ページ認識してのスクリプト実行
Turbolinksを使う上では、全JavaScriptをつないで1ファイルにしてしまうのが大前提となっています。そういうわけで、ビューごとにスクリプトを書く場合、そのビューにあるオブジェクトなどをチェックして、正しくなければすぐに脱出するように書いておかないと、無関係なページで発動してしまう危険があります(とりわけ、bodyやタイプセレクタでイベントを割り当てる場合)。
GET送信のフォームでもTurbolinks
フォームの送信はブラウザの機能ですので、なにもしないとTurbolinksが無効となってしまいます(検索機能など、GETのレスポンスが体感的に効いてくる場面もありえます)。送信すべきデータをjQueryのserialize()などを使って回収して、Turbolinks.visit(url)とすることで、JavaScriptコードからTurbolinksによる遷移を実行できます。