среда, 28 марта 2012 г.

Кроссдоменные iframe и их высота

Тема невозможности взаимодействия между родительской страницей и кроссдоменным iframe является настолько часто обсуждаемой на различных ресурсах по web-разработке, что пора включать ее в F.A.Q.

Юмор в том, что решение на удивление простое, является очень корректным методом, а не хаком и лежит на поверхности: для всех современных браузеров, в том числе для IE, начиная с 8-го существует метод window.postMessage и событие window.onmessage, через которые возможна передача сообщений как между iframe и родителем, так и в дочернее окно, вызванное при помощи window.open

Об этом, кстати, пишет Илья Кантор (javascript.ru). У него хорошо описано как передать сообщение из родителя в iframe. Мне же пришлось решить обратную задачу - передать сообщение из iframe в родительскую страницу. Цель тоже часто встречается в сети в виде вопросов - «Как сделать iframe той же высоты, что и его содержимое, чтобы не было прокрутки»:
<html>
    <head>
        
        
    </head>
 
    <body>
        
    </body>
</html>
 
// Эта конструкция jQuery вызывается по завершении построения DOM 
$(function(){
    var frame = $('#test');
    var height = 0; 

    var listener = function(event){
        // В более серьезных задачах вам придется делать фильтрацию
        // по имени домена (origin) или самим данным (data)
        height = parseInt(event.data);
        // Только если новая высота не совпадает с прежней
        if (height != frame.height()) frame.css('height', height);        
    }
 
    // jQuery не умеет работать с postMessage из коробки - вешаем событие
    // стандартными средствами

    function addEvent(elem, evnt,  func) {
        if (elem.addEventListener) { // W3C DOM
            elem.addEventListener(evnt,func,false);
        } else if (elem.attachEvent) { // IE DOM
            elem.attachEvent("on"+evnt, func);
        } else {
            elem["on"+evnt] = func;
        }
    }

    addEvent (window, "message", listener);
    
    // вешаем событие изменения высоты на событие полной загрузки содержимого фрейма
    frame.bind('load', setheight);
});

В документе, загружающемся в iframe всё еще проще (html не привожу, только скрипт):

// $(window).load вызывается когда загружены все картинки, а так как от них
// зависит высота страницы - нужно учитывать.
$(window).load(function(){
    var $doc = $(document);
    var height = 0;
    
    var sendHeight = function(){
        // проверяем, изменилась ли высота
        if ($doc.height() != height) {
            // если да - изменяем глобальную переменную и отправляем ее родителю
            height = $doc.height();
            parent.postMessage(height, '*');
        }
    }
    
    sendHeight();
    // если высота будет меняться уже после загрузки фрейма - будет вылано новое значение
    window.onresize = sendHeight;
});

Способ этот входит в спецификацию HTML5 и он настолько приятен, что его хочется использовать не только в кросс-доменном варианте, но и просто когда нужно организовать общение между разными страницами.

2 комментария:

  1. Привет!
    Спасибо за статью, весьма познавательно. Только есть один вопросик: что если во фрейм должен загружаться не .html-документ, а .php-файл? Как быть в этом случае? Если я правильно понял - то последний скрипт надо вставлять именно в дочерний документ, который будет грузиться во фрейм?

    ОтветитьУдалить
  2. Откуда в parent.js берется setheight?

    ОтветитьУдалить