Every screenshot extension workflow I've used shares the same flaw: you take a picture of the viewport, then crop. The capture is never of the thing you wanted — it's of a rectangle that happens to contain it. I spent the last months building Snaptura, a Chrome MV3 extension that captures exact DOM elements and turns them into polished exports. This post walks through the four problems that actually took engineering — skip the marketing, steal the techniques. 1. Capture at the device's resolution, not the CSS fantasy t.captureVisibleTab() gives you a bitmap of the viewport. The trap: a 400×300px card on a laptop with devicePixelRatio: 2 is not 400×300 in that bitmap — it's 800×600, and the browser helpfully encodes the DPR into the PNG so naive consumers render it double-sized or blurry. The fix is boring and essential: every capture carries its DPR, and every crop rectangle is multiplied before slicing: const dpr = window.devicePixelRatio; const rect = element.getBoundingClientRect(); // Slice the viewport bitmap in *device* pixels: const slice = { x: Math.round(rect.x * dpr), y: Math.round(rect.y * dpr), width: Math.round(rect.width * dpr), height: Math.round(rect.height * dpr), }; // …and remember `dpr` so exports stay 1:1 sharp. Rounding matters. A Math.floor on the wrong edge gives you a 1-device-pixel seam of the neighboring element — invisible in code, very visible in a marketing screenshot. 2. Hover-select is a hit-testing problem "Select the element under the cursor" sounds like document.elementFromPoint — until you try it on real pages with overlays, shadow DOM, and iframes. What worked in the end is closer to what DevTools does: Walk up from the hovered node and offer the meaningful ancestors, not just the immediate one (users almost never want the inner <span>; they want the card). Score candidates by visible area and semantic weight — a <button> inside a <div> should be grabbable as either, with a keyboard shortcut to walk up the chain. Paint the selection overlay in a page-level layer that ignores pointer events, so highlighting never changes the page you're capturing. The heuristic I landed on: the element people mean to select is usually the nearest ancestor whose background differs from its parent's. Cheap, wrong occasionally, but predictable in a way users learn in ten seconds. 3. Full-page captures and sticky-element ghosts Scroll-and-stitch full-page capture has an obvious implementation: scroll by a viewport height, capture, repeat, then blend the overlap. Two real-world ruinizers: Sticky elements stamp themselves over every fold. The fixed header looks fine in fold one and photobombs folds two through nine. The fix is to detect position: sticky/fixed elements before scrolling and, per fold, either hide them or pin their correct-at-this-scroll-offset version. Detection is a getComputedStyle walk; the subtle part is compositing them back at their natural position in the final fold, where users expect to see the header. Scrolling isn't pixel-exact. Page zoom, fractional scroll positions, and scroll-behavior: smooth all break naive stitching. Forcing scrollTo({ top: y, behavior: 'instant' }) and waiting for both the scroll event and a rAF after it eliminated 95% of the seam artifacts. 4. Redact before the pixels exist Pixel-level redaction (draw a black box over the region) leaves the secret in the file — metadata strips help, but screenshot tools that blur and undo are a meme for a reason. The alternative: redact the DOM itself. Replace the text node with a placeholder element of identical box metrics, capture, then restore the original node: function redact(node) { const box = node.getBoundingClientRect(); const veil = document.createElement('div'); Object.assign(veil.style, { position: 'fixed', left: `${box.x}px`, top: `${box.y}px`, width: `${box.width}px`, height: `${box.height}px`, background: 'currentColor', opacity: '0.85', }); node.hidden = true; document.documentElement.appendChild(veil); return () => { veil.remove(); node.hidden = false; }; } Since the sensitive string is never rasterized, it can't leak — not in the PNG, not in an undo stack, nowhere. The cost is that redaction must be declared before capture, which turns out to be a fine UX: you mark regions once, and every future capture of that page respects them. MV3 logistics worth knowing The service worker dies constantly. Any capture pipeline that assumes a long-lived background page breaks. We keep state in chrome.storage, treat the worker as a router, and do the heavy lifting elsewhere. Offscreen documents are the escape hatch. GIF encoding and video compositing run in an offscreen document with OffscreenCanvas + Workers — keeping the service worker free and alive. tabCapture for viewport recording requires the user gesture and the permission choreography to line up exactly; get that right once and recording "just works." That's the core of it. If you want to see the end result of these techniques, the extension is Snaptura — free, watermark-free, with a Pro tier and a lifetime option. But the four patterns above stand on their own for anything you're building with captureVisibleTab. What did I miss? If you've shipped capture tooling, I'd love to hear which part hurt most.