Ajax使用時のブラウザーバックボタン(history.pushState)の2つの対策

2018年4月14日

当サイトはアフィリエイト広告を利用しています。

基本のバックボタン対応

Ajaxで問題となるのが、ブラウザのバックボタン。
Ajaxは履歴が全く保存されません。そのため、ブラウザのバックボタンを使って前後するとどんなに読み込んだ履歴でも最初からやりなおしになってしまいます。
すごいページ遷移した後に、一度バックボタンを使ってしまうとすべての履歴がなくなってしまうということが起こってしまいます。これはユーザビリティーMAXで最悪です。

ですが、ありがたいことに、HTML5以降であれば、使えるhistory.pushStateというものがあります。
履歴を独自に保存して使えるようにするためのjavascriptのAPIです。
検索するといろんな情報が見つかるのですが、情報をまとめてみました。

対応ブラウザ

IE10以降でしか利用できないのですが、それ以外であれば、ほぼ全部利用できそうです。全部は調べてませんが。

使い方

  • history.pushStateでデータを保存。
  • ブラウザのバックボタンが押された。
  • window.onpopstateで保存したデータを復元。

というのが基本です。

history.pushState(state, title, url)

  • stateはオブジェクトです。復元したいデータを保存させます。
    {page : html}というフォーマットです。htmlの値が復元したいデータになります。
    {page : $('#hoge').html()}というような感じで復元したいデータを保存します。
  • titleは現時点では利用されていないので、nullでOKです。
  • urlは現在のページのURL。相対URLも指定できます。

これで、現在のページの内容(ajaxで読み込んだタグのid等)とその内容を表示するURLがpushStateで保存されます。
※オブジェクトのキーとなるpageは、history.pushStateをコールするたびにユニークにする必要はありません。同じでもpushなのでデータはまるごと保存されて、保存された順にpopされます。
※キーと同様にurlもユニークである必要はありません(※1)。
(※1)urlはユニークにしなくても動きます。ただ、SEO的にはURLを変更したほうがよいので変更しましょう。このサンプルではurl変更はしてません。

※例:
トップページでajaxで.doneの場合に#hogeに受け取ったresを読み込みます。
$('#hoge').append(res);
受け取ったら、その状態をヒストリーとして保存します。

//jqueryのready記述省略
//ajaxの処理省略
//1回目
.done(function(res, textStatus, jqXHR){
 $('#hoge').append(res);
 //ajaxで読み込んだ$('#hoge').html()の内容をトップページのurlで保存。
 history.pushState({ page : $('#hoge').html() }, null, ""); 
})

ここで、またユーザーがもっと読むなどのアクションをとったとします。
同じように、

//jqueryのready記述省略
//2回目
.done(function(res, textStatus, jqXHR){
 $('#hoge').append(res);
 //history.pushStateで変更されたajaxのhtml内容をそのまま新しい履歴としてまるごと保存します。
 history.pushState({ page : $('#hoge').html() }, null, "");
})

ここから戻るためにブラウザのバックボタンを押します。バックボタンを押したときの処理はpopstateで受け取ります。

//jqueryのready記述省略
window.addEventListener("popstate", function(event){
 if(event.state != null) {
  //バックボタンた押されるたびに、pushしたpageオブジェクトのデータがまるごとpopされます。
  $("#hoge").html(event.state.page); 
 }
}

というようにして保存したデータをpopして表示します。
ただし、これでうまく表示できるようになるのは、あくまでajaxのページ遷移なしの状態の場合のみです。

別のページに移動してしまう場合の対応

例えば、ajaxで読み込みこんだページのリンクなどで、別のページに移動してしまうと(通常のページ遷移)、history.pushStateでpushしていてもバックボタンで戻ると、ajaxで読み込んだ内容はなくなってしまいます。

この場合は、フォームのtextareaなどを使って事前にajaxで読み込んだデータを保存しておき、それを読み込ませるようにするのが一番手軽にできる方法です。

//jqueryのready記述省略
//ajaxの処理省略
.done(function(res, textStatus, jqXHR){
 $('#hoge').append(res);
 //ajaxで読み込んだ$('#hoge').html()の内容をトップページのurlで保存。
 history.pushState({ page : $('#hoge').html() }, null, "");
 //pushした内容と同じものをまるっとtextareaに保存。
 $("textarea#savedata").val($('#hoge').html());
})

//フォームデータがあれば読み込む
if($("textarea#savedata").val() != "") {
 $("textarea#savedata").val($('#hoge').html());
 //保存してあったデータを表示
 $('#hoge').html($("textarea#savedata").val());
}

というようにtextareaを確認して、保存したデータがあればその内容を該当エリアに戻すという方法で対応します。

シングルページだけでなくページ遷移を伴うような場合はこれらの対策が必要です。

ちなみに、Chromeとfirefoxではほぼほぼ狙い通りの動きをしてくれますが、IEはhistoryは大丈夫そうなのですが、フォームデータでの復活処理がうまく動かないです。
※metaタグでexpireすればいいという記事をみて対策してみたのですがうまく動いてくれませんでした。
こうなると…cookieにデータ保存して、うんぬんとか…。

なお、pushStateで戻した内容やフォームから読み込んだデータは、delegateかlive(※liveは非推奨なので$(document).on~の書き方)であればちゃんとイベント発火します。

ですが、datepickerの動作がおかしくなります。pop後に再度イニシャライズしても表示されなかったり、consoleにエラーが表示されます。
datepickerは現在調査中。

pushStateは手軽に簡単にできますが、クロスブラウザやイベント関連が複雑になるので大変です。というお話でした…orz

-ワードプレス
-,