CSSとjQueryのreadyイベントのタイミング
ちょっとハマったので紹介しておきます。
症状
jQueryを使ってウェブページのグローバルナビゲーションをウィンドウに対して縦方向にセンタリングしようとしていたとき、ページのリロードを繰り返すと3回に1回ぐらいグローバルナビゲーションの位置が下にズレることがあった。
グローバルナビゲーションは position: fixed でウィンドウに対して固定し、スクロールしてもウィンドウをリサイズしても常に画面左側の中央にいるようにしていた。
センタリングのためのコードはこれ。
$.fn.vcenter = function() { var e = this; // documentのreadyイベント時とwindowがリサイズされたときに中央に調整する $(document).ready(center); $(window).resize(center); // メソッドチェーン用 return this; function center() { var innerHeight = $(e).height(), windowHeight = $(window).height(); $(e).css({"position": "fixed", "top": windowHeight / 2 - innerHeight / 2 }); }; }; // グローバルナビゲーションに対してvcenter()をする。 $("#global_navigation").vcenter();
これが、たまに下にズレる。うーむ困った(´・ω・`)
原因
原因はおそらく、グローバルナビゲーションに使用していたアイコン画像のせいだ。
グローバルナビゲーションのイメージ
このように同じ大きさの丸い画像を縦に並べてグローバルナビゲーションにしていたが、最初にセンタリングをするのは$(document).ready(center);のとき。実はこの時まだ画像は読み込まれていないので、javascript側からは画像のサイズがわからない。おそらく画像分の高さは0として、グローバルナビゲーションの高さが計算されることになる。するとその分グローバルナビゲーションの大きさは小さく見積もられ、上のinnerHeightは小さくなり、CSSのtopは大きくなってしまう。
程なく画像が読み込まれると、その分グローバルナビゲーションは本来の高さに伸び、結果的に下方向にズレてしまうというのが今回の推論だ。
対策
対応は2点
- 画像の高さを(外部)CSSで指定する
- CSSをjavascriptより前に読み込む。(正確には$(document).ready(handler);より前に)
一つ目はつまり「画像の高さが分からないならCSSで指定してしまえばいいわ」というわけで、以下のようにすればローラもオッケー☆
div#gnavi img.gnavi_image { height: 70px; margin: 10px; }
二つ目はjQueryのドキュメントに書いてあった。(つд⊂)
When using scripts that rely on the value of CSS style properties, it's important to reference external stylesheets or embed style elements before referencing the scripts.
[CSSで指定する値に依存するスクリプトを使うときは、外部スタイルシートや埋込みのstyle要素をscriptより前に書くのが大事である。]
.ready() – jQuery API
このこと、ググッて上位に出てくる日本語のレファレンスには書いてないんだよね。気を付けないと。
この2つを守ると、ふむ、たしかにズレがなくなった。
でも、疑問は残る。
なんでCSSへの参照をjavascriptより前に書くと良いのかが分からない。
埋め込みのstyle要素ならまだしも、外部スタイルシートでアイコン画像の高さを指定しているので、非同期のリクエストに対してのCSSが返るまでjavascriptは実行を待っているということだろうか?それとも単に早めにリクエストを投げたほうがjavascriptの実行まで間に合いやすいということだろうか?ちょっと良く分からない。
このへん調べたらまたポストしよう。