Bagaimana cara membuat iframe responsif tanpa asumsi rasio aspek?

14

Bagaimana membuat iframe responsif, tanpa mengasumsikan rasio aspek? Misalnya, konten mungkin memiliki lebar atau tinggi, yang tidak diketahui sebelum rendering.

Catatan, Anda bisa menggunakan Javascript.

Contoh:

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

Ukuran ini iframe-containersehingga isinya hampir tidak muat di dalam tanpa ruang tambahan, dengan kata lain, ada cukup ruang untuk konten sehingga dapat ditampilkan tanpa menggulir, tetapi tidak ada ruang berlebih. Wadah membungkus iframe dengan sempurna.

Ini menunjukkan bagaimana membuat iframe responsif, dengan asumsi rasio aspek konten adalah 16: 9. Tetapi dalam pertanyaan ini, rasio aspek adalah variabel.

ferit
sumber
1
Izinkan saya mengklarifikasi dengan edit @TravisJ
ferit
2
Dimungkinkan untuk menghitung dimensi konten dalam iframe, tetapi bagaimana tepatnya itu harus dilakukan, sedikit tergantung pada konten itu sendiri, dan banyak dari CSS yang digunakan (konten yang diposisikan sangat sulit untuk diukur). Jika Anda menggunakan file reset css, hasilnya berbeda dari halaman yang file reset tidak digunakan, dan juga bervariasi antar browser. Pengaturan ukuran iframe dan induknya sepele. Jadi, kami membutuhkan informasi lebih lanjut tentang struktur halaman, khususnya nama file reset CSS yang digunakan (atau CSS yang sebenarnya, jika Anda tidak menggunakan file umum apa pun).
Teemu
2
Apakah Anda memiliki kendali atas dokumen yang dimuat di iframe? Maksud saya, jika tidak, maka tidak ada yang bisa Anda lakukan. Segala sesuatu harus dilakukan di dalam dokumen iframe (atau dengan mengakses dokumen itu), selain itu kami tidak dapat melakukannya sama sekali.
Teemu
1
Tidak, maka itu tidak mungkin, lihat developer.mozilla.org/en-US/docs/Web/Security/… . Halaman utama tidak dapat mengakses dokumen lintas domain di iframe (dan sebaliknya) karena alasan keamanan, ini dapat dielakkan hanya jika Anda memiliki kontrol atas dokumen yang ditampilkan di iframe.
Teemu
1
... 15 tahun internet mengatakan itu tidak mungkin tidak cukup? Apakah kita benar-benar memerlukan pertanyaan baru tentang ini?
Kaiido

Jawaban:

8

Tidak mungkin berinteraksi dengan iFrame asal yang berbeda menggunakan Javascript sehingga untuk mendapatkan ukurannya; satu-satunya cara untuk melakukannya adalah dengan menggunakan window.postMessagedengan targetOriginset ke domain Anda atau wildchar *dari sumber iFrame. Anda dapat mem-proksi konten dari situs asal yang berbeda dan menggunakan srcdoc, tetapi itu dianggap sebagai peretasan dan itu tidak akan berfungsi dengan SPA dan banyak halaman lain yang lebih dinamis.

Ukuran iFrame asal yang sama

Misalkan kita memiliki dua iFrames asal yang sama, satu dengan tinggi pendek dan lebar tetap:

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

dan iFrame tinggi panjang:

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

Kita bisa mendapatkan ukuran iFrame pada loadacara menggunakan iframe.contentWindow.documentyang akan kami kirim ke jendela induk menggunakan 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>

Kami akan mendapatkan lebar dan tinggi yang tepat untuk kedua iFrames; Anda dapat memeriksanya online di sini dan melihat tangkapan layar di sini .

Yang berbeda asal iFrame ukuran hack ( tidak direkomendasikan )

Metode yang dijelaskan di sini adalah peretasan dan harus digunakan jika benar-benar diperlukan dan tidak ada jalan lain; itu tidak akan berfungsi untuk sebagian besar halaman dan SPA yang dihasilkan dinamis. Metode ini mengambil halaman kode sumber HTML menggunakan proxy untuk memotong kebijakan CORS ( cors-anywhereadalah cara mudah untuk membuat server proxy CORS sederhana dan memiliki demo onlinehttps://cors-anywhere.herokuapp.com ) lalu menyuntikkan kode JS ke HTML itu untuk menggunakan postMessagedan mengirim ukuran file. iFrame ke dokumen induk. Ia bahkan menangani event iFrame resize( dikombinasikan dengan iFramewidth: 100% ) dan memposting ukuran iFrame kembali ke induknya.

patchIframeHtml:

Sebuah fungsi untuk menambal kode HTML iFrame dan menyuntikkan Javascript khusus yang akan digunakan postMessageuntuk mengirim ukuran iFrame ke orang tua terus loaddan terus resize. Jika ada nilai untuk originparameter, maka <base/>elemen HTML akan ditambahkan ke kepala menggunakan URL asal, dengan demikian, HTML URI seperti /some/resource/file.extakan diambil dengan benar oleh URL asal di dalam 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:

Sebuah fungsi untuk mendapatkan halaman HTML yang melewati CORS menggunakan proksi jika useProxyparam diatur. Mungkin ada parameter tambahan yang akan diteruskan ke postMessagesaat mengirim data ukuran.

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();
  });
}

Fungsi pengendali acara pesan sama persis dengan "Ukuran asal yang sama iFrame" .

Kami sekarang dapat memuat domain asal silang di dalam iFrame dengan kode JS khusus kami yang disuntikkan:

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

Dan kita akan mendapatkan ukuran iFrame dengan isi penuh tanpa menggulir vertikal bahkan menggunakan overflow-y: autountuk tubuh iFrame ( seharusnya overflow-y: hiddenjadi kita tidak mendapatkan scrollbar berkedip pada ukuran ).

Anda dapat memeriksanya online di sini .

Sekali lagi untuk memperhatikan bahwa ini adalah peretasan dan harus dihindari ; kami tidak dapat mengakses dokumen iFrame Cross-Origin atau menyuntikkan segala hal.

Christos Lytras
sumber
1
Terima kasih! Jawaban yang bagus
ferit
1
Terima kasih. Sayangnya cors-anywheresedang down saat ini sehingga Anda tidak dapat melihat halaman demo . Saya tidak ingin menambahkan tangkapan layar situs eksternal karena alasan hak cipta. Saya akan menambahkan proxy CORS lainnya jika itu tetap lama.
Christos Lytras
2

Mereka banyak komplikasi dengan menentukan ukuran konten dalam iframe, terutama karena CSS memungkinkan Anda untuk melakukan hal-hal yang dapat merusak cara Anda mengukur ukuran konten.

Saya menulis perpustakaan yang mengurus semua hal ini dan juga berfungsi lintas domain, Anda mungkin merasa terbantu.

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

David Bradshaw
sumber
1
Keren! Akan periksa itu!
ferit
0

Solusi saya ada di GitHub dan JSFiddle .

Menghadirkan solusi iframe responsif tanpa asumsi rasio aspek.

Tujuannya adalah untuk mengubah ukuran iframe bila perlu, dengan kata lain ketika jendela diubah ukurannya. Ini dilakukan dengan JavaScript untuk mendapatkan ukuran jendela baru dan mengubah ukuran iframe sebagai konsekuensinya.

NB: Jangan lupa memanggil fungsi pengubahan ukuran setelah halaman dimuat karena jendela tidak diubah ukurannya sendiri setelah memuat halaman.

Kode

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;

Saya menghabiskan beberapa waktu untuk ini. Saya harap Anda akan menikmatinya =)

Sunting Ini mungkin solusi generik terbaik. Namun, ketika dibuat sangat kecil, iframe tidak diberi ukuran yang tepat. Ini khusus untuk iframe yang Anda gunakan. Tidak mungkin untuk kode jawaban yang tepat untuk fenomena ini kecuali Anda memiliki kode iframe, karena kode Anda tidak memiliki cara untuk mengetahui ukuran mana yang lebih disukai oleh iframe yang akan ditampilkan dengan benar.

Hanya beberapa peretasan seperti yang disajikan oleh @Christos Lytras yang dapat membuat trik tetapi tidak akan berhasil untuk setiap iframe. Hanya dalam situasi tertentu.

Onyr
sumber
Terima kasih telah mencoba! Namun, itu tidak berfungsi seperti yang diharapkan. Ketika konten iframe kecil, wadah masih memiliki ruang ekstra. Lihat ini: jsfiddle.net/6p2suv07/3 Saya mengubah src iframe.
ferit
Saya tidak yakin untuk memahami masalah Anda dengan "ruang ekstra". Ukuran minimum jendela adalah 100 px lebar. Iframe cocok dengan wadah. Tata letak di dalam iframe Anda tidak ada di kendali saya. Saya menjawab pertanyaan Anda. Harap edit milik Anda jika Anda ingin bantuan lebih lanjut
Onyr
Pasang foto dan jelaskan
Onyr
iframe mengambil semua ruang yang diberikan wadah untuk itu, masalahnya adalah, ketika konten iframe kecil, itu tidak memerlukan semua ruang yang bisa diambil. Jadi, wadah harus memberikan ruang yang cukup untuk iframe, tidak kurang tidak lebih.
Ferit
Untuk ketinggian iframe, ini karena ketinggian ruang dibatasi oleh jendela dalam contoh saya. Itu ini? Contoh Anda khusus untuk iframe yang Anda gunakan, saya memberikan jawaban umum yang harus diterima. Tentu saja, mungkin untuk mengubah kode sehingga cocok dengan iframe Anda, tetapi ini perlu mengetahui kode di balik iframe
Onyr
-1

Lakukan satu hal untuk mengatur wadah iframe dengan lebar dan tinggi, setel properti CSS

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

.iframe-container iframe{
     width:100%;
     height:100%;
     object-fit:contain;
}
Aslam khan
sumber
Bagaimana jika konten lebih kecil dari 600px?
Ferit
jika tinggi 600 maka iframe tidak akan terpengaruh karena saya atur objeknya pas berisi
Aslam khan
Wadah tidak bisa lebih kecil dari 600px, ini merupakan pelanggaran terhadap persyaratan yang dijelaskan dalam pertanyaan.
Ferit