HTML
CSS
JavaScript
Web
Ajax

[ページ遷移しないWebシステム] JavaScriptによるURLの操作と、ページ遷移しないプログラム

 ページ遷移しないWebシステムを作成する場合、いくつかの問題点に遭遇します。今回はその一つである、ブラウザの戻るボタン問題について解決していきます。

以前の内容

[ページ遷移しないWebシステム] JavaScriptによるWindow Framework
https://qiita.com/SoraKumo/items/18197b5b61cf8061ae5d
[ページ遷移しないWebシステム]Qiitaの投稿をページ遷移無しで表示してみる
https://qiita.com/SoraKumo/items/0e017195d4ad8a88d762

関連リンク

サンプルプログラム  https://mofon001.github.io/npage/
サンプルソース    https://github.com/mofon001/npage
ページ遷移しないBlog https://croud.jp/

戻れない戻るボタン

 たとえば、JavaScriptでページ遷移しないブログシステムがあったとします。Ajaxによってコンテンツを非同期で拾ってきます。ユーザは操作を行い、いくつかの記事を切り替えました。そして、ふと、一つ前の記事が読みたくなり、ブラウザの戻るボタンを押します。

「うわぁぁ、別のサイトに切り替わったぁぁぁ!!!!」

 そうです、JavaScriptで画面を書き換えたページは、ブラウザの履歴には残らないのです。そして戻るボタンによって、一つ前に見ていたサイトに切り替わるのです。

オーイ、ミズシマ、イッショニ、マエノキジヘカエロウ

 このネタが分かる人は少なそうです。それはどうでもいいとして、解決方法を考えます。実はJavaScriptには、履歴を操る方法が存在しているのです。

 重要なのは以下の三つです

  • location.search
  • history.pushState
  • popstateイベント

 これらを利用して、理想的な戻るボタンの動きを実現します。

ページの構築手順

 以下の手順に従えば、だいたいうまくいきます。

  1. location.searchからページに関するパラメータを抽出
  2. パラメータに従い画面を書き換える
  3. ページ切り替えの操作が行われたら、history.pushStateで履歴をねつ造する
  4. ブラウザの戻るや進むボタンが押された場合、popstateが呼び出されるのでlocation.searchのパラメータに従い画面を書き換える

 popstateで画面の書き換えを行う場合は、history.pushStateは呼び出してはいけません。既に履歴に入っているからです。

サンプルプログラム

 御託は良いから、とっとと出すもの出せやといわれそうなので、出しますサンプル

index.html
<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8"/>
    <link rel="stylesheet" href="Main.css"/>
    <script type="text/javascript" src="Main.js"></script>
    <title>ページ遷移しないための履歴制御</title>
</head>
<body>
</body>
</html>
Main.css
div:nth-child(1){
    background-color: #cccccc;
    padding: 10px;
}
div:nth-child(2){
    margin:10px 0px;
    background-color: #88ff88;
    padding: 10px;
}

div:nth-child(3){
    border: 1px solid;
    height: 300px;
    padding: 20px;
}
Main.js
(function(){
//最初に実行するファンクションを設定
document.addEventListener("DOMContentLoaded",onLoad);

//ページが読み込まれた際に最初に呼び出される
function onLoad(){
    //--------------------------------------------
    //ページタイトルと経過時間表示場所の作成
    var header = document.createElement("div");
    document.body.appendChild(header);
    header.innerHTML = "<span>ページ遷移しないプログラム 経過時間:</span><span>0</span>";
    //ページが遷移していないことを確認するため、経過時間を表示
    var t = header.querySelector("span:nth-child(2)");
    setInterval(function(){t.textContent=parseInt(t.textContent)+1},1000);

    //--------------------------------------------
    //ページ切り替えボタンの作成
    var menu = document.createElement("div");
    document.body.appendChild(menu);
    menu.innerHTML = "<button>ページ0</button><button>ページ1</button><button>ページ2</button>";
    var buttons = menu.querySelectorAll("button");
    buttons.forEach(function(v,i){v.addEventListener("click",function(){changePage(i,true)})})

    //--------------------------------------------
    //メインメッセージ表示領域の作成
    var main = document.createElement("div");
    document.body.appendChild(main);

    //--------------------------------------------
    //URLのパラメータ部分から、表示ページを切り替え
    function goLocation(){
        //パラメータの読み出し
        var p = {};
        location.search.substring(1).split('&').forEach(function(v){s=v.split('=');p[s[0]]=s[1];});
        //指定ページに飛ぶ
        changePage(p['p']>=0?p['p']:0,false);
    }

    //--------------------------------------------
    //ページ切り替えとブラウザの履歴管理
    function changePage(page,flag){
        //flagがtrueなら、ページの状態を履歴に保存
        if(flag)
            history.pushState(null,null,"?p="+page);
        //ページ内容を書き換え
        main.innerHTML = ["あいうえお","かきくけこ","さしすせそ"][page];
    }

    //ブラウザの「戻る」「進む」ボタンが押された場合のイベント処理
    addEventListener('popstate', function(){goLocation();}, false);
    //初期ページへ飛ぶ
    goLocation();
}

})();

 私がWeb系のプログラムを作る際は、ちょっとした例外を除けばBODYタグの中は空です。ページ遷移しないプログラムは、基本的にベタでHTMLを書きません。JavaScriptで生成するのが基本なのです。HTMLを書くのは一番最初にjsファイルを読み出す部分と、innerHTMLなどに突っ込む部分だけです。

 それから今回、DOMのノードを探す際、セレクターにclassやidを指定していません。使ってはいけない訳ではありませんが、それらに頼らないで目的のものが指定できるぐらいには、セレクターの指定方法は覚えておいた方が後々のためです。

実行画面

image.png

 ボタンを押すと内容が書き換わり、ブラウザのURLが変化します。そしてページ遷移が起こっていないことを確認するため、経過時間を表示しています。ボタンを何回か押した後、ブラウザの戻るボタンや進むボタンを押してみてください。ページ遷移せずに、必要な場所だけ書き換わるはずです。

SEO対策

 実は今回の内容は、ブラウザの戻るボタンだけの問題ではありません。SEO対策でも必要になるのです。サーチエンジンはURLが同一だと、違う内容が表示されていたとしても、別々のデータとしてキャッシュしてくれません。当たり前といえば当たり前です。今回のようにパラメータに意味を持たせれば、違うページとしてキャッシュしてくれるのです。

 また、Googleで確認した結果、JavaScriptで動的に作成したページでも、きちんと内容を読み取ってキャッシュされました。ただしURLにパラメータを与えるのは必須条件です。

まとめ

 ページ遷移しないWebシステムは、ページのルーティングがサーバサイドにある限り実現できません。有名どころのフレームワークは壊滅です。

 ページ遷移しないフレームワークを作るならば、クライアントサイドでルーティングさせる必要があります。その部分を作るのは、それほど難しいことではありません。結局の所、コントローラに相当する機能をクライアントに持っていくことになるので、距離ができてしまうサーバサイドとのデータのやりとりを、いかに簡潔に記述できるかが重要になります。

 そんなこんなで何年か後には、ページ遷移しないことを前提としたフレームワークが当たり前の世の中になっていて欲しいところです。