Автоматическая кросс-доменная установка высоты Iframe

в 9:33, , рубрики: iframe, javascript, Веб-разработка, метки: ,

Думаю, многие, кто сталкивался в своей работе с iframe, сталкивались и с задачей установки высоты этого самого айфрейма.

Это может быть необходимо, например, когда ты хочешь дать возможность пользователям ставить виджеты с вашего сайта на их сайт, и хочется, чтобы размер контейнера (iframe) виджета соответствовал размерам содержимого этого виджета.

В яндексе можно найти множество решений этой проблемы, но большинство из них обладают одной проблемой: они не поддерживают возможность менять размеры окна когда содержимое iframe и родительский элемент находятся на разных доменах.

Есть одно неплохое кросс-доменное решение, но оно было написано в 2007 году, а с тех пор многое изменилось. Поэтому пришлось разрабатывать решение этой проблемы самостоятельно, основываясь на приведенном решении.

Собственно, основное, что поменялось — это появился инструмент window.postMessage, который позволяет окнам браузера обмениваться сообщениями. Благодаря этому мы сможем сделать аналог приведенного решения, обладающий следующими преимуществами:

  • В новых браузерах не будет спама в hash (и заодно будет корректно работать хеш-навигация на клиентской странице)
  • корректная работа в webkit
  • более простой и понятный код

Наш код не должен зависеть от каких-то внешних факторов, и для его использования должно быть достаточно на стороне содержимого фрейма подключить 2 js файла (один для postMessage, а ворой — наш), а на стороне клиента — подключить также 2 js файла и добавить айфрейму два аттрибута — id и name (должны быть идентичны), а также на onload повесить регистрацию iframe.

Для работы с postmessage мы будем использовать следующую библиотеку:
postmessage.freebaseapps.com/
Эта библиотека является очень удобной оберткой для window.postMessage, которая, кроме прочего, также позволяет пользоваться передачей данных через hash в браузерах, которые не поддерживают window.postMessage.

Логика работы скрипта предельно проста — на клиентской странице мы «регистрируем» iframe — отправляем дочерней странице (которая в iframe) идентификатор этого iframe и подписываемся на сообщения от дочерней страницы. При получении сообщения — изменяем размер.

На стороне дочерней страницы (которая в iframe) мы в свою очередь подписываемся на сообщения о регистрации, когда оно приходит — запоминаем идентификатор и включаем таймер, который при изменении размера содержимого фрейма отправляет родительской странице информацию о том, что нужно поменять размер iframe.

Собственно вот и все. Сам код скриптов:

FrameManager.js («клиентский» скрипт)

var FrameManager =
{
    registerFrame : function(frame)
    {
        pm({
          target: window.frames[frame.id],
          type:   "register", 
          data:   {id:frame.id},
          url: frame.contentWindow.location
        });
        
        pm.bind(frame.id, function(data) {
            var iframe = document.getElementById(data.id);
            if (iframe == null) return;
            iframe.style.height = (data.height+12).toString() + "px";            
        });        
    }
};

Frame.js (скрипт для содержимого фрейма)

var FrameHeightManager =
{
    FrameId: '',
    getCurrentHeight : function()
    {
          myHeight = 0;
          
          if( typeof( window.innerWidth ) == 'number' ) {
            myHeight = window.innerHeight;
          } else if( document.documentElement && document.documentElement.clientHeight ) {
            myHeight = document.documentElement.clientHeight;
          } else if( document.body && document.body.clientHeight ) {
            myHeight = document.body.clientHeight;
          }
          
          return myHeight;      
    },    
    publishHeight : function()
    {
        if (this.FrameId == '') return;
        // если нет jQuery - воспользуемся решениями для  определения размеров из яндекса
        if(typeof jQuery === "undefined") {
            var actualHeight = (document.body.scrollHeight > document.body.offsetHeight)?document.body.scrollHeight:document.body.offsetHeight;
            var currentHeight = this.getCurrentHeight();            
        } else {
            var actualHeight = $("body").height();
            var currentHeight = $(window).height();            
        }

        if(Math.abs(actualHeight - currentHeight) > 20)
        {
            pm({
              target: window.parent,
              type: this.FrameId, 
              data: {height:actualHeight, id:this.FrameId}
            });
        }       
    }   

};

pm.bind("register", function(data) {
    FrameHeightManager.FrameId = data.id;
    // не забываем передать правильный this
    window.setInterval(function() {FrameHeightManager.publishHeight.call(FrameHeightManager)}, 300);
});

И напишем 2 тестовых html-страницы:

test.html (родительская)

<!DOCTYPE html>
<html>
<body>
    <script src="postmessage.js"></script>
    <script src="FrameManager.js"></script>                
    <iframe height="10" id="frame1" name="frame1" src="test2.html" onload="FrameManager.registerFrame(this)" scrolling="no" frameborder="0" marginheight="0" marginwidth="0" ></iframe>   
</body>
</html>

test2.html (дочерняя)

<!DOCTYPE html>
<html>
<body>
    <!--<script src="http://yandex.st/jquery/1.7.1/jquery.min.js"></script>-->
    <script src="postmessage.js"></script>
    <script src="Frame.js"></script>
<div style="border:1px solid red;margin:0; height:200px;">
  test
  </div>
</body>
</html>

В дочерней странице также можно расскомментировать участок кода с подключением jquery — тогда определение высоты будет более точным. Буду благодарен за нормальные кросс-браузерные функции для определения размеров страницы и окна, я использовал первые, которые нашел — они работают, но пикселей на 10 в вебките допускают погрешность.

Для меня это некритично, потому что у меня на стороне клиента (т.е. в дочерней странице) есть jquery, но у кого-то, возможно, его не будет — тогда код все равно сохранит работоспособность.

Автор: rednaxi

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js