The Tech Behind ASOShot: Building a Canvas-Based Screenshot Engine
Technical deep-dive into ASOShot's canvas-based screenshot rendering engine. React 19, 2D-canvas rendering, 3D projection, and client-side export architecture.
The Tech Behind ASOShot: Building a Canvas-Based Screenshot Engine
Technical deep-dive into ASOShot's canvas-based screenshot rendering engine. React 19, 2D-canvas rendering, 3D projection, and client-side export architecture.
The Architecture
ASOShot is built with:
- •React 19 + TypeScript for the UI layer
- •React Router 7 in framework mode for routing and SSR
- •TailwindCSS + @ninna-ui/* for styling
- •Lucide React for icons
- •JSZip for ZIP batch export
- •Custom 2D-canvas rendering engine for screenshot export
Everything runs client-side. No server processing, no backend API calls for screenshot rendering. The entire export pipeline happens in the browser.
The Canvas Export Engine
The most interesting technical challenge was rendering screenshots at full resolution. We initially used html-to-image (SVG foreignObject approach), but it couldn't rasterize CSS 3D transforms: Chrome rasterizes preserve-3d content at composited size, causing blur under transform scale.
So we built a custom 2D-canvas renderer:
- •projection.ts: 3D point projection, perspective-warped image rendering, rounded rect tracing
- •drawDevice.ts: flat + 3D device drawing with 40-slice extrusion for realistic edges
- •drawBackground.ts: solid, gradient, image, and panorama strip slicing with patterns and orbs
- •drawCaption.ts: text wrapping, shadow, stroke, background pills, letter spacing via ctx.letterSpacing
- •drawBadge.ts: store badge pills
- •index.ts: orchestrates all renderers at unit=1 for full-resolution export
The engine mirrors the on-screen ScreenshotCanvas geometry exactly, ensuring what you see is what you get.
Fluid Canvas Preview
The on-screen preview uses a fluid canvas approach:
- •ScreenshotCanvas is a fluid aspectRatio box (width 100%, no fixed px)
- •It self-measures rendered width via ResizeObserver + useLayoutEffect
- •Every px-derived value is multiplied by a unit factor (1.0 at export resolution)
- •No ancestor transform: scale is used, avoiding Chrome's preserve-3d rasterization issues
- •CanvasPreview cards size by width only, maintaining consistent aspect ratios
This approach ensures the preview is always crisp, regardless of screen size or zoom level.
3D Device Rendering
3D device frames use a custom rendering approach:
- •On-screen: CSS 3D transforms with preserve-3d for real-time interaction
- •Export: 2D-canvas slice renderer with 40-slice extrusion + perspective-warped front face
- •Edge depth: 16px at export resolution (scaled by unit factor)
- •Perspective: 1200px focal distance for a natural-looking 3D effect
- •Frame radius: parsed from device specs (e.g., '14%/6.5%' for iPhone outer/inner radius)
The 2D-canvas approach produces pixel-perfect 3D exports that match the on-screen preview exactly.
On-Canvas Drag Interaction
Users can drag device frames and captions directly on the canvas:
- •Screen pixels are converted to export pixels via division by the unit factor
- •SelectionRing with corner handles for visual feedback
- •Only the active card in CanvasPreview is interactive
- •Position changes propagate through useProject's updateScreenshot function
This makes the editing experience feel direct and intuitive: no separate panels or controls needed for positioning.
Try the Canvas Engine
Experience the fluid canvas preview and pixel-perfect export for yourself. Free, no sign-up.
Try ASOShot (Free)