Ответ 1
Я ударился головой о стену, пока не узнал, что происходит здесь.
Фоновая информация
- Использование
.load()
невозможно, если iframe уже загружен (событие никогда не срабатывает) - Использование
.ready()
в элементе iframe не поддерживается (ссылка) и вызовет обратный вызов сразу, даже если iframe не является загружен еще - Использование
postMessage
или вызов функции контейнера наload
внутри iframe возможен только при наличии контроля над ним - Использование
$(window).load()
в контейнере также будет ждать загрузки других ресурсов, таких как изображения и другие фреймы. Это не решение, если вы хотите подождать только для определенного iframe - Проверка
readyState
в Chrome для события onload, запущенного alredy, не имеет смысла, поскольку Chrome инициализирует каждый iframe пустой страницей "about: blank".readyState
этой страницы может бытьcomplete
, но это неreadyState
ожидаемой страницы (атрибутsrc
).
Решение
Необходимо следующее:
- Если iframe еще не загружен, мы можем наблюдать событие
.load()
- Если iframe уже загружен, нам нужно проверить
readyState
- Если
readyState
-complete
, мы можем предположить, что iframe уже загружен. Однако из-за вышеупомянутого поведения Chrome мы также должны проверить, есть лиreadyState
пустой страницы - Если это так, нам нужно наблюдать
readyState
в интервале, чтобы проверить, действительно ли фактический документ (относящийся к атрибуту src)complete
Я решил это со следующей функцией. Он был (преобразован в ES5) успешно протестирован в
- Chrome 49
- Safari 5
- Firefox 45
- IE 8, 9, 10, 11
- Edge 24
- iOS 8.0 ( "Safari Mobile" )
- 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();
}
};
Рабочий пример
Состоит из двух файлов (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/". Просто поиграйте с ним.