Responsive Testing Tool: iframe Sandbox and Device Simulation
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 submissionsallow-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 restrictions—html2canvas 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