How would you go about detecting the height of content within an iframe?

twitter logo github logo ・1 min read

Let's say you have an iframe and you'd like to detect the height of its window once the content is loaded, how would you go about doing that?

This approach seems satisfactory, but I feel like this is a domain with possible edge cases and gotchas, so I'm super open to differing approaches.

You can retrieve the height of the IFRAME's content by using contentWindow.document.body.scrollHeight

After the IFRAME is loaded, you can then change the height by doing the following:

<script type="text/javascript">
  function iframeLoaded() {
      var iFrameID = document.getElementById('idIframe');
      if(iFrameID) {
            // here you can make the height, I delete it first,
twitter logo DISCUSS (10)
markdown guide
 

Since offsetHeight does not take into account non-visible content due to a scrollbar, it can often be significantly different than the true absolute (visible + non-visible) value, which is usually more accurate with scrollHeight. This, in turn, usually closely matches with getBoundingClientRect(), with the main difference being that scrollHeight will round to an integer, whereas bounding will return a fractional (float). [Edited: Thanks Nicholas]

So my recommendation would be for getBoundingClientRect as the most accurate.

As for implementation details, it seems like the most common pattern is to measure within the iframe using either document.body.offsetHeight or document.body.getBoundingClientRect().height, and then pass the height to the parent window via postMessage. The parent window has an listener for it, and upon receiving it, resizes the iframe.

For a practical example, JSFiddle uses the patterns I've outlined to dynamically resize their fiddle embeds which you can generate and place onto sites. You can see that in the parent window embed code, they have set up an event listener. And within the iframe embed JS, they get the height with getBoundingClientRect() and pass it via postMessage:

Embed.prototype.setHeight = function(element) {
  var activeTab, height;
  activeTab = element.getBoundingClientRect();
  height = activeTab.height;
  return this.pushMessage("embed", {
    slug: slug,
    height: height
  });
};

// Code for pushMessage just uses postMessage:
Utils.prototype.pushMessage = function(name, value) {
  if (value == null) {
    value = {};
  }
  return window.parent.postMessage([name, value], "*");
};

A library that was linked to in the StackOverflow you posted, iframe-resizer, uses postMessage and defaults to using offsetHeight.

 

Neither offsetHeight nor getBoundingClientRect include margins. They never include absolutely positioned elements with no positioned ancestor, and will return a height of 0 if their computed display value is "contents". scrollHeight on the root element has none of those problems.

 

Thanks for the comment; I think I got offsetHeight and scrollHeight mixed up in my head, and it looks like scrollHeight is generally better than offset since it is computed for the height of the element as if all of it were visible, even if in reality you need to scroll to view its entirety. I've updated my original comment to address this.

However, as for the comment about margins, can you provide a source? Because based on the MDN docs, it certainly looks like scrollHeight is not supposed to include margins either. And for the 'display:contents' issue, I was not able to reproduce:
Root element - Changing to display:contents

However, when changing display to contents for a non-root element, all of the methods, including scrollHeight, seemed to fail:
non root element - changing to display:contents

All I can really say with 100% certainty is this stuff is complicated! I found this interesting thread from the Google AMP project which shows how even the "big guys" have trouble with this.

Yes, yes and yes. MDN is slightly lacking in this area, and you really need to read the actual specs.

First, on display:contents, I specfically said the "computed value" because the relevant spec says "The root element’s display type is always blockified. Additionally, a display of contents computes to block on the root element."

Second, on the matter of margins, it differs depending on whether we're dealing with the root element or not. Step 4 of the rules for the calculation of the scrollHeight say that the root element is treated specially. It says that "If the element is the root element and document is not in quirks mode return max(viewport scrolling area height, viewport height)." The calculation of the viewport scrolling area is quite detailed but an abbreviation for the bottom edge says it's "The bottom-most edge of the bottom edge of the initial containing block and the bottom margin edge of all of the viewport’s descendants' boxes." (my emphasis) Note that the initial containing block itself doesn't have margins.

If you compare that with the rules for other elements, then you see that the margins of the target element are not counted, but the margins of its descendant boxes are counted.

Thanks for the clarification! Much appreciated since the specs can be rather dense and hard to understand at a glance.

 

I cheat and let somebody else do all the hard work for me. ¯\(ツ)/¯ Highly recommend the iFrame Resizer library when dealing with iFrames of dynamic height. It works basically the way you've outlined, by setting the height of the iFrame itself once the content has loaded through a script that is included on the parent site. I've never had to adjust any of the default settings to get the library to work for my use case, and I use it across a wide variety of sites/in different contexts.

 
 

Well, yeah, those answers, (including iframe-resizer) are easy to break. But you need to define "the height of its window" more tightly. CSSOM View has a concept called the Scrolling area of the Viewport which I suspect is close to what you want, but it's not clear.

 

Interested by the answer since that's something that needs to be fixed in the feature I introduced for dev.to (runnable kotlin snippets)

github.com/thepracticaldev/dev.to/...

 

Again, the problem needs defining. I don't believe a perfect solution is possible. To get the size of the iframe's content, an initial height of the iframe must be set. Which means that the content's height can be affected via the vh unit and media queries. There's an unresolvable circular dependency there.

So you're looking for a solution which is "good enough" for your scenario. In which case, any of the proposed solutions might do. But if you can identify why a particular solution doesn't work for you, it might be possible to point you to a better one for your scenario.

Classic DEV Post from May 11

Handling Array Duplicates Can Be Tricky

Handling Array Duplicates Can Be Tricky

Ben Halpern profile image
A Canadian software developer who thinks he’s funny.