Dev.to WebDev 🛠 Dev 👁 0 📖 3 min read

The iframe Auto-Resize Handshake: Engineering Embeddable Widgets That Don't Fight Their Host

If you have ever embedded a third-party widget in an iframe, you know the two failure modes: an inner scrollbar that swallows half the widget, or a 2000-pixel-tall frame with 400 pixels of content. The usual fixes are ha

If you have ever embedded a third-party widget in an iframe, you know the two failure modes: an inner scrollbar that swallows half the widget, or a 2000-pixel-tall frame with 400 pixels of content. The usual fixes are hacky (hardcoded heights, scrolling=no and prayer). This post is the full engineering notes for the embed surface I run at Practical Web Tools — every calculator on the site (400+, across finance, math, health and science) is embeddable as an auto-sizing iframe, and the whole sizing layer is ~60 lines of framework-free ES5.

The handshake

The framed page measures itself; the host page applies the height. Three pieces:

Inside the frame — measure on load, on every DOM change, and on a few early intervals (cache-cold renders change height late):

var send = function (force) {
  var h = Math.ceil(Math.max(
    document.documentElement.offsetHeight,
    document.body.offsetHeight || 0));
  if (!h || (h === last)) return;
  last = h;
  parent.postMessage({ type: 'pwt-embed:resize', height: h }, '*');
};
new ResizeObserver(function () { send(); }).observe(document.body);
// plus: DOMContentLoaded, load, and 500ms x 40 early ticks

On the host — one listener, matched to the right frame (never document.querySelectorAll('iframe')[0]):

window.addEventListener('message', function (e) {
  if (e.origin !== 'https://practicalwebtools.com') return;
  if (e.data.type !== 'pwt-embed:resize') return;
  var h = Math.min(Math.max(Math.round(e.data.height), 160), 20000);
  // find the iframe whose contentWindow === e.source, then:
  iframe.style.height = h + 'px';
});

The late-script case — if the host's resizer script loads after the frame already reported (async scripts, bottom-of-page pastes), the frame's message is gone. Fix: the host pings back {type:'pwt-embed:ping'} on load and on DOMContentLoaded, and the frame force-sends on ping. No timing assumptions in either direction.

The feedback loop that ate my afternoon

Apply the reported height → the framed page reflows → it reports a new height → apply → reflow. This happens whenever the framed document has any viewport-sized element (a min-height:100vh hero is the classic). The loop's signature: the same height delta each round. So the frame-side code tracks the delta; if |new - last| repeats five times, height reporting stops. The widget might be slightly wrong-sized at that point, but it stops growing forever, which is the difference between "a bit short" and "the page is 40,000 pixels long."

Third-party frames deny your storage

Browsers throw on localStorage access inside cross-origin iframes (Safari is the strictest). A calculator that persists its inputs — the retirement calculator keeps your balances, the GPA calculator keeps your courses — crashes on load inside a frame unless you shim it before any tool code runs:

try { localStorage.setItem('__probe', '1'); localStorage.removeItem('__probe'); }
catch (e) {
  var mem = {};
  window.localStorage = shim(mem);   // getItem/setItem/removeItem/key/length
}

Probe, don't feature-detect once and cache: the probe itself is the detection.

Clicks, links, and not hijacking the host

Inside a frame, a plain link either traps the user in the widget or navigates the host page out from under them. Neither is acceptable. The embed bootstrap intercepts left-clicks on same-site links and rewrites them to window.open(href, '_blank', 'noopener'). Off-site links keep their authored target. The host page never navigates because of something inside the widget.

Ads and embeds don't mix

The site is ad-supported; embeds load zero ads, zero analytics trackers, zero site chrome. This is not altruism — a widget that renders ads onto someone else's article gets ripped out, and rightly so. The framed document drops to a bare tool with a two-line credit bar ("Free calculator by Practical Web Tools" + "Open full calculator"). That credit link is the entire business model, and it ships inside the snippet, in the host page's HTML, as a plain anchor — not injected by script after the fact.

Themes, honestly

"Follow the visitor's OS" paints dark widgets into light articles — the host picked their page's look, not the visitor's OS. Widgets default light and accept an explicit &theme=dark. Boring, correct.

Try it

Every calculator page (say the moon phase tool or the body-fat calculator) shows its own copy-paste snippet with a live preview at the bottom of the page; the flagship hub with the nine one-line script widgets (no iframe at all) is practicalwebtools.com/embed. The MIT-licensed scripts are on GitHub if you'd rather self-host the whole thing.

If you hit a sizing edge case I didn't — especially CSS-driven oscillation — I'd genuinely like to hear about it in the comments.

Tags: javascript, webdev, iframe, widgets

📰 Read the original article on Dev.to WebDev

Originally published by Dev.to WebDev. Aggregated on AIWithGhost for educational purposes — full credit and traffic to the original publisher.