Solución:
Me golpeé la cabeza contra una pared hasta que descubrí lo que estaba pasando aquí.
Información de contexto
- Utilizando
.load()
no es posible si el iframe ya se ha cargado (el evento nunca se activará) - Utilizando
.ready()
en un elemento de iframe no es compatible (referencia) y llamará a la devolución de llamada inmediatamente incluso si el iframe aún no está cargado - Utilizando
postMessage
o una llamada a una función de contenedor enload
dentro del iframe solo es posible cuando se tiene control sobre él - Utilizando
$(window).load()
en el contenedor también esperaría a que se carguen otros activos, como imágenes y otros iframes. Esta no es una solución si solo desea esperar un iframe específico - Comprobación
readyState
en Chrome para un evento onload ya disparado no tiene sentido, ya que Chrome inicializa cada iframe con una página vacía “about: blank”. losreadyState
de esta página puede sercomplete
, pero no es elreadyState
de la página que esperassrc
atributo).
Solución
Es necesario lo siguiente:
- Si el iframe aún no está cargado podemos observar el
.load()
evento - Si el iframe ya se ha cargado, debemos verificar el
readyState
- Si el
readyState
escomplete
, normalmente podemos asumir que el iframe ya se ha cargado. Sin embargo, debido al comportamiento mencionado anteriormente de Chrome, además debemos verificar si es elreadyState
de una página vacía - Si es así, debemos observar el
readyState
en un intervalo para comprobar si el documento real (relacionado con el atributo src) escomplete
He resuelto esto con la siguiente función. Ha sido (transpilado a ES5) probado con éxito en
- Cromo 49
- Safari 5
- Firefox 45
- Es decir, 8, 9, 10, 11
- Borde 24
- iOS 8.0 (“Safari Mobile”)
- Android 4.0 (“Navegador”)
Función tomada de 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();
}
};
Ejemplo de trabajo
Consta de dos archivos (index.html e 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>
También puede cambiar el src
atributo dentro index.html
por ejemplo, “http://example.com/”. Solo juega con eso.
Usaría postMessage. El iframe puede asignar su propio evento de carga y publicarlo en el padre. Si hay problemas de tiempo, asegúrese de asignar el controlador postMessage del padre antes de crear el iframe.
Para que esto funcione, el iframe debe conocer la URL del padre, por ejemplo, pasando un parámetro GET al iframe.
¡Haz clic para puntuar esta entrada!
(Votos: 0 Promedio: 0)