JavaScript で、画像本来のサイズ(幅, 高さ)を取得する方法
Opera で DOM Mutation Event を使用するコードを追記しました。
まとめを追加しました。
Opera パート2に取得できないケースが見つかったため、パート3 を追加しました。
rhino.jpg(幅:300px, 高さ:227px) を、
と、100 x 75 で表示している場合を例に、画像本来のサイズを取得する方法をご紹介します。
Firefox, Safari, Google Chrome なら
image.naturalWidth と image.naturalHeight を利用します。
image.naturalWidth と image.naturalHeight の初期値は 0 です。画像の読み込みが完了した時点で適切な値に更新されます。
<html><head><title></title></head><body> <img id="rhino" src="img/rhino.jpg" width="100" height="75" /> <script> window.onnload = function() { alert(rhino.width + " " + rhino.height); // "100 75" alert(rhino.naturalWidth + " " + rhino.naturalHeight); // "300 227" } </script></body></html>
IE なら
runtimeStyle を利用します。
runtimeStyle は CSS の !important をプログラマブルにエミュレートする機構です。
runtimeStyle.width と runtimeStyle.height を一時退避し、"auto" で上書することで本来のサイズを取得しています。
"auto" の代わりに "" でもよさそうに見えますが、"" だと期待した結果は得られません。
ページのリフローは発生しません(ガタガタ動きません)。
<html><head><title></title></head><body> <img id="rhino" src="img/rhino.jpg" width="100" height="75" /> <input type="button" value="revalidate" onclick="boot()" /> <script> function getActualDimension1(image) { var w, h, key = "actual", run, mem; if (image[key] && image[key].src === image.src) { return image[key]; } run = image.runtimeStyle; mem = { w: run.width, h: run.height }; // keep runtimeStyle run.width = "auto"; // override run.height = "auto"; w = image.width; h = image.height; run.width = mem.w; // restore run.height = mem.h; return image[key] = { width: w, height: h, src: image.src }; // bond } function boot() { var actual = getActualDimension1(rhino); alert(rhino.width + " " + rhino.height); // "100 75" alert(actual.width + " " + actual.height); // "300 227" } window.onnload = boot; </script></body></html>
Opera なら
Opera には、runtimeStyle が無く(currentStyle はある)、image.naturalWidth もないため、非同期で処理します。
img.onnload = ""; や img = void 0; は IE で発生するメモリリークを回避するためのコードです。Opera では顕著なメモリリークは発生しないようですが、このコード自体は Opera 以外のブラウザにも流用可能なため、一応やってます。
<html><head><title></title></head><body> <img id="rhino" src="img/rhino.jpg" width="100" height="75" /> <input type="button" value="revalidate" /> <script> function getActualDimension2(image, callback) { var img, delayLoader; delayLoader = function() { var actual = { width: img.width, height: img.height }; img.onnload = ""; img = void 0; // free obj callback(actual); }; img = new Image(); img.onnload = delayLoader; img.src = image.src; } function boot() { var fn = function(actual) { alert(rhino.width + " " + rhino.height); // "100 75" alert(actual.width + " " + actual.height); // "300 227" }; getActualDimension2(rhino, fn); } window.onnload = boot; </script></body></html>
Opera なら(パート2)
addEventListener("DOMAttrModified") で同期処理が可能です(id:edvakf さんアドバイス感謝です)
removeAttribute("width", "height"); がポイントです。rhino.width = なにか; のようにwidth, height に代入するやり方だとダメです。
ページのリフローは発生しません(ガタガタ動きません)。
パート2は、以下のように height が明示されていないHTMLでイベントが発生しないという弱点がありました。パート3 をご覧下さい。
<html><head><title></title></head><body> <img id="rhino" src="img/rhino.jpg" width="100" height="75" /> <input type="button" value="revalidate" onclick="boot()" /> <script> function getActualDimension3(image) { var w = 0, h = 0, mem, key = "actual"; if (image[key] && image[key].src === image.src) { return image[key]; } function fn() { w = image.width; h = image.height; } mem = { w: image.width, h: image.height }; image.removeAttribute("width"); image.addEventListener("DOMAttrModified", fn, false); image.removeAttribute("height"); // call fn image.removeEventListener("DOMAttrModified", fn, false); image.width = mem.w; image.height = mem.h; return image[key] = { width: w, height: h, src: image.src }; // bond } function boot() { var actual = getActualDimension3(rhino); alert(rhino.width + " " + rhino.height); // "100 75" alert(actual.width + " " + actual.height); // "300 227" } window.onnload = boot; </script></body></html>
Opera なら(パート3)
パート2 に取得できないケース(穴)が見つかったため、もう一度考え直してみました。
ものすごく簡単になりましたが、このようなコードでもページのリフローは発生しないようです(リフローが発生するようでしたら教えてください)。
<html><head><title></title></head><body> <img id="rhino" src="img/rhino.jpg" width="100" height="75" /> <input type="button" value="revalidate" onclick="boot()" /> <script> function getActualDimension3(image) { var w, h, mem, key = "actual"; if (image[key] && image[key].src === image.src) { return image[key]; } mem = { w: image.width, h: image.height }; image.removeAttribute("width"); image.removeAttribute("height"); w = image.width; h = image.height; image.width = mem.w; image.height = mem.h; return image[key] = { width: w, height: h, src: image.src }; // bond } function boot() { var actual = getActualDimension3(rhino); alert(rhino.width + " " + rhino.height); // "100 75" alert(actual.width + " " + actual.height); // "300 227" } window.onnload = boot; </script></body></html>
まめちしき
今回は利用していませんが、IE の runtimeStyle に似た document.getOverrideStyle が、DOM に存在します。
DOM: http://sdc.sun.co.jp/java/docs/j2se/1.4/ja/docs/ja/guide/plugin/dom/org/w3c/dom/css/DocumentCSS.html
var css = document.getOverrideStyle(element, pseudoElement);参考: http://archives.devshed.com/forums/standards-105/document-getoverridestyle-2200574.html
Safari や Google Chrome でメソッドが実装されているようです。Opera ではエラーになります。
さがしてます
Opera で、image.naturalWidth や、runtimeStyle に代わる方法をご存知の方は教えてください。
# 予め画面外に <img id="hidden_rhino" src="rhino.jpg"> を設置しておいて、hidden_rhino からサイズを取得する方法以外で
あわせて読みたい
- id:inamenai さんが同様のテーマについて書かれています: http://d.hatena.ne.jp/inamenai/20081011/p1
- runtimeStyle について: http://d.hatena.ne.jp/uupaa/20080628/1214645293
- 完全に状況を掌握した画像の遅延読み込みの実現 - latest log
まとめ
まとめるとこうなります。こちらで実際に試せます
Firefox2+, Google Chrome1+, Safari3+, Opera9.27+, IE6+ で動作を確認しました。
<html><head><title></title></head><body> <img id="rhino1" src="img/rhino.jpg" width="100" height="75" /> <img id="rhino2" src="img/rhino.jpg" width="100" /> <img id="rhino3" src="img/rhino.jpg" height="75" /> <input type="button" value="revalidate" onclick="boot()" /> <div id="log"></div> <script> /* LICENSE: MIT * AUTHOR: uupaa.js@gmail.com */ function getActualDimension(image) { var run, mem, w, h, key = "actual"; // for Firefox, Safari, Google Chrome if ("naturalWidth" in image) { return { width: image.naturalWidth, height: image.naturalHeight }; } if ("src" in image) { // HTMLImageElement if (image[key] && image[key].src === image.src) { return image[key]; } if (document.uniqueID) { // for IE run = image.runtimeStyle; mem = { w: run.width, h: run.height }; // keep runtimeStyle run.width = "auto"; // override run.height = "auto"; w = image.width; h = image.height; run.width = mem.w; // restore run.height = mem.h; } else { // for Opera and Other /* function fn() { w = image.width; h = image.height; } mem = { w: image.width, h: image.height }; // keep current style image.removeAttribute("width"); image.addEventListener("DOMAttrModified", fn, false); image.removeAttribute("height"); // call fn image.removeEventListener("DOMAttrModified", fn, false); image.width = mem.w; // restore image.height = mem.h; */ mem = { w: image.width, h: image.height }; // keep current style image.removeAttribute("width"); image.removeAttribute("height"); w = image.width; h = image.height; image.width = mem.w; // restore image.height = mem.h; } return image[key] = { width: w, height: h, src: image.src }; // bond } // HTMLCanvasElement return { width: image.width, height: image.height }; } function boot() { var actual1 = getActualDimension(rhino1), actual2 = getActualDimension(rhino2), actual3 = getActualDimension(rhino3); /* alert(rhino1.width + " " + rhino1.height); // "100 75" alert(actual1.width + " " + actual1.height); // "300 227" */ log.innerHTML += [ [rhino1.width, rhino1.height, actual1.width, actual1.height].join(", "), [rhino2.width, rhino2.height, actual2.width, actual2.height].join(", "), [rhino3.width, rhino3.height, actual3.width, actual3.height].join(", ") ].join(" / ") + "<br />"; } window.onnload = boot; </script></body></html>