jQuery - $(function(){}); の落とし穴

先日、いつものように Web ページで jQuery プログラムを書いててちょっとハマったのでメモ的に失敗談を開陳(恥)。

$(function(){}) は画像のロードを待たない?

きっかけは U/I 系の jQuery プラグイン jScrollPane が期待どおりに動作しないという現象でした。このプラグイン、固定サイズのブロック要素が overflow した際のスクロールバーをミニマルなデザインに差し替えてくれる優れものです。現象とは、そのブロック要素に画像が含まれている場合にスクロールバーが表示されない、というもの。しかも ChromeSafari の WebKib 系ブラウザに限って。

調べて判ったことを纏めると以下のようになります。

  • 普段深く考えもせずおまじない的に書いてた $(function(){}) は、$(document).ready(function(){}) の短縮形であり、DOMContentLoaded という DOM 構築完了イベントへのハンドラ記述法である。
  • WebKit 系ブラウザでは DOMContentLoaded 発火の時点で画像のロードが完了しておらず(バグ?)、img タグに width や height 属性を明示しない限り画像のロードが完了するまで幅や高さ等が JavaScript で取得できない。
  • したがって画像を含むブロック内部の高さも算出することができず、画像抜きの高さとなってしまう。この理由により、jScrollPane が当該ブロックで overflow しないものと解釈してしまった。
  • これを回避するには DOMContentLoaded イベントではなく、画像のロードが全て完了した後で発火される window.onload を用いるといいみたい。

イベントは適材適所で使い分けましょうw ケースによっては window.onload を

ということで、万全を期すのであれば window.onload を常用すればいいのでしょうが、そうなると JavaScript 処理がとてももたついた動作になってしまう。なので、通常は $(function(){...}); で。今回のケースのように画像等の埋め込み要素をストリクトに操作したいような場合には window.onload を用いれば良い、という教訓というかバッドノウハウでした。この場合、JavaScript の記述的には

window.onload = function(){...};

でもいいのですが、これだと既存のイベントプロシジャをオーバーロードしてしまう危険性もあるので、イベントプロシジャを追加する書き方として以下のようにすれば良いみたいです(jQuery ってホントに良く出来てるなぁ)。

jQuery.event.add(window,"load",function(){...});

おまけ(デモ)

本件を再現するデモを作りました。

http://mariyudu.net/labo/jqueryEventDemo

このページには、画像を含んだブロック要素が左右二つあり、両方とも

height: 200px;
overflow: hidden;

とスタイル定義してあります。左側は $(document).ready() で、右側は window.onload 時に jScrollPane を適用するようにしてあります。これを ChromeSafari で表示させると、左のブロックにはスクロールバーが適用されずに下半分くらいが隠れたままなのが分かりますね。