Skip to content

fix: render iframe layouts at their on-screen resolution - #2728

Open
aron-intframe wants to merge 1 commit into
slidevjs:mainfrom
aron-intframe:fix/iframe-layout-blur
Open

fix: render iframe layouts at their on-screen resolution#2728
aron-intframe wants to merge 1 commit into
slidevjs:mainfrom
aron-intframe:fix/iframe-layout-blur

Conversation

@aron-intframe

Copy link
Copy Markdown

Fixes #2281 (same underlying cause as #1271, which was closed for lack of reproduction).

Root cause

Slidev renders slides at their design size (default 980x552) and fits them to the window with transform: scale(...) on the slide container. Regular DOM content is re-rasterized by the browser under that scale, so it stays sharp. But iframe content that sizes its own raster from the embedded viewport — canvas apps like Excalidraw, which set canvas.width = cssSize * devicePixelRatio — renders at the slide's design size and is then bitmap-upscaled by the slide scale. At a 1920x1080 window the scale is ~1.9565, so every canvas pixel is smeared across ~2 screen pixels. That matches the issue screenshots: the shared Excalidraw link is sharp standalone but blurry embedded.

Fix

Let the iframe in the iframe / iframe-left / iframe-right layouts lay out at its real on-screen size and counter the slide scale, using the inverse-scale idiom this codebase already uses for .rough-annotation and Monaco's context menu:

.slidev-layout-iframe {
  --slidev-iframe-raster: max(var(--slidev-slide-scale, 1), 1);
  width: calc(100% * var(--slidev-iframe-raster));
  height: calc(100% * var(--slidev-iframe-raster));
  transform: scale(calc(var(--slidev-iframe-scale, 1) / var(--slidev-iframe-raster)));
  transform-origin: top left;
}
  • Scales below 1 are clamped with max(scale, 1) on purpose, so scaled-down views (overview, presenter thumbnails) keep the current behavior.
  • The layouts' scale prop keeps its meaning; it now feeds the same formula via --slidev-iframe-scale.

Verified with the reproduction from the issue

Loaded the exact Excalidraw link from the issue in layout: iframe, headless Chromium at 1920x1080 (dpr 1), and measured the blue text region of the drawing in before/after screenshots:

iframe layout width displayed width net raster scale dark (<100) ink pixels gradient energy / px
before 980 1917.4 1.9565 (bitmap upscale) 4123 5.47
after 1917 1917.4 1.0002 8019 6.35

Strokes resolve to dark cores again instead of gray smears. Because Excalidraw fits the scene to its viewport, the apparent size of the drawing barely changes — only sharpness does.

I also measured with a local page that mimics Excalidraw's canvas handling (backing store = CSS size x devicePixelRatio) drawing a 1px stripe pattern: before, the pattern is stretched to a ~3.9px period (1px detail cannot survive the upscale); after, the 2px period is preserved pixel-for-pixel, and the embedded render differs from opening the same page directly in the browser by a mean of 2.8/255 per pixel.

The new Cypress spec asserts the iframe's layout width matches its displayed width at a 2x viewport and stays at the design size at 0.5x. Against the previous behavior it fails with displayed width: expected 600 to be close to 1960 +/- 2.

Behavior note (intentional, please review)

When a slide is scaled up, the embedded page now sees its real on-screen viewport (e.g. 1917px at fullscreen 1920) instead of the design-size 980px. Responsive pages lay out the way they would in a standalone browser window of that size — which is what the issue reporter expected — but decks that relied on the old "980px layout blown up ~2x" look will see embedded pages render with more content at a smaller apparent size (the scale prop can compensate).

One remaining limit: when the fitted slide has a fractional scale/offset (e.g. 1920 window gives scale 1.9565 and a 1.3px centering offset), pixel-aligned 1px patterns lose some contrast to subpixel blending. That is inherent to non-integer compositing and far less visible than the previous 2x upscale.

Ran pnpm verify locally (build, typecheck, lint, unit tests all pass) plus the new Cypress spec.

@netlify

netlify Bot commented Aug 26, 2026

Copy link
Copy Markdown

Deploy Preview for slidev ready!

Name Link
🔨 Latest commit 023c903
🔍 Latest deploy log https://app.netlify.com/projects/slidev/deploys/6a8f43ae9301010008e78ea9
😎 Deploy Preview https://deploy-preview-2728--slidev.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.
🤖 Make changes Run an agent on this branch

To edit notification comments on pull requests, go to your Netlify project configuration.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Rendering an iframe from excalidraw shared link that alone looks good, become blurred. Excalidraw+

1 participant