Як зробити iframe чуйним без припущення про співвідношення сторін?


14

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

Зверніть увагу, ви можете використовувати Javascript.

Приклад:

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

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

Це показує, як зробити iframe чуйним, припускаючи, що співвідношення сторін вмісту становить 16: 9. Але в цьому питанні співвідношення сторін є змінним.


1
Дозвольте уточнити за допомогою редагування @TravisJ
ferit

2
Можна обчислити розміри вмісту в iframe, але як саме це слід зробити, трохи залежить від самого контенту та багато використовуваного CSS (абсолютно розміщений вміст вкрай важко виміряти). Якщо ви використовуєте файл скидання css, результат відрізняється від сторінки, в якій файл скидання не використовується, а також змінюється між браузерами. Встановлення розміру iframe та його батьківського значення є тривіальним. Отже, нам знадобиться додаткова інформація про структуру сторінки, зокрема назву (-ів) використовуваних файлів (-ів) скидання CSS (або власне CSS, якщо ви не використовуєте жодних загальних файлів).
Теему

2
Чи маєте ви контроль над документом, завантаженим у рамку? Я маю на увазі, якщо ви цього не зробите, то нічого не можете зробити. Все потрібно зробити всередині документа iframe (або отримати доступ до цього документа), інакше це взагалі неможливо.
Теему

1
Ні, тоді це неможливо, див. Developer.mozilla.org/en-US/docs/Web/Security/… . Головна сторінка не може отримати доступ до міждоменного документа в iframe (і навпаки) з міркувань безпеки, це можна обійти, лише якщо у вас є контроль над документом, показаним у iframe.
Теему

1
... 15 років Інтернету говорять, що неможливо, недостатньо? Чи справді нам потрібне нове запитання з цього приводу?
Каїдо

Відповіді:


8

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

Розмір iFrame одного походження

Припустимо, у нас два iFrames одного походження, один короткої висоти та фіксованої ширини:

<!-- 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>

Ми отримаємо належну ширину та висоту для обох iFrames; ви можете перевірити його онлайн тут і подивитися скріншот тут .

Різні походження розмір плаваючого фрейма хак ( не рекомендується )

Описаний тут метод є злому, і його слід використовувати, якщо це абсолютно необхідно і немає іншого способу; він не працюватиме для більшості динамічно створених сторінок та SPA-програм. Метод отримує вихідний код HTML-сторінки за допомогою проксі-сервера для обходу політики CORS ( cors-anywhereце простий спосіб створити простий проксі-сервер CORS і має демонстрацію в Інтернетіhttps://cors-anywhere.herokuapp.com ), після чого він вводить JS-код у цей HTML, щоб використовувати postMessageта надсилати розмір iFrame до батьківського документа. Він навіть обробляє iFrame resizeпоєднанні з iFramewidth: 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 Cross-Origin і не вводити будь-які речі.


1
Дякую! Чудова відповідь.
ferit

1
Дякую. На жаль cors-anywhere, на даний момент не працює, тому ви не можете побачити демонстраційну сторінку. Я не хочу додавати скріншот зовнішнього сайту з міркувань авторських прав. Я додаю інший проксі-сервер CORS, якщо він затримується надовго.
Крістос Літрас

2

Їх безліч ускладнень при розробці розміру вмісту в iframe, головним чином завдяки CSS, що дозволяє вам робити речі, які можуть порушити те, як ви вимірюєте розмір вмісту.

Я написав бібліотеку, яка піклується про всі ці речі, а також працює між доменами, можливо, вам це стане в нагоді.

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


1
Класно! Перевірте це!
ferit

0

Моє рішення - на GitHub та JSFiddle .

Представлення рішення для чуйного iframe без припущення про співвідношення сторін.

Мета полягає в тому, щоб змінити розмір 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;

Я витратив на це деякий час. Сподіваюся, вам сподобається =)

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

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


Дякуємо за спробу! Однак це не працює, як очікувалося. Коли вміст iframe невеликий, контейнер все ще має додатковий простір. Дивіться це: jsfiddle.net/6p2suv07/3 Я змінив src iframe.
ferit

Я не впевнений, що розумію вашу проблему з «додатковим простором». Мінімальний розмір вікна - 100 пікс. Iframe підходить до контейнера. Макет у вашому iframe не контролюється. Я відповів на ваше запитання. Будь ласка, відредагуйте свою, якщо хочете отримати додаткову допомогу
Onyr

Покладіть картинку та опишіть її
Онір

iframe займає весь простір, який контейнер надає йому, річ полягає в тому, що якщо вміст iframe невеликий, він не потребує всього місця, яке він може зайняти. Отже, контейнер повинен давати достатньо місця для рамки, не менше і не більше.
ferit

Що стосується висоти iframe, це тому, що висота простору обмежена вікном у моєму прикладі. Це все? Ваш приклад специфічний для рамки, яку ви використовуєте, я дав загальну відповідь, яку слід прийняти. Звичайно, можна налаштувати код так, щоб він добре відповідав вашому iframe, але для цього потрібно знати код за
рамкою

-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
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.