Dynamic Viewports in Iframes: Showcase Responsive Design

April 5, 2024

For web designers and developers, embedding our work within a webpage is common practice, and iframes are a popular choice. But a static iframe viewport can downplay the very feature you might be most proud of - your website's responsive design.

This blog post will guide you through implementing dynamic viewports in iframes, similar to how I did in my portfolio. This technique will allow you to effectively communicate the adaptability of your website across various screen sizes.

React Solution

I'll be using React for this example, but you can adapt the concepts to any other framework or vanilla JavaScript.

We'll create a ResponsiveIframe component accepting two props:

  • src: The URL of the website you want to display in the iframe.
  • viewportWidth: The width of the viewport in pixels.
  • Addintional props can be added to customize the iframe further.
interface ResponsiveIframeProps {
  src: string
  viewportWidth: number
}

const ResponsiveIframe = ({ src, viewportWidth }: ResponsiveIframeProps) => {
  return (
    <iframe
      src={src}
      style={{
        width: viewportWidth,
      }}
    />
  )
}

Using this component in its current state would display the iframe with a fixed width, likely overflowing the page. We want the iframe to fill the width and height of its container.

We'll achieve this using the CSS scale property. We'll calculate the scale factor based on the parent container's width and the viewport width, applying it to the iframe.

First, we need a reference to the iframe element. In React, we can use the useRef hook. In vanilla JavaScript, use document.querySelector or document.getElementById.

const iframeRef = useRef<HTMLIFrameElement>(null)

return (
  <iframe
    ref={iframe}
    src={src}
    style={{
      width: viewportWidth,
    }}
  />
)

With a reference to the iframe, we can calculate the scale factor and apply it. The useCallback hook (not strictly necessary) helps prevent creating a new function on every render.

const resize = useCallback(() => {
  const iframe = iframeRef.current
  const wrapper = iframe?.parentElement

  // Avoid errors when the iframe isn't rendered yet
  if (!iframe || !wrapper) return

  // Get the width of the wrapper and the iframe
  const wrapperWidth = wrapper.offsetWidth
  const iframeWidth = iframe.offsetWidth

  // Scale is the ratio between the wrapper width and the iframe width
  const scale = wrapperWidth / iframeWidth
  iframe.style.transform = `scale(${scale})`

  // Set the iframe's height to match the parent's height while applying the responsive scale
  const wrapperHeight = wrapper.offsetHeight
  const height = wrapperHeight / scale
  iframe.style.height = `${height}px`
}, [])

We need to call the resize function whenever the window is resized and on initial render. The useEffect hook achieves this.

useEffect(() => {
  resize()
  window.addEventListener('resize', resize)
  return () => window.removeEventListener('resize', resize)
}, [resize])

That's it! Use the ResponsiveIframe component to showcase your work responsively.

Full Code

interface ResponsiveIframeProps {
  src: string
  width: number
}

const ResponsiveIframe = ({ src, width }: ResponsiveIframeProps) => {
  const iframeRef = useRef<HTMLIFrameElement>(null)

  const resize = useCallback(() => {
    const iframe = iframeRef.current
    const wrapper = iframe?.parentElement

    if (!iframe || !wrapper) return

    const wrapperWidth = wrapper.offsetWidth
    const iframeWidth = iframe.offsetWidth

    const scale = wrapperWidth / iframeWidth
    iframe.style.transform = `scale(${scale})`

    const wrapperHeight = wrapper.offsetHeight
    const height = wrapperHeight / scale
    iframe.style.height = `${height}px`
  }, [])

  useEffect(() => {
    resize()
    window.addEventListener('resize', resize)
    return () => window.removeEventListener('resize', resize)
  }, [resize])

  return (
    <iframe
      ref={iframeRef}
      src={src}
      style={{ width, transformOrigin: 'top left' }}
    />
  )
}

Usage:

const [viewportWidth, setViewportWidth] = useState<number>(1220)

return (
  <div>
    <ResponsiveIframe src="https://baraa.app" width={viewportWidth} />
    <div className="buttons">
      <button onClick={() => setViewportWidth(375)}>Mobile</button>
      <button onClick={() => setViewportWidth(768)}>Tablet</button>
      <button onClick={() => setViewportWidth(1220)}>Desktop</button>
    </div>
  </div>
)

Vanilla JavaScript Solution

If you're not using React, you can achieve the same result using vanilla JavaScript. Here's how you can do it:

<div class="iframe-wrapper">
  <iframe id="iframe" src="https://baraa.app"></iframe>
</div>

<script>
  const iframe = document.getElementById('iframe')

  const resize = () => {
    const wrapper = iframe.parentElement

    if (!wrapper) return

    const wrapperWidth = wrapper.offsetWidth
    const iframeWidth = iframe.offsetWidth

    const scale = wrapperWidth / iframeWidth
    iframe.style.transform = `scale(${scale})`

    const wrapperHeight = wrapper.offsetHeight
    const height = wrapperHeight / scale
    iframe.style.height = `${height}px`
  }

  resize()
  window.addEventListener('resize', resize)
</script>

Get Updates

Get the latest updates on my projects, articles, and more.