Invocar código JavaScript en un iframe desde la página principal

642

Básicamente, tengo un iframeincrustado en una página y iframetiene algunas rutinas de JavaScript que necesito invocar desde la página principal.

Ahora, lo contrario es bastante simple, ya que solo necesita llamar parent.functionName(), pero desafortunadamente, necesito exactamente lo contrario.

Tenga en cuenta que mi problema no es cambiar la URL de origen del iframe, sino invocar una función definida en el iframe.

1
  • 1
    Tenga en cuenta que al depurar puede hacer cosas como window.location.href o parent.location.href para ver la URL del iframe, si desea verificar que tiene una referencia al iframe que está buscando. AaronLS 21/0313 a las 19:57
574

Suponga que la identificación de su iFrame es "targetFrame" y la función que desea llamar es targetFunction():

document.getElementById('targetFrame').contentWindow.targetFunction();

También puede acceder al marco usando en window.frameslugar de document.getElementById.

// this option does not work in most of latest versions of chrome and Firefox
window.frames[0].frameElement.contentWindow.targetFunction(); 
14
  • 3
    funciona en FF 7, Chrome 12, IE 8/9 y Safari (no estoy seguro de esta versión)Darcy 2/11/11 a las 19:38
  • 3
    Esta solución no funciona para mi dispositivo, aquí está mi código document.getElementById('remote_iframe_0').contentWindow.my.create_element_gadg‌​et('verify_user');"remote_iframe_0 creado programáticamente por un servidor apache shindig pero window.parent.document.getElementById('remote_iframe_0').contentWindow.my.create_element_gadg‌​et('verify_user');"funcionaJ Bourne 10 de enero de 2012 a las 11:30
  • 3
    @Dirty Henry, ¿qué quieres decir con "una función jQuery?" jQuery es una biblioteca de JavaScript. Joel Anair 1 de julio de 2013 a las 13:11
  • 13
    @JellicleCat Eso es porque la página principal y el iframe tienen hosts diferentes, lo que lo convierte en un marco de origen cruzado, como le indica el mensaje. Siempre que el protocolo, el dominio y el puerto de la página principal y el iframe coincidan, todo funcionará bien. Mark Amery 14/10/2014 a las 20:38
  • 1
    Agregué un ejemplo sobre cómo usar el método window.frames mencionado. Elijah Lynn 25 feb.15 a las 18:43
153

Hay algunas peculiaridades a tener en cuenta aquí.

  1. HTMLIFrameElement.contentWindowes probablemente la forma más fácil, pero no es una propiedad estándar y algunos navegadores no la admiten, sobre todo los antiguos. Esto se debe a que el estándar HTML DOM Nivel 1 no tiene nada que decir sobre el windowobjeto.

  2. También puede intentarlo HTMLIFrameElement.contentDocument.defaultView, lo que permiten algunos navegadores antiguos, pero IE no. Aun así, el estándar no dice explícitamente que recupere el windowobjeto, por la misma razón que (1), pero puede elegir algunas versiones adicionales del navegador aquí si lo desea.

  3. window.frames['name']devolver la ventana es la interfaz más antigua y, por tanto, la más fiable. Pero luego debe usar un name="..."atributo para poder obtener un marco por nombre, que es un poco feo / obsoleto / transitorio. ( id="..."sería mejor, pero a IE no le gusta eso).

  4. window.frames[number]también es muy confiable, pero conocer el índice correcto es el truco. Puede salirse con la suya, por ejemplo. si sabe que solo tiene un iframe en la página.

  5. Es muy posible que el iframe secundario aún no se haya cargado, o que algo más haya fallado para hacerlo inaccesible. Puede que le resulte más fácil revertir el flujo de comunicaciones: es decir, hacer que el iframe secundario notifique a su window.parentscript cuando haya terminado de cargarse y esté listo para que se le devuelva la llamada. Al pasar uno de sus propios objetos (por ejemplo, una función de devolución de llamada) al script principal, ese padre puede comunicarse directamente con el script en el iframe sin tener que preocuparse por el HTMLIFrameElement con el que está asociado.

4
  • 13
    ¡Buena cosa! Especialmente su sugerencia 5. ha demostrado ser extremadamente valiosa. Me resolvió muchos dolores de cabeza al intentar acceder a las funciones de iFrame desde el padre en Chrome. Pasar el objeto de función desde el iFrame hizo que se ejecutara como un encanto en Chrome, FF e incluso IE de 9 a 7 y también facilitó la verificación de si la función ya se había cargado. ¡Gracias! Jpsy 24/11/11 a las 7:10
  • El número 3 funciona, pero mejor si lo haces como @le dorfier lo puso en su comentario. Fíjate en la methode()pieza. ErickBest 5 de septiembre de 2013 a las 10:54
  • Tenga en cuenta también que, debido a las restricciones de seguridad en los navegadores modernos (probado FF29 y Chrome34), es muy importante dónde realiza la llamada de función. Simplemente llamar a la función desde una etiqueta de script o $ (document) .ready () no funcionará. En su lugar, coloque la llamada dentro de la etiqueta onload del cuerpo, o en un ancla <a href="javascript:doit();"> hágalo </a> como se sugiere en otra parte (consulte stackoverflow.com/questions/921387/… ). Pasar la llamada de función a un script como devolución de llamada también suele funcionar. titusn 13 de mayo de 2014 a las 10:23
  • @chovy: No, vea la aclaración en la respuesta de Vivek sobre esto. bobince 28/10/2014 a las 16:40
69

Es posible llamar a una función JS principal desde iframe, pero solo cuando tanto la página principal como la página cargada en el iframeson del mismo dominio, es decir, abc.com, y ambos están usando el mismo protocolo, es decir, ambos están en http://o https://.

La llamada fallará en los casos mencionados a continuación:

  1. La página principal y la página de iframe son de un dominio diferente.
  2. Están usando diferentes protocolos, uno está en http: // y el otro está en https: //.

Cualquier solución a esta restricción sería extremadamente insegura.

Por ejemplo, imagina que registré el dominio superwinningcontest.com y envié enlaces a los correos electrónicos de las personas. Cuando cargaron la página principal, pude ocultar algunos iframemensajes de correo electrónico allí y leer su feed de Facebook, verificar las transacciones recientes de Amazon o PayPal o, si usaron un servicio que no implementó la seguridad suficiente, transferir dinero de su cuentas. Es por eso que JavaScript está limitado al mismo dominio y al mismo protocolo.

3
  • 6
    La solución es utilizar el hermoso y peligroso en.wikipedia.org/wiki/Cross-document_messagingLaramie 8 de enero de 2013 a las 7:16
  • ¿Alguien sabe si un subdominio diferente hará que esto falle? Tengo dificultades para depurar un problema que creo que está relacionado con esto: el iframe está llamando a una función de ventana principal, pero la llamada no está sucediendo. Jake 19/08/2013 a las 21:55
  • 1
    Tenga en cuenta que también fallaría para diferentes números de puerto. es decir, abc.com:80! = abc.com:8080prasanthv 1 dic 2015 a las 17:17
36

En el IFRAME, haga pública su función para el objeto de ventana:

window.myFunction = function(args) {
   doStuff();
}

Para acceder desde la página principal, use esto:

var iframe = document.getElementById("iframeId");
iframe.contentWindow.myFunction(args);
10
  • Gran respuesta. No estoy seguro de la convención aquí. Tengo una pregunta de seguimiento. Si debo comenzar una nueva pregunta, lo haré. Tengo un problema. El iframe de mi página se completa dinámicamente. Contiene un botón cuyo onclick()evento simplemente llama window.print(). Esto imprime el contenido del iframeperfectamente. Pero cuando la función pública de la página del iframe llama window.print()y si la página principal llama a la función pública, se imprime el contenido de la principal. ¿Cómo debo codificar esto para que la llamada a window.print()en la función pública se comporte como lo hace en el onclickevento? Karl 1 de agosto de 2012 a las 20:43
  • 1
    @Karl No quieres llamar onclick. Quieres llamar iframe.contentWindow.print(). Tomalak 1 de agosto de 2012 a las 21:15
  • Desafortunadamente, eso no funciona. ¿Quizás debería comenzar una nueva pregunta y documentar mi código? De cualquier manera, el contenido de la página principal también se imprime. document.getElementById('myFrame').contentWindow.print();' and the pubic function printContent () `(no el controlador de eventos oncick) cuyas llamadas window.print()se llamaron de esta manera:document.getElementById('myFrame').contentWindow.printContent();Karl 1 de agosto de 2012 a las 21:41
  • 1
    @Karl - Sí, entonces es mejor comenzar una nueva pregunta. A menos que la ventana principal esté en un dominio diferente al del iframe. Entonces nada de lo que intente funcionará jamás . Tomalak 2 de agosto de 2012 a las 2:01
  • Lo que informé aquí parece ser un problema de IE (prueba en IE8). No hay problema en la versión actual de Chrome. Sin embargo, todavía tengo que probar otros navegadores. Para aquellos interesados, vea este jsFiddle (creo que este es un buen ejemplo de carga dinámica de iframe y llamada a una función pública desde el padre)Karl 2 de agosto de 2012 a las 2:43
23

Si el destino del iFrame y el documento que lo contiene están en un dominio diferente, es posible que los métodos publicados anteriormente no funcionen, pero hay una solución:

Por ejemplo, si el documento A contiene un elemento iframe que contiene el documento B, y el script en el documento A llama a postMessage () en el objeto Ventana del documento B, entonces se disparará un evento de mensaje en ese objeto, marcado como originado en la Ventana de documento A. La secuencia de comandos en el documento A podría verse así:

var o = document.getElementsByTagName('iframe')[0];
o.contentWindow.postMessage('Hello world', 'http://b.example.org/');

Para registrar un controlador de eventos para eventos entrantes, el script usaría addEventListener () (o mecanismos similares). Por ejemplo, la secuencia de comandos en el documento B podría tener este aspecto:

window.addEventListener('message', receiver, false);
function receiver(e) {
  if (e.origin == 'http://example.com') {
    if (e.data == 'Hello world') {
      e.source.postMessage('Hello', e.origin);
    } else {
      alert(e.data);
    }
  }
}

Este script primero verifica que el dominio sea el dominio esperado y luego mira el mensaje, que muestra al usuario o responde enviando un mensaje al documento que envió el mensaje en primer lugar.

a través de http://dev.w3.org/html5/postmsg/#web-messaging

2
  • 1
    easyXDM proporciona una abstracción sobre PostMessage para navegadores más antiguos (incluido un respaldo de Flash (LocalConnection) para los dinosaurios obstinados). JonnyReeves 6/03/12 a las 9:55
  • 1
    eso es genial ... después de probar las respuestas anteriores finalmente obtuve tu respuesta, ¡gracias! vkGunasekaran 5 mar.20 a las 8:33
9

Solo para que conste, me encontré con el mismo problema hoy, pero esta vez la página estaba incrustada en un objeto, no en un iframe (ya que era un documento XHTML 1.1). Así es como funciona con objetos:

document
  .getElementById('targetFrame')
  .contentDocument
  .defaultView
  .targetFunction();

(perdón por los feos saltos de línea, no cabía en una sola línea)

9

Algunas de estas respuestas no abordan el problema de CORS o no dejan en claro dónde coloca los fragmentos de código para hacer posible la comunicación.

He aquí un ejemplo concreto. Digamos que quiero hacer clic en un botón en la página principal y que haga algo dentro del iframe. Así es como lo haría yo.

parent_frame.html

<button id='parent_page_button' onclick='call_button_inside_frame()'></button>

function call_button_inside_frame() {
   document.getElementById('my_iframe').contentWindow.postMessage('foo','*');
}

iframe_page.html

window.addEventListener("message", receiveMessage, false);

function receiveMessage(event)
    {
      if(event) {
        click_button_inside_frame();
    }
}

function click_button_inside_frame() {
   document.getElementById('frame_button').click();
}

Para ir en la otra dirección (haga clic en el botón dentro del iframe para llamar al método fuera del iframe) simplemente cambie el lugar donde se encuentra el fragmento de código y cambie esto:

document.getElementById('my_iframe').contentWindow.postMessage('foo','*');

a esto:

window.parent.postMessage('foo','*')
2
  • 1
    Finalmente una buena solución, ¡gracias! NobodySomewhere 15 mar a las 15:08
  • El método parent.postMessage parece seguir siendo confiable entre marcos. eyal_katz 17 de mayo a las 14:38
8

El IFRAME debe estar en la frames[]colección. Usa algo como

frames['iframeid'].method();
1
  • Esta respuesta ciertamente podría usar más información, ya que definitivamente hay otras consideraciones. Por un lado, supongo que el desarrollador necesita hacer methoduna propiedad del windowobjeto del iframe . matty 24 de junio de 2015 a las 5:06
8

Quirksmode tenía una publicación sobre esto .

Dado que la página ahora está rota y solo se puede acceder a ella a través de archive.org, la reproduje aquí:

IFrames

En esta página, ofrezco una breve descripción general del acceso a los iframes desde la página en la que se encuentran. No es sorprendente que haya algunas consideraciones sobre el navegador.

Un iframe es un marco en línea, un marco que, si bien contiene una página completamente separada con su propia URL, se coloca dentro de otra página HTML. Esto da muy buenas posibilidades en el diseño web. El problema es acceder al iframe, por ejemplo, para cargar una nueva página en él. Esta página explica cómo hacerlo.

¿Marco u objeto?

La pregunta fundamental es si el iframe se ve como un marco o como un objeto.

  • Como se explica en la Introducción a las páginas de marcos , si usa marcos, el navegador crea una jerarquía de marcos para usted ( top.frames[1].frames[2]y tal). ¿Encaja el iframe en esta jerarquía de marcos?
  • ¿O el navegador ve un iframe como un objeto más, un objeto que tiene una propiedad src? En ese caso, tenemos que usar una llamada DOM estándar (como document.getElementById('theiframe'))para acceder a ella. En general, los navegadores permiten ambas vistas en iframes 'reales' (codificados), pero no se puede acceder a los iframes generados como marcos.

Atributo NAME

La regla más importante es darle un nameatributo a cualquier iframe que cree , incluso si también usa un id.

<iframe src="iframe_page1.html"
    id="testiframe"
    name="testiframe"></iframe>

La mayoría de los navegadores necesitan el nameatributo para que el iframe forme parte de la jerarquía de marcos. Algunos navegadores (especialmente Mozilla) necesitan idpara hacer que el iframe sea accesible como un objeto. Al asignar ambos atributos al iframe, mantiene abiertas sus opciones. Pero namees mucho más importante que id.

Acceso

O accedes al iframe como un objeto y lo cambias srco accedes al iframe como un marco y cambias su location.href.

document.getElementById ('iframe_id'). src = 'nueva página.html'; marcos ['iframe_name']. location.href = 'newpage.html'; La sintaxis del marco es ligeramente preferible porque Opera 6 la admite, pero no la sintaxis del objeto.

Accediendo al iframe

Por lo tanto, para una experiencia completa en varios navegadores, debe asignar un nombre al iframe y usar el

frames['testiframe'].location.href

sintaxis. Hasta donde yo sé, esto siempre funciona.

Accediendo al documento

Acceder al documento dentro del iframe es bastante simple, siempre que use el nameatributo. Para contar la cantidad de enlaces en el documento en el iframe, haga frames['testiframe'].document.links.length.

Iframes generados

Sin embargo, cuando genera un iframe a través del DOM W3C, el iframe no se ingresa inmediatamente en la framesmatriz y la frames['testiframe'].location.hrefsintaxis no funcionará de inmediato. El navegador necesita un poco de tiempo antes de que aparezca el iframe en la matriz, tiempo durante el cual no se puede ejecutar ningún script.

La document.getElementById('testiframe').srcsintaxis funciona bien en todas las circunstancias.

El targetatributo de un enlace tampoco funciona con los iframes generados, excepto en Opera, aunque le di a mi iframe generado un namey un id.

La falta de targetsoporte significa que debe usar JavaScript para cambiar el contenido de un iframe generado, pero como necesita JavaScript de todos modos para generarlo en primer lugar, no veo que esto sea un gran problema.

Tamaño del texto en iframes

Un curioso error único de Explorer 6:

Cuando cambia el tamaño del texto a través del menú Ver, los tamaños de texto en iframes se cambian correctamente. Sin embargo, este navegador no cambia los saltos de línea en el texto original, por lo que parte del texto puede volverse invisible o pueden producirse saltos de línea mientras la línea todavía puede contener otra palabra.

3
7

Encontré una solución bastante elegante.

Como dijiste, es bastante fácil ejecutar código ubicado en el documento principal. Y esa es la base de mi código, haz todo lo contrario.

Cuando se carga mi iframe, llamo a una función ubicada en el documento principal, pasando como argumento una referencia a una función local, ubicada en el documento del iframe. El documento principal ahora tiene acceso directo a la función del iframe a través de esta referencia.

Ejemplo:

Sobre el padre:

function tunnel(fn) {
    fn();
}

En el iframe:

var myFunction = function() {
    alert("This work!");
}

parent.tunnel(myFunction);

Cuando se cargue el iframe, llamará a parent.tunnel (YourFunctionReference), que ejecutará la función recibida en el parámetro.

Así de simple, sin tener que lidiar con todos los métodos no estándares de los distintos navegadores.

5
  • Hola, ¿puedes confirmar esto "Así de simple, sin tener que lidiar con todos los métodos no estándares de los distintos navegadores"? Milche Patern 26 de mayo de 2015 a las 23:17
  • 1
    Utilizo este método en varios proyectos, parece que funciona muy bien. No he probado personalmente todos los navegadores en circulación, pero tampoco recibí un informe de error. Julien L 18/06/15 a las 22:24
  • Funciona a las mil maravillas, IE 11, Chrome ~ 50. Augustas 19 de mayo de 2016 a las 6:48
  • 1
    Supongo que eso no es de dominio cruzado ... ¿verdad? Tushar Shukla 31 de mayo de 2016 a las 9:22
  • @TusharShukla Lamentablemente no, no lo es. jeff-h 18/06/18 a las 9:17
4

Continuando con la respuesta de JoelAnair :

Para mayor robustez, use de la siguiente manera:

var el = document.getElementById('targetFrame');

if(el.contentWindow)
{
   el.contentWindow.targetFunction();
}
else if(el.contentDocument)
{
   el.contentDocument.targetFunction();
}

Funcionó como el encanto :)

3

Siguiendo la respuesta de Nitin Bansal

y para una robustez aún mayor:

function getIframeWindow(iframe_object) {
  var doc;

  if (iframe_object.contentWindow) {
    return iframe_object.contentWindow;
  }

  if (iframe_object.window) {
    return iframe_object.window;
  } 

  if (!doc && iframe_object.contentDocument) {
    doc = iframe_object.contentDocument;
  } 

  if (!doc && iframe_object.document) {
    doc = iframe_object.document;
  }

  if (doc && doc.defaultView) {
   return doc.defaultView;
  }

  if (doc && doc.parentWindow) {
    return doc.parentWindow;
  }

  return undefined;
}

y

...
var el = document.getElementById('targetFrame');

var frame_win = getIframeWindow(el);

if (frame_win) {
  frame_win.targetFunction();
  ...
}
...
3

Si desea invocar la función de JavaScript en el padre desde el iframe generado por otra función, por ejemplo, shadowbox o lightbox.

Debería intentar hacer uso del windowobjeto e invocar la función principal:

window.parent.targetFunction();
2
  • 4
    Creo que el OP está tratando de ir en la otra direcciónCommandZ 23 de enero de 2020 a las 3:40
  • para alguien como yo que vino buscando la comunicación inversa, usted es un ahorrador de tiempo y vidaOlfredos6 30 de julio a las 7:24
2
       $("#myframe").load(function() {
            alert("loaded");
        });
2

Las mismas cosas, pero una forma un poco más fácil, será Cómo actualizar la página principal desde la página dentro del iframe . Simplemente llame a la función de la página principal para invocar la función javascript para volver a cargar la página:

window.location.reload();

O haga esto directamente desde la página en iframe:

window.parent.location.reload();

Ambas obras.

1

Prueba solo parent.myfunction()

2
-3

Utilice lo siguiente para llamar a la función de un marco en la página principal

parent.document.getElementById('frameid').contentWindow.somefunction()
0