画像のレスポンスコードが304の場合、onloadイベントが発生しない件と対策


以下のようなコードを書くと、画像が304を返した場合に、onloadイベントが発生しない

var img = new Image();
img.onload = function(){ console.log("loaded!"); }
img.src = "./01.jpg"; // 01.jpg が304を返すと、onloadが発生しない!!


当然と言えば当然なのですが、少しハマったので対策をメモ

先に対策


setIntervalで img.complete をチェックし続ける
img.completeは、画像が読み込み完了したら true を返す

  • キャッシュであれ、webから取ってくるのであれ、読み込みが終わったらtrueになるので、304でも読み込み完了を知ることができる

onloadと、img.completeの動き


MacのFirefox16, Safari5.1, chrome23で動作確認した結果はこんな感じ

  • onloadは200の時にのみイベントが発生
  • img.completeはなんであれ、サーバから結果を受け取ったらtrueがかえってくるっぽい
  • ブラウザのキャッシュの場合は未調査
    • 後でやる

サンプルコード


rack でこんなの書いて実験した

def self.call(env)
  data = nil
  File.open("./01.jpg", "rb") do |f|
    data = f.read
  end
  case env['PATH_INFO']
  when '/200.jpg'
    [200, {"Content-Type" => "image/jpeg"}, [data]]
  when '/304.jpg'
    [304, {}, [data]]
  when '/500.jpg'
    [500, {}, [data]]
  when '/404.jpg'
    [404, {"Content-Type" => "image/jpeg"}, []]
  when '/'
    [200, {"Content-Type" => "text/html"}, [(<<-EOS)]]
      <html>
      <head>
        <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
        <script>
          var img_check = function(path){
            var img = new Image();
            img.onload = function(){console.log(path + ":onload");}
            img.src = path;
            var id = setInterval(function(){
              console.log(path + ":img.complete:" + img.complete);
              if(img.complete){
                clearInterval(id);
              }
            }, 100);
          };
          img_check("/200.jpg"); // onloadイベントが発生して、complete:true
          img_check("/304.jpg"); // onloadイベントが発生しなくて、complete:true
          img_check("/500.jpg"); // onloadイベントが発生しなくて、complete:true
          img_check("/404.jpg"); // onloadイベントが発生しなくて、complete:true
        </script>
      </head>
      <body>
      </body>
      </html>
    EOS
  end
end
run self


そんな感じ

追記


onerrorも試してみたら、304もonerrorが発生してた