うちのブログは記事を更新するよりもガワ部分を更新するのがメインになっていますが、今回はすべてのページ移動に非同期遷移を採用してみました。
※現在はリニューアルしたため廃止しています。

具体的やっていることは、①jQueryの.load();でリンク先のコンテンツを読み込み、②HTML5のpushStateでブラウザの履歴を追加する、といった内容です。

.load();だけだと同ページ内にただコンテンツを読む込むだけですが、pushStateという技術を使って履歴の追加とURLの書き換えを行うことで、ページ移動を行っているかのように見せかけるんですね。

メリットはページ移動時に「切れ目」が生まれないことで表現の幅が広がる。魅力的だけどデメリットもたくさんあって、それを解決していったまとめを書き殴っていく。

ブラウザの戻る・進むボタンが機能しない問題

ページ移動を行っているかのように見せかけているだけなので、ブラウザの戻る・進むボタンを押した時にページ移動しないんです。これは使い勝手の悪さがヤバイ。

これを解決するためにpopState使います。popStateは「URLが変更された」をフラグにイベントを発生させることができる。popState発火時にページをリロードさせればいいんだ。

ところが1つ目の問題が発生。popStateの「URLが変更された」時というのは、ブラウザの戻る・進むボタンを押した時だけでなく、ページ内リンクで「#~~~」が追加された時も含まれてしまう。ページ内移動するたびにリロードが発生してしまうダメっぷり。

1つ目の問題を解決するためにoriginalEvent.stateを使います。これは「現在のURLはpushStateで読み込まれたページなのか?」を判別するために使います。

pushStateの形式history.pushState(state, title, url);の、stateの部分に何でもいいから文字列を渡しとけば、pushStateで読み込んだページはoriginalEvent.state = true、そうでなければnullとなります。

window.history.pushState('state', title, url);

そして先ほどのpopStateで、trueだったら〜の分岐を付け加える。

$(window).on('popstate', function(e){
    if(!e.originalEvent.state) { return; }
    history.go(0);
});

ところがここで2つ目の問題が発生。サイトに来訪して最初に開いたページはpushStateで読み込んだページではないので、stateが仕込まれていない=戻ってもリロードが発生しない、という事態に。図にするとこんな感じ。

jquery-pushstate01

この2つ目の問題を解決するために、replaceStateを使います。これは現在のページ履歴を書き換えるために使います。現在のページを読み込んだ時に、現在のページのstateを書き換えるんですね。追加ではなく書き換えなので、無駄な履歴も残りません。

$(window).on('load', function(){
    title = document.title;
    url = window.location.href;
    window.history.replaceState('state', title, url);
});

これをやるとこうなります。

jquery-pushstate02

ここまでで「ブラウザの戻る・進むボタンが機能しない」問題は解決しました。pushStatepopStatereplaceStateの3つを使いこなせなきゃいけないんですね。

読み込んだ部分の外部jsが機能しない問題

.load();したコンテンツにjsが反映されません。使ってるjsの物にもよるので具体的な内容は割愛しますが、うちの場合slick.jsとhighlight.jsはこのように再実行することで正常に表示されました。

$('#article').load(request + ' ' + '.class', function(){
    //__highlight.jsの再実行
    $('pre code').each(function(i, block) { hljs.highlightBlock(block); });
    //__slick.jsの再実行
    $('.class').slick();
});

.load();で読み込んだ記事部分のTwitterウィジェットが正常に表示されないのは何とかしたいところ・・・。

センスが問われるpushState

以上の2点のデメリットを解決できれば、pushStateかなり実用できます。ただ、実際やってみるとなんか昔のFlashサイト感が多少ある・・・。使い方にセンスが求められますね。全てが上手く噛み合わないと「これpushStateさせる意味ある?」ってなっちゃう。