uhyohyo.net

JavaScript初級者から中級者になろう

十二章第三回 History API

次に紹介するのがHTML5 History APIです。Historyとは履歴のことです。履歴関係のことは、historyという名前のオブジェクトから利用可能です。

古典的な履歴操作

実はこのhistory自体は昔からあって、いくつかメソッドを持っていました。

history.back();

例えばhistory.backメソッドは、いわゆる「戻る」ボタンと同じ動きです。

実際にやってみましょう。

このボタンは次のようにできています。

<input type="button" value="戻る" onclick="history.back()">

逆に「進む」はhistory.forward()です。他に、「3個すすむ」とか「2個戻る」とかを汎用的に表現するhistory.goがあります。これは引数を取って、使いかたはこうです。

history.go(3);	//3個すすむ
history.go(-2);	//-2個戻る

このように、正の数値は進む、負の数値は戻るを表す数値を引数としてとります。

以上が、昔からある古典的な履歴操作です。次に、HTML5で加わった機能を説明します。

履歴の追加

HTML5での画期的な新機能は何かというと、履歴の追加です。勝手に自分で履歴を追加できるのです。

例えば、を押してみましょう。

押すと、実際にはページを移動していないのに、上のURLが「http://uhyohyo.net/javascript/testtest」というように変わったと思います。もちろん、実際にそんなURLを開いても何もありません。

さらに、そこからブラウザで「戻る」をやってみましょう。またもページが変わらずに、URLがもとのように戻ったと思います。つまり、これが意味するのは、このボタンによって、実際にページが変わったわけではないけれども履歴が追加されたということです。「進む」を押してみるとまたURLが変わるはずです。

それでは、その方法を紹介します。使うのは、history.pushStateメソッドです。

history.pushState(null, null, "/javascript/testtest");

今回用いたコードはこれです。3つの引数があることがわかりますね。ところで、nullというのは二章第十四回でも紹介したように「何もない」ことを表すのでした。つまり、今回第一・第二引数としては特に何も渡したくないということです。第三引数が、変わるURLだということが分かると思います。

実は第三引数だけは省略可能であり、省略した場合は履歴だけがひっそりと追加されてURLは変わりません。

では第一引数と第二引数は何かというと、第一引数はstateであり、これはその履歴と関連付けられた情報です。この情報をプログラムから利用したりしてみるわけです(後述)。ただ、今回は特に何もないのでnullにしてあります。

第二引数はタイトルです。これを指定してやると、URLだけでなくページのタイトルも変わることでしょう(ただし、2012/02/24現在で対応しているブラウザはありませんでした)。

ちなみに、「履歴を追加」ではなく「現在の履歴を上書き」してしまう、history.replaceStateもあります。使い方はpushStateと同じです。

履歴の活用

さて、これで新しい履歴を追加する方法が分かりました。ちなみに、追加した履歴を消す方法はありません。

ところで、実際にページが移動するわけでもない履歴を追加して何の役に立つのでしょうか。

というのも、実は履歴移動を検知できるのです。この機能と組み合わせて利用するといいでしょう。

それは、popstateイベントです。これは、履歴を移動したら発生するイベントです。ただし、別のページへ行ってしまう場合は発生しません。今回のように、追加された履歴の間で移動する場合に発生してくれます。

popstateイベントはどこで発生するかというと、実はdocumentなどではなくwindowです。すなわち、こうです。

window.addEventListener('popstate',function(ev){
},false);
        

しかし、「履歴を移動した」だけでは情報が足りません。そこで役に立つのがさっきのstateです。実は、その履歴に関連付けられた情報はイベントオブジェクトのstateプロパティに入っています。つまり今回の場合はev.stateですね。これを用いればどういう履歴かを判別できるわけです。

さて、それではこれらを活用して具体的に何ができるかを見ていきたいと思いますが、前回に続きサンプルを提示したいと思います。

今回作ってみるのはタブを用いたページです。タブを選択すると内容が切り替わるというやつで、まずはHistory APIを使っていない土台のサンプルを見てください。上に3つリストがあって、クリックするとタブA,タブB,タブCが切り替わります。ちなみに、WAI-ARIAを使っていますが、HTML要素に情報を関連付けるくらいにここでは抑えておけばいいでしょう。

それではここに、History APIを組み込んでみましょう。「タブを開いた」ということを履歴に追加して、「戻る」ボタンなどを使って履歴を移動するともとのタブが開けばいいのです。

これを組み込んだのが次のサンプル2です。

タブを切り替えるとhistory.pushStateで履歴を追加します。その際、どのタブを開いたかはstateに入れることにしています。

このように、やはりクリックして何かが切り替わると、「戻る」などが使えるようになるのが自然です。それを実現するのがこのHistory APIです。

location

ところで、さらに手軽なのがハッシュです。ハッシュとは、

http://uhyohyo.net/index.html#abc

というような、「#」以降の部分です。これは一般的にページ名には含まれず、あるページ内での位置を表すなどするのでした。

JavaScriptでは、このハッシュを用いた操作をすることができます。それには、locationというオブジェクトを使います。

locationもわりと昔からあり、URLを扱うためのものです。例えば、

location.href

で現在のURLを取得できます。さらに、このlocation.hrefに代入すると、そのページへ移動することができます。

ここで、ハッシュの部分だけを変更するには、location.hashを使います。例えば先程の例だと、location.hashには"#abc"と入っています。これに代入して変更すると、pushState同様にURLが変わります。しかも、ちゃんと履歴にも入ります。履歴に入るということは、ページ移動しているわけでもありませんので、popStateが使えるということです。

さっきのタブの例を、pushStateではなくlocation.hashを用いるように変更したのがサンプル3です。

これは、pushStateの部分がlocation.hashへの代入になり、popStateイベントのほうもlocation.hashを調べています。

ここで注目すべきことは、popStateが呼び出された時点でlocationのURL情報は履歴移動後のものになっているということです。ですから、例えばpushStateの第三引数にURLを指定した場合、location.hrefを調べることでURLをもとにした処理ができます。

hashchangeイベント

ところで、popstateと似たイベントとして、ハッシュだけが変わった場合に発生するhashchangeイベントがあります。つまり、pushStateの第三引数によってURLも変わってしまった場合の履歴移動などでは発生しないイベントということです。

hashchangeイベントのイベントオブジェクトにはoldURLnewURLという2つのプロパティがあり、移動元・移動後のURLを両方得ることができます。ただし、先程述べたとおり、ハッシュ以外は変わっていません。

ということで、最後に、popstateの代わりにこのhashchangeイベントを利用したサンプル4を提示します。今回は、このoldURL・newURLは特に使う必要もないので使っていません。popstateのときと同様にlocation.hashを参照しています。

特筆すべきは、さっきのサンプル3では、location.hashに代入するときにopenTab関数を呼び出していたのに、サンプル4ではそれが消えているということです。

これはなぜかというと、hashchangeイベントが発生するのは「ハッシュが変わった場合」ですので、履歴移動により変わった場合はもちろんのこと、実は「JavaScript側によって変えられた場合」にも呼ばれるのです。ですから、location.hashに代入してハッシュが変わったので、その時点でhashchangeイベントが呼ばれているのです。

このようにhashchangeイベントを適切に使うと、今回の場合はopenTabを呼び出す箇所が一回だけに減るなど、すっきりしたコードが書けますので、うまく使い分けましょう。

locationのまとめ

今回ちらりと紹介したlocationオブジェクトについて以下にまとめます。


//プロパティ(すべて代入して変更可能です)
location.href;		//例: "http://uhyohyo.net/javascript/12_3.html?key=value#abc"
location.protocol;	//例: "http:"
location.host;		//例: "uhyohyo.net"
location.hostname;		//例: "uhyohyo.net"
location.port;		//例: ""
location.pathname;	//例: "/javascript/12_3.html"
location.search;	//例: "?key=value"
location.hash;		//例: "#abc"
        

protocolは、「:」まで入っていることに注意してください。また、portは、通信に使用するポートです。HTTP通信は普通80で省略されます。普通でないポート(例:http://example.com:8080/ )というときには、文字列で"8080"などと入ります。

hostとhostnameの違いは、このポートまで含めるかどうかです。hostnameは含めませんが、hostは含めます。

また、locationはlocation.assignというメソッドがあります。これは引数をひとつとってそのURLへ移動するメソッドで、location.hrefに直接代入するのとあまり変わりません。

また、location.replaceというメソッドがあって、これはassignと同様の使い方ですが現在のページの履歴情報を上書きするもので、これを使ってページを移動すると「戻る」を押しても移動前が履歴に残っておらず、さらにその一つ前に飛ぶことになります。

また、location.reloadは引数なしのメソッドで、現在のページを「更新」します。