Skip to content

Build1 publisher3 min readPublished

Your overlay is in the wrong coordinate space, and scroll is the tell

A Chrome extension author's notes on overlaying designs onto live pages come down to one bug repeated: mixing viewport space with document space, and CSS pixels with device pixels.

The Engineer · Build desk

Drafted by a language model from the sources cited here and checked against its claim ledger before publication. How we use AISend a correction

Illustration accompanying Your overlay is in the wrong coordinate space, and scroll is the tell
Generated illustration

What happened

  • A developer spent a few weeks building a Chrome extension that puts a design frame on top of a running web page so you can see where the implementation drifted, and wrote up four traps that cost real time.
  • The obvious way to draw the overlay is position: fixed, and it is wrong the moment the user scrolls.
  • The page content being compared against lives in document space, while a fixed overlay lives in viewport space.
  • Line the overlay up at the top of the page, scroll down two hundred pixels, and the design has silently slid two hundred pixels relative to the content it is supposed to match; every alignment is gone and it looks like the page is wrong.
  • The fix is to put the overlay in document space: position = absolute, top = 0, left = 0, and transform = translate3d(layer.x px, layer.y px, 0).

Compiled by The EngineerSomething wrong?How this is made

Why it matters

A developer spent a few weeks building a Chrome extension that drops a design frame over a running page so you can see where the implementation drifted, then wrote up the four things that cost real time [1]. The most useful one is not a styling bug at all: an overlay drawn with `position: fixed` lives in viewport space while the content it is measuring lives in document space, so the alignment holds only at the scroll offset where you made it [2][3].

The author's example is the whole argument. Line the design up at the top of the page, scroll down two hundred pixels, and the design has slid two hundred pixels relative to the content it is supposed to match [4]. The error is not noise, it is equal to the scroll depth, which means it grows 1:1 with every wheel event [19]. Nothing throws. The overlay is semi-transparent and roughly the right shape, so what you see is not a broken tool, it is a page that appears to be wrong [4].

The fix is to put the overlay where the content is: `position: absolute`, `top: 0`, `left: 0`, and a `translate3d` for the offset [5]. Anchored at the document origin, the browser scrolls it in step with the content for free, with no scroll listener and no per-frame repositioning [6]. The companion detail is that new layers should be created at `window.scrollX` and `window.scrollY`, not at the document top, or a layer added halfway down a long page lands off-screen and the user concludes the click did nothing [7].

The second trap is the same category of mistake in a different unit. Design exports are routinely 2x or 3x, so a 1440-wide frame arrives as a 2880-pixel PNG [8]. Setting `naturalWidth` as the CSS width, which is the obvious move, renders it at 2880 CSS pixels [9], covering four times the intended area once both dimensions are scaled [18]. When you fetch from a design API you asked for the scale, so you divide it out [10]. When the user drops in a file you cannot: a PNG carries no record of its export scale, and the author's position is that there is no heuristic worth trusting [11]. The single exception he automates is a high-density screenshot, detected by comparing the image width against `clientWidth * devicePixelRatio` inside a tolerance of `max(8, expected * 0.01)` [12]. Everything else stays at true size behind a visible scale control, on the reasoning that a silently mis-scaled overlay is worse than one the user has to adjust [13]. He also keeps source dimensions beside displayed ones and always computes from the source, because scaling relative to the current size accumulates rounding drift until "100%" no longer means the original [14].

The third item is containment, and it is where the two problems separate cleanly. Injected UI is at the mercy of the host stylesheet, including resets, a global `* { box-sizing }`, a stray `img { opacity: 1 }`, or another extension's rules, and a closed shadow root blocks traffic in both directions [15][16]. But the overlay images stay outside it, appended to `document.body`, precisely because they need document coordinates [17]. Style isolation and coordinate placement are different requirements, and one container cannot serve both.

Worth checking in any tool that pins annotations to a page: whether new markers are anchored to the current scroll position [7], whether there is an explicit scale control rather than a guess [13], and whether repeated resizes are computed from source dimensions [14]. All three failures pass code review and produce plausible-looking output.

Loading claim ledger
Loading source directory links
Loading share composer
Loading topic controls
Loading related stories