Как сделать iframe отзывчивым без предположения о соотношении сторон?

14

Как сделать iframe отзывчивым, не предполагая соотношения сторон? Например, контент может иметь любую ширину или высоту, которые неизвестны до рендеринга.

Обратите внимание, вы можете использовать Javascript.

Пример:

<div id="iframe-container">
    <iframe/>
</div>

Измените размер iframe-containerтак, чтобы его содержимое едва помещалось внутри без дополнительного пространства, другими словами, для содержимого достаточно места, чтобы его можно было отображать без прокрутки, но без лишнего пространства. Контейнер отлично оборачивает фрейм.

Это показывает, как сделать iframe отзывчивым, предполагая, что соотношение сторон контента составляет 16: 9. Но в этом вопросе соотношение сторон является переменным.

Ferit
источник
1
Позвольте мне уточнить с правкой @TravisJ
ferit
2
Можно рассчитать размеры содержимого в iframe, но то, как именно это должно быть сделано, зависит немного от самого содержимого и большого количества используемого CSS (абсолютно позиционированное содержимое чрезвычайно сложно измерить). Если вы используете файл сброса css, результат отличается от страницы, где файл сброса не используется, а также зависит от браузера. Установка размера iframe и его родителя тривиальна. Итак, нам понадобится больше информации о структуре страницы, в частности, об именах используемых файлов сброса CSS (или фактического CSS, если вы не используете какие-либо общие файлы).
Теему
2
Есть ли у вас контроль над документом, загруженным в iframe? Я имею в виду, если ты этого не сделаешь, тогда ты ничего не сможешь сделать. Все должно быть сделано внутри документа iframe (или путем доступа к этому документу), в противном случае это вообще невозможно.
Теему
1
Нет, тогда это невозможно, см. Developer.mozilla.org/en-US/docs/Web/Security/… . Главная страница не может получить доступ к междоменному документу в iframe (и наоборот) по соображениям безопасности, это можно обойти, только если вы контролируете документ, показанный в iframe.
Теему
1
... 15 лет Интернета говорят, что это невозможно, не достаточно? Нам действительно нужен новый вопрос по этому поводу?
Кайидо

Ответы:

8

Невозможно взаимодействовать с другим источником iFrame, используя Javascript, чтобы получить его размер; единственный способ сделать это - использовать window.postMessageс targetOriginустановленным на ваш домен или подстановочным символом *из источника iFrame. Вы можете проксировать содержимое различных исходных сайтов и использовать их srcdoc, но это считается хаком и не будет работать с SPA и многими другими динамическими страницами.

Тот же самый размер кадра iFrame

Предположим, у нас есть два одинаковых исходных фрейма: один с небольшой высотой и фиксированной шириной:

<!-- iframe-short.html -->
<head>
  <style type="text/css">
    html, body { margin: 0 }
    body {
      width: 300px;
    }
  </style>
</head>
<body>
  <div>This is an iFrame</div>
  <span id="val">(val)</span>
</body>

и большая высота кадра:

<!-- iframe-long.html -->
<head>
  <style type="text/css">
    html, body { margin: 0 }
    #expander {
      height: 1200px; 
    }
  </style>
</head>
<body>
  <div>This is a long height iFrame Start</div>
  <span id="val">(val)</span>
  <div id="expander"></div>
  <div>This is a long height iFrame End</div>
  <span id="val">(val)</span>
</body>

Мы можем получить размер iFrame для loadсобытия, используя iframe.contentWindow.documentкоторый мы отправим в родительское окно, используя postMessage:

<div>
  <iframe id="iframe-local" src="iframe-short.html"></iframe>
</div>
<div>
  <iframe id="iframe-long" src="iframe-long.html"></iframe>
</div>

<script>

function iframeLoad() {
  window.top.postMessage({
    iframeWidth: this.contentWindow.document.body.scrollWidth,
    iframeHeight: this.contentWindow.document.body.scrollHeight,
    params: {
      id: this.getAttribute('id')
    }
  });
}

window.addEventListener('message', ({
  data: {
    iframeWidth,
    iframeHeight,
    params: {
      id
    } = {}
  }
}) => {
  // We add 6 pixels because we have "border-width: 3px" for all the iframes

  if (iframeWidth) {
    document.getElementById(id).style.width = `${iframeWidth + 6}px`;
  }

  if (iframeHeight) {
    document.getElementById(id).style.height = `${iframeHeight + 6}px`;
  }

}, false);

document.getElementById('iframe-local').addEventListener('load', iframeLoad);
document.getElementById('iframe-long').addEventListener('load', iframeLoad);

</script>

Мы получим правильную ширину и высоту для обоих кадров; Вы можете проверить это онлайн здесь и увидеть скриншот здесь .

Размер взлома iFrame различного происхождения ( не рекомендуется )

Метод, описанный здесь, является хакерским, и его следует использовать, если он абсолютно необходим и другого пути нет; это не будет работать для большинства динамически генерируемых страниц и SPA. Метод извлекает исходный код HTML страницы, используя прокси-сервер для обхода политики CORS ( cors-anywhereэто простой способ создания простого прокси-сервера CORS и имеет интерактивную демонстрациюhttps://cors-anywhere.herokuapp.com ), затем внедряет код JS в этот HTML для использования postMessageи отправляет размер iFrame к родительскому документу. Он даже обрабатывает событие iFrame resizeсочетании с iFramewidth: 100% ) и отправляет размер iFrame обратно родителю.

patchIframeHtml:

Функция пропатчить плавающий фрейм HTML - код и ввести пользовательские Javascript , который будет использовать postMessageдля отправки размера IFrame родителя на loadи resize. Если для originпараметра есть значение , то к заголовку <base/>будет добавлен элемент HTML, использующий этот исходный URL, поэтому HTML-идентификаторы URI, такие как, /some/resource/file.extбудут правильно извлечены исходным URL-адресом внутри iFrame.

function patchIframeHtml(html, origin, params = {}) {
  // Create a DOM parser
  const parser = new DOMParser();

  // Create a document parsing the HTML as "text/html"
  const doc = parser.parseFromString(html, 'text/html');

  // Create the script element that will be injected to the iFrame
  const script = doc.createElement('script');

  // Set the script code
  script.textContent = `
    window.addEventListener('load', () => {
      // Set iFrame document "height: auto" and "overlow-y: auto",
      // so to get auto height. We set "overlow-y: auto" for demontration
      // and in usage it should be "overlow-y: hidden"
      document.body.style.height = 'auto';
      document.body.style.overflowY = 'auto';

      poseResizeMessage();
    });

    window.addEventListener('resize', poseResizeMessage);

    function poseResizeMessage() {
      window.top.postMessage({
        // iframeWidth: document.body.scrollWidth,
        iframeHeight: document.body.scrollHeight,
        // pass the params as encoded URI JSON string
        // and decode them back inside iFrame
        params: JSON.parse(decodeURIComponent('${encodeURIComponent(JSON.stringify(params))}'))
      }, '*');
    }
  `;

  // Append the custom script element to the iFrame body
  doc.body.appendChild(script);

  // If we have an origin URL,
  // create a base tag using that origin
  // and prepend it to the head
  if (origin) {
    const base = doc.createElement('base');
    base.setAttribute('href', origin);

    doc.head.prepend(base);
  }

  // Return the document altered HTML that contains the injected script
  return doc.documentElement.outerHTML;
}

getIframeHtml:

Функция для получения HTML страницы в обход CORS с использованием прокси, если useProxyзадан параметр. Могут быть дополнительные параметры, которые будут переданы postMessageпри отправке данных о размере.

function getIframeHtml(url, useProxy = false, params = {}) {
  return new Promise(resolve => {
    const xhr = new XMLHttpRequest();

    xhr.onreadystatechange = function() {
      if (xhr.readyState == XMLHttpRequest.DONE) {
        // If we use a proxy,
        // set the origin so it will be placed on a base tag inside iFrame head
        let origin = useProxy && (new URL(url)).origin;

        const patchedHtml = patchIframeHtml(xhr.responseText, origin, params);
        resolve(patchedHtml);
      }
    }

    // Use cors-anywhere proxy if useProxy is set
    xhr.open('GET', useProxy ? `https://cors-anywhere.herokuapp.com/${url}` : url, true);
    xhr.send();
  });
}

Функция обработчика событий сообщения точно такая же, как и в «Тот же самый размер iFrame источника» .

Теперь мы можем загрузить домен перекрестного происхождения внутри iFrame с добавленным нашим пользовательским кодом JS:

<!-- It's important that the iFrame must have a 100% width 
     for the resize event to work -->
<iframe id="iframe-cross" style="width: 100%"></iframe>

<script>
window.addEventListener('DOMContentLoaded', async () => {
  const crossDomainHtml = await getIframeHtml(
    'https://en.wikipedia.org/wiki/HTML', true /* useProxy */, { id: 'iframe-cross' }
  );

  // We use srcdoc attribute to set the iFrame HTML instead of a src URL
  document.getElementById('iframe-cross').setAttribute('srcdoc', crossDomainHtml);
});
</script>

И мы увеличим размер iFrame до его содержимого в полную высоту без вертикальной прокрутки, даже не используя overflow-y: autoтело iFrame ( так должно быть, overflow-y: hiddenчтобы полоса прокрутки не мерцала при изменении размера ).

Вы можете проверить это онлайн здесь .

Еще раз заметить, что это взлом, и его следует избегать ; мы не можем получить доступ к документу Cross-Origin iFrame или ввести какие-либо вещи.

Христос Литрас
источник
1
Спасибо! Отличный ответ.
Ferit
1
Спасибо. К сожалению cors-anywhere, в данный момент не работает, поэтому вы не можете увидеть демонстрационную страницу. Я не хочу добавлять скриншот внешнего сайта по причинам авторского права. Я добавлю другой прокси-сервер CORS, если он не будет работать долго.
Христос Литрас
2

Их сложность заключается в определении размера контента в iframe, в основном из-за того, что CSS позволяет вам делать вещи, которые могут помешать измерению размера контента.

Я написал библиотеку, которая заботится обо всех этих вещах, а также работает в междоменной сфере. Возможно, вы найдете это полезным.

https://github.com/davidjbradshaw/iframe-resizer

Дэвид Брэдшоу
источник
1
Круто! Собираюсь проверить это!
Ferit
0

Мое решение на GitHub и JSFiddle .

Представление решения для отзывчивого iframe без предположения о соотношении сторон.

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

NB. Не забудьте вызвать функцию изменения размера после загрузки страницы, так как размер окна не изменяется сам по себе после загрузки страницы.

Код

index.html

<!DOCTYPE html>
<!-- Onyr for StackOverflow -->

<html>

<head> 
    <meta http-equiv="content-type" content="text/html;charset=utf-8" />
    <title>Responsive Iframe</title>
    <link rel="stylesheet" type="text/css" href="./style.css">
</head>

<body id="page_body">
    <h1>Responsive iframe</h1>

    <div id="video_wrapper">
        <iframe id="iframe" src="https://fr.wikipedia.org/wiki/Main_Page"></iframe>
    </div>

    <p> 
        Presenting a solution for responsive iframe without aspect
        ratio assumption.<br><br>
    </p>

    <script src="./main.js"></script>
</body>

</html>

style.css

html {
    height: 100%;
    max-height: 1000px;
}

body {
    background-color: #44474a;
    color: white;
    margin: 0;
    padding: 0;
}

#videoWrapper {
    position: relative;
    padding-top: 25px;
    padding-bottom: 100px;
    height: 0;
    margin: 10;
}
#iframe {
    top: 0;
    left: 0;
    width: 100%;
    height: 100%;
}

main.js

let videoWrapper = document.getElementById("video_wrapper");

let w;
let h;
let bodyWidth;
let bodyHeight;

// get window size and resize the iframe
function resizeIframeWrapper() {
    w = window.innerWidth;
    h = window.innerHeight;

    videoWrapper.style["width"] = `${w}px`;
    videoWrapper.style["height"] = `${h - 200}px`;
}

// call the resize function when windows is resized and after load
window.onload = resizeIframeWrapper;
window.onresize = resizeIframeWrapper;

Я провел некоторое время на этом. Я надеюсь, вам понравится =)

Редактировать Это, вероятно, лучшее общее решение. Однако, когда сделан очень маленький, iframe не дан надлежащий размер. Это зависит от используемого вами фрейма. Невозможно закодировать правильный ответ для этого явления, если у вас нет кода iframe, поскольку ваш код не может определить, какой размер iframe предпочтителен для правильного отображения.

Только некоторые хаки, такие как представленные @Christos Lytras, могут сделать трюк, но он никогда не сработает для каждого iframe. Просто в конкретной ситуации.

Onyr
источник
Спасибо за попытку! Тем не менее, это не работает, как ожидалось. Когда содержимое iframe невелико, в контейнере остается дополнительное пространство. Смотрите это: jsfiddle.net/6p2suv07/3 Я изменил src iframe.
Ferit
Я не уверен, что понимаю вашу проблему с "лишним пространством". Минимальный размер окна - 100 пикселей. Iframe подходит для контейнера. Макет внутри вашего фрейма не находится под моим контролем. Я ответил на ваш вопрос. Пожалуйста, отредактируйте свой, если вам нужна дополнительная помощь
Onyr
Поместите картинку и опишите ее
Onyr
Если iframe занимает все пространство, которое дает ему контейнер, дело в том, что если содержимое iframe мало, ему не требуется все пространство, которое он может занять. Таким образом, контейнер должен предоставлять достаточно места для iframe, не меньше и не больше.
Ferit
Для высоты iframe это связано с тем, что высота пространства ограничена окном в моем примере. Вот это? Ваш пример относится к фрейму, который вы используете, я дал общий ответ, который следует принять. Конечно, можно настроить код так, чтобы он хорошо вписывался в ваш iframe, но для этого необходимо знать код, стоящий за iframe
Onyr
-1

Сделайте одну вещь, установите контейнер Iframe, установите CSS и ширину и высоту.

.iframe-container{
     width:100%;
     min-height:600px;
     max-height:100vh;
}

.iframe-container iframe{
     width:100%;
     height:100%;
     object-fit:contain;
}
Аслам хан
источник
Что делать, если контент меньше 600 пикселей?
Ferit
если высота равна 600, то это не повлияет на iframe, потому что я установил его как подходящий объект
Аслам хан
Контейнер не может быть меньше 600 пикселей, это является нарушением требования, описанного в вопросе.
Ferit