ここで何が起こっているのかがわかるまで、頭を壁にぶつけました。
背景情報
.load()
iframe が既に読み込まれている場合は使用できません (イベントは発生しません)。
- iframe 要素での使用
.ready()
はサポートされておらず (参照)、iframe がまだ読み込まれていない場合でも、すぐにコールバックが呼び出されます。
- iframe 内でのコンテナー関数の使用
postMessage
または呼び出しは、load
それを制御できる場合にのみ可能です。
- コンテナーでを使用
$(window).load()
すると、画像や他の iframe などの他のアセットが読み込まれるまで待機します。特定の iframe だけを待ちたい場合、これは解決策ではありません
readyState
Chrome はすべての iframe を「about:blank」の空のページで初期化するため、既に発生した onload イベントを Chrome でチェックしても意味がありません。このreadyState
ページの は である可能性がありますが、期待するページcomplete
の ではありませんreadyState
(src
属性)。
解決
以下が必要です。
- iframe がまだロードされていない場合は、
.load()
イベントを観察できます
- iframe がすでに読み込まれている場合は、
readyState
readyState
がの場合、complete
通常は iframe が既に読み込まれていると見なすことができます。ただし、上記の Chrome の動作のためreadyState
、空のページかどうかをさらに確認する必要があります。
- もしそう
readyState
なら、実際のドキュメント (src 属性に関連する) がcomplete
私はこれを次の関数で解決しました。これは (ES5 にトランスパイルされ) で正常にテストされています。
- クロム 49
- サファリ5
- Firefox 45
- IE 8、9、10、11
- エッジ 24
- iOS 8.0 (「Safari モバイル」)
- Android 4.0 (「ブラウザー」)
jquery.markから取得した関数
/**
* Will wait for an iframe to be ready
* for DOM manipulation. Just listening for
* the load event will only work if the iframe
* is not already loaded. If so, it is necessary
* to observe the readyState. The issue here is
* that Chrome will initialize iframes with
* "about:blank" and set its readyState to complete.
* So it is furthermore necessary to check if it's
* the readyState of the target document property.
* Errors that may occur when trying to access the iframe
* (Same-Origin-Policy) will be catched and the error
* function will be called.
* @param {jquery} $i - The jQuery iframe element
* @param {function} successFn - The callback on success. Will
* receive the jQuery contents of the iframe as a parameter
* @param {function} errorFn - The callback on error
*/
var onIframeReady = function($i, successFn, errorFn) {
try {
const iCon = $i.first()[0].contentWindow,
bl = "about:blank",
compl = "complete";
const callCallback = () => {
try {
const $con = $i.contents();
if($con.length === 0) { // https://git.io/vV8yU
throw new Error("iframe inaccessible");
}
successFn($con);
} catch(e) { // accessing contents failed
errorFn();
}
};
const observeOnload = () => {
$i.on("load.jqueryMark", () => {
try {
const src = $i.attr("src").trim(),
href = iCon.location.href;
if(href !== bl || src === bl || src === "") {
$i.off("load.jqueryMark");
callCallback();
}
} catch(e) {
errorFn();
}
});
};
if(iCon.document.readyState === compl) {
const src = $i.attr("src").trim(),
href = iCon.location.href;
if(href === bl && src !== bl && src !== "") {
observeOnload();
} else {
callCallback();
}
} else {
observeOnload();
}
} catch(e) { // accessing contentWindow failed
errorFn();
}
};
実施例
2 つのファイル (index.html と iframe.html) で構成されています:
index.html :
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<title>Parent</title>
</head>
<body>
<script src="https://code.jquery.com/jquery-1.12.2.min.js"></script>
<script>
$(function() {
/**
* Will wait for an iframe to be ready
* for DOM manipulation. Just listening for
* the load event will only work if the iframe
* is not already loaded. If so, it is necessary
* to observe the readyState. The issue here is
* that Chrome will initialize iframes with
* "about:blank" and set its readyState to complete.
* So it is furthermore necessary to check if it's
* the readyState of the target document property.
* Errors that may occur when trying to access the iframe
* (Same-Origin-Policy) will be catched and the error
* function will be called.
* @param {jquery} $i - The jQuery iframe element
* @param {function} successFn - The callback on success. Will
* receive the jQuery contents of the iframe as a parameter
* @param {function} errorFn - The callback on error
*/
var onIframeReady = function($i, successFn, errorFn) {
try {
const iCon = $i.first()[0].contentWindow,
bl = "about:blank",
compl = "complete";
const callCallback = () => {
try {
const $con = $i.contents();
if($con.length === 0) { // https://git.io/vV8yU
throw new Error("iframe inaccessible");
}
successFn($con);
} catch(e) { // accessing contents failed
errorFn();
}
};
const observeOnload = () => {
$i.on("load.jqueryMark", () => {
try {
const src = $i.attr("src").trim(),
href = iCon.location.href;
if(href !== bl || src === bl || src === "") {
$i.off("load.jqueryMark");
callCallback();
}
} catch(e) {
errorFn();
}
});
};
if(iCon.document.readyState === compl) {
const src = $i.attr("src").trim(),
href = iCon.location.href;
if(href === bl && src !== bl && src !== "") {
observeOnload();
} else {
callCallback();
}
} else {
observeOnload();
}
} catch(e) { // accessing contentWindow failed
errorFn();
}
};
var $iframe = $("iframe");
onIframeReady($iframe, function($contents) {
console.log("Ready to got");
console.log($contents.find("*"));
}, function() {
console.log("Can not access iframe");
});
});
</script>
<iframe src="iframe.html"></iframe>
</body>
</html>
iframe.html :
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<title>Child</title>
</head>
<body>
<p>Lorem ipsum</p>
</body>
</html>
src
内部の属性index.html
を「http://example.com/ 」などに変更することもできます。遊んでみてください。