infinite scrollとRails4のTurbolinks

TurbolinksはRails4で導入された画面遷移を高速化する機能です。
体感的にもすごく速くなります。
画面遷移先のコンテンツをAJAXでとってきて、bodyの内容を置き換える(cssやjavascriptを再ロードしない)というトリッキーなことをやっていて少々癖があります。
分かりやすいところでは、jQueryのreadyイベントが発火しないということですが、jquery.turbolinksである程度は対応できたりします。
Gemfile:
gem 'jquery-turbolinks'
application.js
//= require jquery //= require jquery.turbolinks //= require jquery_ujs // // ... 他のスクリプトを読み込む ... // //= require turbolinks
javascriptを書いていくとturbolinksのために予期せぬ動きをします。
FacebookやTwitterで無限スクロールを実現するのにjQueryのinfinite scrollプラグインを使用したときにも問題が発生しました。
infinite scrollはwindowオブジェクトにスクロールイベントを仕込んで、スクロール量に応じて次ページをバックグラウンドでとってくるという処理をしています。
turbolinksでページ遷移したときはwindowオブジェクトは残ったままですから、遷移先のページでスクロールをするとバックグラウンドでデータをとり続けるという問題が発生します。
こんなことが度々発生するので、開発の現場ではTurbolinksを切ってしまうことも多いのですが、Turbolinksのイベントを使いこなせるようになるとあっさり解決したりします。
- page:before-change Turbolinksが有効なリンクをクリックしたとき
- page:fetch 遷移先のページを取得開始したとき
- page:receive 遷移先のページをサーバから取得完了したとき
- page:before-unload ページを取得してコンテンツを置き換える直前
- page:after-remove event.dataに保存されているnodeが削除されたとき
- page:change コンテンツが置き換わったとき(DOMContentLoadedも発火する)
- page:update page:changeとjQueryのajaxSuccessの両方が発火したとき
- page:load loadが完了したとき
以下のようなスクリプトを埋め込むとイベントの発火状況がよくわかります。
$(document).on 'page:before-change', ->
console.log('page:before-change')
$(document).on 'page:fetch', ->
console.log('page:fetch')
$(document).on 'page:receive', ->
console.log('page:receive')
$(document).on 'page:change', ->
console.log('page:change')
$(document).on 'page:update', ->
console.log('page:update')
$(document).on 'page:load', ->
console.log('page:load')
$(document).on 'page:restore', ->
console.log('page:restore')
$(document).on 'page:before-unload', ->
console.log('page:before-unload')
$(document).on 'page:after-remove', ->
console.log('page:after-remove')
$(document).on 'ready', ->
console.log('ready')
URLを直接指定したとき
ready
page:change
page:update
リンクをクリックして画面遷移したとき
page:before-change
page:fetch
page:receive
page:before-unload
page:change
page:update
page:load
ブラウザの戻るボタン、進むボタンで移動したとき
page:before-unload
page:change
page:update
page:restore
リンクをクリックして画面遷移したときにはreadyイベントは呼ばれないので、jquery.turbolinksはそこを補ってくれたりするわけです。
戻ってきたときにはページ番号を維持して続きから動いて欲しいので、destoryではなくpauseにします。
画面が書き換わってからだと、DOMが書き換わってinifinite scrollを指定した要素がなくなっています。
要素がないとinfinite scrollインスタンスにアクセスできなくなってしまいますので、page:before-unloadイベント発火時にpauseにします。
で、すべての場合に呼ばれるpage:changeイベントでinfinite scrollを呼べばいいです。
インスタンスがなければ作られますし、あれば指定したオプションで設定しなおしてくれます。
オプションのstate.isPausedをfalseに明示的に指定するのがポイントです。
$(document).on 'page:change', ->
$(".pagination").hide()
$(".post-list").infinitescroll
navSelector: ".pagination"
nextSelector: ".pagination a[rel=next]"
itemSelector: ".post-item"
loading:
msgText: "現在読み込み中です"
finishedMsg: "全てのコンテンツを読み込みました"
state:
isPaused: false
$(document).on 'page:before-unload', ->
$(".post-list").infinitescroll('pause')
page:changeだと乱暴だと思う場合(page:changeは使用しないほうがいいという人もいる)は、ready, page:load, page:restoreを指定してあげればいいです。
