Оперативное изменение Height в iFrame через postMessage.
Установка высоты iFrame в родителе в зависимости от изменения контента дочерней страницы.
В некоторых ситуациях необходимо поместить на основной веб странице дополнительную страницу со своей структурой документа. Так же это могут документы PDF, видео и интерактивные медиа файлы, отдельные приложения или ДЕМО страницы. Встроить в разметку основной страницы элемент iFrame и задать ему начальные параметры сложностей не вызывает. Можно задать параметры через стиль и управлять фреймом в зависимости от ситуации на главной странице. С шириной фрейма обычно проблем не возникает так как в большинстве случаев для него отводят всю ширину родительского блока, а внутренней контент фрейма адаптируется согласно текущей ширине его окна. Но что делать с высотой? Заданная изначально высота фрейма не изменяется в зависимости от структуры документа внутри него. Если он выходит за нижнюю границу фрейма, то появляется полоса прокрутки если не запрещена в установке его параметров, а если задать высоту с запасом, то остаётся довольно большой отступ до следующего блока. Особенно эта коллизия проявляется в мобильных версиях, когда экран меняет своё положение. К тому же контент внутренней страницы может быть динамичным и в процессе работы тоже менять свою высоту. Встаёт задача оперативно изменять высоту фрейма по событию изменения высоты внутреннего документа.
Напрямую осуществить такое управление не получится, так как Iframe для того и введён к HTML чтобы ограничить взаимодействие документов, их структуры и событий межу собой. Но обойти такое ограничение в рамках поставленной задачи несложно.
Для начала нужно после загрузки внутренней страницы фрейма определить стартовую высоту документа. Затем записать это значение в Cookie для передачи данных в родительский документ. Для этого используем такой код:
$(document).ready(function () { //Определяем полную высоту документа var Hdok = $(document).outerHeight(true); //записываем значение высоты в cookie document.cookie = "Hdok="+Hdok; });
Теперь после загрузки документов кука с данными высоты внутренней страницы будет доступна в родителе. Останется только извлечь её и задать высоте фрейма. Для надёжности нужно осуществить задержку в исполнении принимающей стороной, чтобы внутренняя страница успела загрузиться и прописать куку. Вроде хорошо, да не очень. Точно расчитать таймер задержки не получится. хорошо когда интернет быстрый и сервер мощный, но ведь так далеко не всегда бывает. Ещу одним недостатком такого решения - невозможность оперативно менять высоту фрейма после загрузки если меняются размеры окна или высота контента во внутреннем документе.
Добавляем в код обработчик события изменения размера документа window.addEventListener('resize', start) , а для отправки сообщения родительскому документу о смене значений высоты используем метод window.postMessage().
Метод window.postMessage() безопасно обеспечивает связь между объектами из разных источников: например, между страницей и всплывающим окном, которое она создала, или между страницей и iframe, встроенным в нее. Подробнее.
JStop.postMessage("Hdok" + "=" + Hdok, '/test/test.html'); //Функция отправки сообщений //Первый параметр: строка сообщения //Второй параметр: // домен - "*"); // папка-"/demo/; // страница назначения: "index.html"
Перестраиваем код на дочерней странице:
top.postMessage("Hdok" + "=" + Hdok, '/test/test.html'); //Функция отправки сообщений //Первый параметр: строка сообщения //Второй параметр: // домен - "*"); // папка-"/demo/; // страница назначения: "index.html"
Ставим слушатель сообщений на основной странице:
$(document).ready(function () { //Получаем сообщение из фрейма window.addEventListener("message", function(e){ //Разбиваем по разделителю var dataE = e.data.split('=', 2); //Проверяем откуда пришёл if (e.origin !== 'https://test.com') { return false; } //Проверяем на соответствие if (dataE[0] == "demo") { //Задаем высоту фрейма $('iframe[name=demo]').height(dataE[1]); } }, false); });
Последний код вполне рабочий, но можно и поусложнять при желании .
Чтобы родитель не реагировал на возможно ложные сообщения из вне включаем проверку по случайному коду. Планируем работу поэтапно:
- Дочерний документ. Получаем случайную цифровую последовательность и записываем в куке в качестве проверочного ключа.
- Дочерний документ. После загрузки страницы и в случае изменения размеров документа отправляем методом postMessage строку: ключ + разделитель + Height документа.
- Родительская страница. Получаем данные. Разбиваем по разделителю. Проверяем ключ на соответствие значений из сообщения и куки. Если все правильно, задаём высоту фрейма.
$(document).ready(function () { //Создаём ключ и записываем в Cookie var demoKl = Math.random(); document.cookie = "demoKl="+demoKl; //Посылаем данные на родитель после загрузки start(); //Посылаем данные на родитель из слушателя событий window.addEventListener('resize', start); }); //Сообщение к родительской стороне function start(){ //Определяем полную высоту документа var Hdok = $(document).outerHeight(true); //Отправка данных в сообщении родителю top.postMessage(getCookie('demoKl')+"|"+Hdok, '*'); }
Функцию получения куки по имени ключа вынесу отдельно (не забываем подставлять в код):
function getCookie(name) { var matches = document.cookie.match(new RegExp("(?:^|; )" + name.replace(/([\.$?*|{}\(\)\[\]\\\/\+^])/g, '\\$1') + "=([^;]*)")); return matches ? decodeURIComponent(matches[1]) : undefined; }
На родительской странице ставим слушатель сообщений от дочернего документа.
$(document).ready(function () { //Получаем сообщение из фрейма window.addEventListener("message", function(e){ //Разбиваем по разделителю var dataE = e.data.split('|', 2); //Проверяем на соответствие if (dataE[0] == getCookie('demoKl')) { //Задаем высоту фрейма $('iframe[name=demo]').height(dataE[1]); } }, false); });
Для более надёжной работы мне пришлось в код на стороне дочерней страницы добавить одноразовую функцию, которая записывает ключ проверки в куку только один раз после загрузки страницы и больше не выполняется. Ниже полный код решения для доченего докумена.
$(document).ready(function () { //Вызов одноразовой функции func_one(); //Посылаем данные на родитель после загрузки start(); //Посылаем данные на родитель из обработчика события 'resize' window.addEventListener('resize', start); //Ещё один обработчик события для верности var elem = $('body')[0]; let resizeObserver = new ResizeObserver(() => { start(); }); resizeObserver.observe(elem); }); //Функция отправки сообщения к родительской стороне function start(){ //Определяем полную высоту документа var Hdok = $(document).outerHeight(true); //Отправка данных в сообщении родителю top.postMessage(getCookie('demoKl')+"|"+Hdok, '*'); } //Одноразовая функция. Создаём ключ и записываем в Cookie function func_one() {document.cookie = "demoKl="+Math.random(); func_one = () => {};}