Responsive Testing Tool: iframe Sandbox and Device Simulation#

Testing responsive designs usually means switching devices in Chrome DevTools or opening multiple tabs. I built a responsive testing tool that consolidates common device sizes with one-click switching and real-time preview.

iframe Sandbox: Loading External Pages Securely#

The core is using iframe to load target websites, but direct embedding has security restrictions:

<iframe
  src="https://example.com"
  sandbox="allow-scripts allow-same-origin allow-forms allow-popups"
/>

The sandbox attribute is critical:

  • allow-scripts: Allow JavaScript execution (required by most sites)
  • allow-same-origin: Allow same-origin requests (load resources, API calls)
  • allow-forms: Allow form submissions
  • allow-popups: Allow popups (window.open)

If the target site sets X-Frame-Options: DENY, iframe will refuse to load. This is a site security policy we can’t bypass. However, most public sites support embedding.

Device Simulation: Size and Scaling#

The key to responsive testing is simulating different device viewports:

const devices = [
  { name: "iPhone SE", width: 375, height: 667 },
  { name: "iPhone 14", width: 390, height: 844 },
  { name: "iPhone 14 Pro Max", width: 430, height: 932 },
  { name: "iPad Mini", width: 768, height: 1024 },
  { name: "Desktop HD", width: 1920, height: 1080 },
]

Directly setting iframe size causes issues—if a site is 1920px wide, it will show horizontal scrollbars in a 375px container.

The solution is scaling:

const [scale, setScale] = useState(1)

<div style={{
  width: deviceWidth,
  height: deviceHeight,
  transform: `scale(${scale})`,
  transformOrigin: "top left",
}}>
  <iframe src={url} className="w-full h-full" />
</div>

transformOrigin: "top left" ensures scaling starts from top-left, not the default center point.

Scale calculation:

// Auto-scale to fit container
const containerWidth = window.innerWidth - 80  // Reserve margin
const containerHeight = window.innerHeight - 200

const autoScale = Math.min(
  containerWidth / deviceWidth,
  containerHeight / deviceHeight,
  1  // Max 100%
)

setScale(autoScale)

This lets users see the complete device preview instead of a truncated view.

Device Rotation: Swapping Width and Height#

Mobile devices support portrait/landscape switching. Implementation is simple:

const [isRotated, setIsRotated] = useState(false)

const displayWidth = isRotated ? deviceHeight : deviceWidth
const displayHeight = isRotated ? deviceWidth : deviceHeight

The real challenge is forcing iframe content to rotate. If the target site isn’t responsive, rotating the iframe container won’t change internal layout.

A solution uses CSS transform: rotate(90deg):

<iframe
  style={{
    width: deviceHeight,  // Adjust size before rotation
    height: deviceWidth,
    transform: "rotate(90deg)",
    transformOrigin: "center center",
  }}
/>

But this causes position offset, requiring translate correction. In practice, let the site respond to container size changes rather than forcing rotation.

URL Input and Protocol Completion#

Users often omit https:// when entering URLs. We need auto-completion:

const handleUrlSubmit = (e: React.FormEvent) => {
  let finalUrl = url
  if (!url.startsWith("http://") && !url.startsWith("https://")) {
    finalUrl = "https://" + url
    setUrl(finalUrl)
  }
}

Why default to https://? Modern sites mostly support HTTPS, and browsers auto-redirect. Forcing HTTP might trigger redirects, causing iframe load failures.

Performance: iframe Lazy Loading#

When users switch devices frequently, each switch reloads the iframe, wasting bandwidth and time.

Optimization: use key attribute to control iframe remounting:

const [iframeKey, setIframeKey] = useState(0)

// Only reload on URL change
const handleUrlSubmit = () => {
  setIframeKey(prev => prev + 1)
}

<iframe key={iframeKey} src={url} />

Further optimization: cache iframe content:

// Use react-query to cache page content
const { data: html } = useQuery({
  queryKey: ['page', url],
  queryFn: () => fetch(url).then(r => r.text()),
  staleTime: 5 * 60 * 1000,  // 5-minute cache
})

<iframe srcDoc={html} />

But srcDoc loses the original URL, breaking relative paths. This approach only works for pure static pages.

Responsive Breakpoint Detection#

Besides manual device switching, we can implement automatic breakpoint detection:

const breakpoints = [
  { name: "xs", min: 0, max: 639 },
  { name: "sm", min: 640, max: 767 },
  { name: "md", min: 768, max: 1023 },
  { name: "lg", min: 1024, max: 1279 },
  { name: "xl", min: 1280, max: Infinity },
]

function getCurrentBreakpoint(width: number) {
  return breakpoints.find(bp => width >= bp.min && width <= bp.max)?.name
}

// Detect on iframe resize
<iframe
  onLoad={() => {
    const iframeWidth = iframe.contentWindow?.innerWidth
    const bp = getCurrentBreakpoint(iframeWidth)
    console.log(`Current breakpoint: ${bp}`)
  }}
/>

Users can see how the site behaves at different breakpoints without memorizing pixel values.

Real-World Case: Batch Screenshots#

A common requirement for responsive testing tools is batch generating screenshots for different devices:

async function captureAllDevices(url: string) {
  const screenshots = []

  for (const device of devices) {
    // Create temporary iframe
    const iframe = document.createElement('iframe')
    iframe.src = url
    iframe.style.width = `${device.width}px`
    iframe.style.height = `${device.height}px`
    document.body.appendChild(iframe)

    // Wait for load
    await new Promise(resolve => {
      iframe.onload = resolve
    })

    // Screenshot with html2canvas
    const canvas = await html2canvas(iframe.contentDocument.body)
    screenshots.push({
      device: device.name,
      dataUrl: canvas.toDataURL('image/png')
    })

    document.body.removeChild(iframe)
  }

  return screenshots
}

The limitation is cross-origin restrictionshtml2canvas can’t access cross-origin iframe content. The solution is using Puppeteer for server-side screenshots, then returning image URLs.

The Result#

Based on these ideas, I built: Device Simulator

Features:

  • 8 preset devices (iPhone/iPad/Desktop)
  • Custom size input
  • Portrait/landscape switching
  • Scale adjustment (25%-100%)
  • Real-time preview

The implementation isn’t complex, but requires handling iframe security, scaling algorithms, and performance optimization. Hope this helps.


Related: CSS Gradient Generator | Flexbox Layout Generator