43
42

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

History.js ではまったこと

Last updated at Posted at 2013-04-15

History.js は,ブラウザヒストリ・ステート用 API (pushState 等) のブラウザ間非互換性を解消したり,(IE 9 以下など) pushState に対応していないブラウザでも hash を利用して擬似的に state の遷移ができるようにしたり,と便利なライブラリです。

ですが,いろいろとはまったことがありました。

IE (等,エミュレーション実装) で戻った時にステートが反映されない

まず,以下のような状況で困りました。

  1. History.js を利用したページ (A) をロード
  2. pushState(B)
  3. pushState(C)
  4. リンク等により History.js を利用していないページ (D) に遷移
  5. ブラウザの「戻る」ボタン等で戻る

正しい挙動はステート C になること (で,もちろん Chrome や Firefox ではそうなる) なのですが,IE 9 で D から戻ると A になってしまいます。

そこからさらに戻ると,きちんと?ステート B になります。

原因

この原因は,つぎのようなコーディングをしていたことにありました。

$(document).ready(function () {
    $(window).on('statechange', function (ev) {
        // popState なイベントハンドラ
    });
});

DOM ready な状況で statechange イベントをハンドリングしようとしても,(別画面からの) 「戻る」遷移では発火されないのですね。

このようにする必要があります。

// DOM ready ではなく,スクリプト読み込み時に,バインドを行う
$(window).on('statechange', function (ev) {
    // popState なイベントハンドラ
});

追記 2013-04-19 10:30
正確には DOM ready で statechange が発火されるんですが, History.js (の HTML4 用モジュール) を先に読み込んでいると,そいつの DOM ready handler より後で statechange handler を登録しても間に合わない,ということみたいです。
ですので, History.js をよりあとで読みこむようにすると,後述するような面倒なコーディングをしなくていいのかも。

statechange ハンドラで DOM をいじりたい場合もあるでしょうが (というかほとんどの場合?),この場合は,statechange ハンドラではフラグ立てだけしておいて,DOM ready イベント時に (も),擬似的にハンドラを呼ぶ必要があります。

// わかりやすいようにあえてフラットに書いてます

var dom_ready           = false,
    initial_statechange = false,
    initial_state;

$(window).on('statechange', function (ev) {
    if (dom_ready) {
        on_pop_state(History.getState());
    } else {
        // DOM ready 前
        initial_state       = History.getState();
        initial_statechange = true;
    }
});

$(document).ready(function (ev) {
    dom_ready = true;

    if (initial_statechange) {
        initial_statechange = false;
        on_pop_state(initial_state);
    }
});

function on_pop_state(state) {
    // ......
}

おまけ ($(document).ready(fn) について)

jQuery で DOM ready イベントハンドラはまぁ普通に

$(function () {
    // ......
});

って書けばいいんですが,なんとなく抵抗感があって,

$(document).ready(function () {
    // ......
});

という形式で書いています。

以前は (他のイベントハンドラと形式をあわせて)

$(document).on('ready', function () {
    // ......
});

のように書いていたんですが,これ jQuery 1.8 以降では obsolete 扱いとなっていて,このように書いてはいけません。

どのように困るかというと,たとえば DOM ready イベントハンドラからさらに DOM ready イベントハンドラを (この obsolete 形式で) 登録しようとした場合 (わざとそうしようとしなくても,結果的にそうなることはよくあります), その2つ目のハンドラが呼ばれません (正しい形式で書くときちんと即座に呼ばれます)。くわしくは .ready() | jQuery API Documentation を参照してください。

エンコードされた URL が文字化けする

たとえば,

//         http://example.com/日本語
var url = 'http://example.com/%e6%97%a5%e6%9c%ac%e8%aa%9e';
History.pushState(null, null, url);

のようにすると,statechange イベントでうけとったステートオブジェクトの url プロパティが http://example.com/%e6%97%a5%e6%9c%ac%e8%aa%9e ではなく http://example.com/日本語 になります。

で,IE でこの URL にアクセスしようとするとなぜかそこから CP932 にエンコードされたりして,もともとアクセスしたかった URL とは異なる URL になってしまいます。

これ,どうも History.js が無駄にアグレッシブに unescape() しまくるからみたいなんですね。なので,手元のプロダクトではとりあえず,こんな感じにしています。

var url = 'http://example.com/%e6%97%a5%e6%9c%ac%e8%aa%9e';
History.pushState(null, null, encodeURIComponent(url));

これたまたま今手元の環境で動いているだけで,どんな環境でも動くとは限らないです。

(今考えたら State data オブジェクトのほうに入れたらいいんじゃないかなと思いますが,前述のブラウザで別画面から戻る場合に問題が発生しそうですね)

このバグは GitHub issues でも何回か討議されてたりしますが, History.js にきちんと手を入れないといけない部分だと思います。

追記 2013-04-16 16:30

だいたいこんな感じのパッチをあてればいい感じみたいです。
https://github.com/dayflower/history.js/commit/4c60734bf5d7ec41016d204f7f9e479205ea5ad7

Chrome 25, Firefox 20, IE 9 あたりでは確認済。
IE 9 だと非 latin な URL の場合,URL ハッシュが冗長になった気もしますが。

同じような内容のプルリクがもうあがっているので自分から Pull Request を送ったりはしません。ちょっと否定的?なコメントもあがってたりしたのであれがマージされるかはあやしいですが。Fork が多いのはみんな手元で直して使っているのかな?

43
42
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
43
42

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?