Saltar al contenido

Cómo detectar cuando ya se ha cargado un iframe

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 en load 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”. los readyState de esta página puede ser complete, pero no es el readyState de la página que esperassrc atributo).

Solución

Es necesario lo siguiente:

  1. Si el iframe aún no está cargado podemos observar el .load() evento
  2. Si el iframe ya se ha cargado, debemos verificar el readyState
  3. Si el readyState es complete, 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 el readyState de una página vacía
  4. Si es así, debemos observar el readyState en un intervalo para comprobar si el documento real (relacionado con el atributo src) es complete

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)



Utiliza Nuestro Buscador

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *