62
55

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

TurbolinksとJavaScriptの折り合い

Posted at

Rails 4には、ページ遷移を高速化するためのTurbolinksという仕組みが組み込まれています。以前に、これらの枠組みについておおまかに触れましたが、今回はJavaScriptの動作に絞って考えていきます。

いきなりまとめ

  • ページ遷移ごとに<body>とその中身は入れ替わる(それ以外のものはそのまま残る)
  • ページ遷移ごとにreadyが動くかは選べる
  • リンク段階で止めることも可能

Turbolinksの動作条件

まず最初に動作条件を示しておきますが、Turbolinksは、以下のように動作します。

  1. まず、リンクの段階でTurbolinksに適するかチェック
  2. Ajaxでリンク先をロード
  3. ロードしたデータを見て、JSやCSSが合わないなど、うまく行かなければ普通の遷移にフォールバック
  4. ロードしたデータから<body><title>を抽出して、ページに適用

除外されるリンク

Turbolinksの動作から除外されるリンクは以下のようなものです。

  • (リンク自体の性質じゃないけど)Shift+クリック、センタークリックなど、修飾付きでリンクが選択された場合
  • サイト外へのリンク1
  • POST、DELETEなど、GETメソッド以外の動作を指定してあるリンク
  • .html以外の拡張子がついたファイル(設定で変更可能です)
  • そのリンク自身、あるいは上位要素にdata-no-turbolink属性を指定してあるリンク

除外すべきリンク

サイト内のリンクの場合、上で書いたようにいったんAjaxロードした上で、不適切なら普通の遷移に切り替えるという動作をします。そういうわけで、そもそもJSやCSSを違えたページへリンクを貼る場合、最初から除外しておいたほうがいいでしょう。

また、AngularやReactなど、フロントエンドフレームワークを動かす場合、Turbolinksは干渉してしまう危険が大きいし、もともとページ遷移も少ないのでそこまで役に立ちません。サイト全体をフロントエンドフレームワークで組む場合には、Turbolinksを止めてしまったほうが適当でしょう。

JavaScriptの動作に与える影響

変わるもの、変わらないもの

Turbolinksのページ遷移の場合、body要素を新しいページのものに入れ替えてしまいます。そういうわけで、body自体やその下にある要素へ割り当てたイベントは、ページ遷移でクリアされます。一方で、

  • windowdocumenthtmlに割り当てたイベント
  • JavaScriptのグローバルオブジェクト
  • CSS
  • タイマー(setIntervalsetTimeout

といったものは、Turbolinksのページ遷移でリセットされません。

イベント委譲

$(window).on('click','.selector')のように、バブリングするイベントをwindowへ割り当ててしまうと、ページ遷移のあとにも残ってしまいます。全ページで稼働させるものならそれでいいのですが、このページでだけ適用したいという場合には、$('body').on('click','.selector')のように、bodyあるいはそれ以下に割り当てましょう。

外部スクリプト

TwitterボタンやGoogle Analyticsのように外部のスクリプトを読み込む機会も多いと思いますが、これらを動かす場合にも、Turbolinksの効果を最大限に発揮するために、

  1. 読み込みコードは、それぞれが持つグローバルオブジェクト(Twitterだったらtwttr)がない場合にのみ駆動させる
  2. ページ遷移のたびに、適用させるためのメソッド(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のイベントを、ページ固有に使う

scrollresizeなど、ウインドウ単位でないと取れないイベントもありますが、このようなものをある特定のページ内で使いたい場合、

  1. 全体で、windowで拾いたいイベントに、bodyのオリジナルイベントを呼び出すような形のハンドラを入れておく
  2. ページ単位のところで、bodyのオリジナルイベントにハンドラをセットする

というような流れにすることができます(各ページ単位のスクリプトからwindowにイベントをセットすると、ページを超えて残ってしまいます)。

ページ認識してのスクリプト実行

Turbolinksを使う上では、全JavaScriptをつないで1ファイルにしてしまうのが大前提となっています。そういうわけで、ビューごとにスクリプトを書く場合、そのビューにあるオブジェクトなどをチェックして、正しくなければすぐに脱出するように書いておかないと、無関係なページで発動してしまう危険があります(とりわけ、bodyやタイプセレクタでイベントを割り当てる場合)。

GET送信のフォームでもTurbolinks

フォームの送信はブラウザの機能ですので、なにもしないとTurbolinksが無効となってしまいます(検索機能など、GETのレスポンスが体感的に効いてくる場面もありえます)。送信すべきデータをjQueryのserialize()などを使って回収して、Turbolinks.visit(url)とすることで、JavaScriptコードからTurbolinksによる遷移を実行できます。

  1. サイト内にリダイレクタがある場合、そのリダイレクト先が外部サイトであってもデータを拾ってくることがあります。特に、オープンリダイレクタを作ってしまっていた場合や、完全なる第三者のサイトにリダイレクトする場合には、XSS攻撃の危険が生じることもあります。

  2. 単なる即時実行ですので、別に関数に入れずに書いても構いません。

62
55
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
62
55

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?