Jak sprawić, by iframe reagował bez założenia współczynnika proporcji?

14

Jak sprawić, by iframe reagował bez zakładania współczynnika proporcji? Na przykład treść może mieć dowolną szerokość lub wysokość, która jest nieznana przed renderowaniem.

Uwaga: możesz używać Javascript.

Przykład:

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

Zmień to iframe-containertak, aby jego zawartość ledwo mieściła się w środku bez dodatkowej przestrzeni, innymi słowy, jest wystarczająco dużo miejsca na treść, aby można ją było wyświetlić bez przewijania, ale bez nadmiaru miejsca. Kontener idealnie otacza ramkę iframe.

To pokazuje, jak sprawić, by iframe reagował, zakładając, że proporcje treści wynoszą 16: 9. Ale w tym pytaniu współczynnik kształtu jest zmienny.

ferit
źródło
1
Pozwól mi wyjaśnić, edytując @TravisJ
ferit
2
Możliwe jest obliczenie wymiarów treści w ramce iframe, ale to, jak dokładnie należy to zrobić, zależy nieco od samej treści i dużej ilości używanego CSS (absolutnie trudno jest zmierzyć zawartość absolutnie pozycjonowaną). Jeśli używasz pliku resetowania css, wynik różni się od strony, z której plik resetowania nie jest używany, a także różni się w zależności od przeglądarki. Ustawienie wielkości elementu iframe i jego elementu nadrzędnego jest banalne. Potrzebowalibyśmy więc więcej informacji na temat struktury strony, w szczególności nazwy używanych plików resetowania CSS (lub rzeczywistego CSS, jeśli nie używasz żadnych wspólnych plików).
Teemu
2
Czy masz kontrolę nad dokumentem załadowanym do ramki iframe? Mam na myśli, że jeśli nie, to nic nie możesz zrobić. Wszystko musi być zrobione wewnątrz dokumentu iframe (lub poprzez dostęp do tego dokumentu), w przeciwnym razie nie jest to w ogóle możliwe.
Teemu
1
Nie, to nie jest możliwe, patrz developer.mozilla.org/en-US/docs/Web/Security/... . Strona główna nie może uzyskać dostępu do dokumentu między domenami w ramce iframe (i odwrotnie) ze względów bezpieczeństwa, można to obejść tylko wtedy, gdy masz kontrolę nad dokumentem wyświetlanym w ramce iframe.
Teemu
1
... 15 lat internetu mówi, że to niemożliwe, nie wystarczy? Czy naprawdę potrzebujemy nowego pytania na ten temat?
Kaiido

Odpowiedzi:

8

Nie można wchodzić w interakcje z ramką iFrame innego pochodzenia przy użyciu Javascript, aby uzyskać jej rozmiar; jedynym sposobem, aby to zrobić jest użycie window.postMessagewraz z targetOriginzestawem do domeny lub wildchar *od źródła iFrame. Możesz proxy zawartości różnych witryn źródłowych i używać srcdoc, ale jest to uważane za włamanie i nie będzie działać z SPA i wieloma innymi bardziej dynamicznymi stronami.

Rozmiar iFrame tego samego pochodzenia

Załóżmy, że mamy dwa iFrame tego samego pochodzenia, jeden o małej wysokości i stałej szerokości:

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

i iFrame o dużej wysokości:

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

Możemy uzyskać rozmiar iFrame przy loadzdarzeniu, korzystając z iframe.contentWindow.documenttego, który wyślemy do okna nadrzędnego za pomocą 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>

Otrzymamy odpowiednią szerokość i wysokość dla obu ramek iFrame; możesz to sprawdzić online tutaj i zobaczyć zrzut ekranu tutaj .

Hack innego rozmiaru iFrame pochodzenia ( niezalecany )

Opisana tutaj metoda to hack i należy jej użyć, jeśli jest to absolutnie konieczne i nie ma innej możliwości; nie będzie działać dla większości dynamicznie generowanych stron i SPA. Metoda pobiera kod źródłowy HTML strony za pomocą serwera proxy, aby ominąć zasady CORS ( cors-anywherejest to prosty sposób na utworzenie prostego serwera proxy CORS i ma wersję demonstracyjną onlinehttps://cors-anywhere.herokuapp.com ), a następnie wstrzykuje kod JS do tego HTML, aby użyć postMessagei wysłać rozmiar iFrame do dokumentu nadrzędnego. Obsługuje nawet zdarzenie iFrame resize(w połączeniu z iFramewidth: 100% ) i przesyła rozmiar iFrame z powrotem do elementu nadrzędnego.

patchIframeHtml:

Funkcja załatać i wstrzyknąć kod HTML iFrame zwyczaj JavaScript, który będzie używany postMessagedo wysyłania rozmiar iFrame do rodzica na loadi na resize. Jeśli parametr ma wartość origin, wówczas <base/>element HTML zostanie dodany do nagłówka przy użyciu tego początkowego adresu URL, dlatego też identyfikatory URI HTML /some/resource/file.extzostaną poprawnie pobrane przez pierwotny adres URL wewnątrz ramki 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:

Funkcja pozwalająca uzyskać HTML strony pomijający CORS za pomocą proxy, jeśli useProxyustawiony jest parametr. Mogą istnieć dodatkowe parametry, które zostaną przekazane postMessagepodczas wysyłania danych o rozmiarze.

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

Funkcja obsługi zdarzenia komunikatu jest dokładnie taka sama, jak w opcji „Rozmiar iFrame tego samego pochodzenia” .

Możemy teraz załadować domenę pochodzącą z różnych źródeł w ramce iFrame z wprowadzonym niestandardowym kodem 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>

I dostosujemy ramkę iFrame do jej zawartości na pełną wysokość bez żadnego przewijania w pionie, nawet przy użyciu overflow-y: autoelementu iFrame ( powinno być overflow-y: hiddentak, aby pasek przewijania nie migał przy zmianie rozmiaru ).

Możesz to sprawdzić online tutaj .

Ponownie zauważmy, że jest to hack i należy go unikać ; nie możemy uzyskać dostępu do dokumentu iFrame Cross-Origin ani wstrzykiwać żadnych rzeczy.

Christos Lytras
źródło
1
Dzięki! Świetna odpowiedź.
ferit
1
Dziękuję Ci. Niestety cors-anywherew tej chwili nie działa, więc nie możesz wyświetlić strony demonstracyjnej . Nie chcę dodawać zrzutu ekranu strony zewnętrznej z powodów związanych z prawami autorskimi. Dodam inny serwer proxy CORS, jeśli pozostanie on długo wyłączony.
Christos Lytras
2

Powoduje to wiele komplikacji w obliczaniu wielkości zawartości w ramce iframe, głównie ze względu na to, że CSS umożliwia robienie rzeczy, które mogą zepsuć sposób pomiaru wielkości zawartości.

Napisałem bibliotekę, która zajmuje się tymi wszystkimi rzeczami, a także działa między domenami, może być to pomocne.

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

David Bradshaw
źródło
1
Fajne! Sprawdzę to!
ferit
0

Moje rozwiązanie jest na GitHub i JSFiddle .

Przedstawienie rozwiązania dla responsywnego iframe bez założenia współczynnika proporcji.

Celem jest zmiana rozmiaru iframe, gdy jest to konieczne, innymi słowy, gdy okno jest zmieniane. Odbywa się to za pomocą JavaScript, aby uzyskać nowy rozmiar okna i w konsekwencji zmienić rozmiar iframe.

Uwaga: nie zapomnij wywołać funkcji zmiany rozmiaru po załadowaniu strony, ponieważ sama wielkość okna nie jest zmieniana po załadowaniu strony.

Kod

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;

Spędziłem nad tym trochę czasu. Mam nadzieję, że ci się spodoba =)

Edytuj To prawdopodobnie najlepsze ogólne rozwiązanie. Jednak gdy jest bardzo mały, element iframe nie ma odpowiedniego rozmiaru. Jest to specyficzne dla używanego elementu iframe. Nie jest możliwe zakodowanie poprawnej odpowiedzi na to zjawisko, chyba że masz kod iframe, ponieważ twój kod nie ma sposobu, aby wiedzieć, który rozmiar jest preferowany przez iframe, aby był poprawnie wyświetlany.

Tylko niektóre hacki, takie jak ten zaprezentowany przez @Christos Lytras, mogą dać radę, ale nigdy nie zadziała dla każdego elementu iframe. Tylko w określonej sytuacji.

Onyr
źródło
Dzieki za probe! Jednak nie działa zgodnie z oczekiwaniami. Gdy zawartość elementu iframe jest niewielka, kontener nadal ma dodatkową przestrzeń. Zobacz: jsfiddle.net/6p2suv07/3 Zmieniłem src iframe.
ferit
Nie jestem pewien, czy rozumiem twój problem z „dodatkową przestrzenią”. Minimalny rozmiar okna to 100 pikseli szerokości. Ramka iframe pasuje do kontenera. Układ wewnątrz elementu iframe nie jest pod moją kontrolą. Odpowiedziałem na twoje pytanie. Jeśli chcesz uzyskać dodatkową pomoc,
edytuj swoją
Umieść zdjęcie i opisz je
Onyr
iframe zajmuje całą przestrzeń, jaką daje mu kontener, chodzi o to, że gdy zawartość iframe jest mała, nie zajmuje całej przestrzeni, jaką może zająć. Tak więc kontener powinien dawać wystarczająco dużo miejsca dla iframe, nie mniej więcej.
ferit
W przypadku wysokości ramki iframe wynika to z faktu, że wysokość przestrzeni jest ograniczona przez okno w moim przykładzie. To jest to? Twój przykład jest specyficzny dla iframe, którego używasz, podałem ogólną odpowiedź, którą należy zaakceptować. Oczywiście możliwe jest dostosowanie kodu, aby dobrze pasował do twojego iframe, ale wymaga to znajomości kodu iframe
Onyr
-1

Wykonaj jeden zestaw kontenerów iframe, ustaw właściwość CSS dla szerokości i wysokości

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

.iframe-container iframe{
     width:100%;
     height:100%;
     object-fit:contain;
}
Aslam Khan
źródło
Co jeśli zawartość jest mniejsza niż 600 pikseli?
ferit