Как сделать iframe отзывчивым без предположения о соотношении сторон? - PullRequest
15 голосов
/ 24 апреля 2020

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

Примечание. Можно использовать Javascript.

Пример:

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

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

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

Ответы [ 4 ]

8 голосов
/ 27 апреля 2020

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

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

Предположим, что у нас есть два одинаковых кадра 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:

<!-- 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 hack ( не рекомендуется )

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

patchIframeHtml:

Функция для исправления кода iFrame HTML и ввода пользовательского Javascript, который будет использовать postMessage для отправки размера iFrame родительскому элементу на load и resize. Если есть значение для параметра origin, то элемент HTML <base/> будет добавлен к заголовку с использованием этого 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, поэтому мы не получаем мерцание полосы прокрутки при изменении размера ).

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

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

2 голосов
/ 30 апреля 2020

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

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

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

0 голосов
/ 03 мая 2020

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

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

.iframe-container iframe{
     width:100%;
     height:100%;
     object-fit:contain;
}
0 голосов
/ 27 апреля 2020

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

Представление решения для адаптивного фрейма без предположения о соотношении сторон.

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

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

Код

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;

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

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

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

...