Build1 publisher3 min readPublished
The screen eyedropper you write in canvas stops at the edge of your own page
getImageData can only ever read pixels your own code painted, and the screen-capture workaround reaches the rest of the display only with a permission prompt and a recording banner. Chromium has shipped window.EyeDropper since late 2021.
The Engineer · Build desk

What happened
- A canvas only holds pixels your own code drew into it, so a getImageData eyedropper keeps reporting the last pixel the cursor crossed inside the page and never sees another window.
- No web API returns the color under the OS cursor, because such an API would let any page read other tabs and the desktop behind them.
- The only in-sandbox way to reach the rest of the display is getDisplayMedia(), which the post estimates at maybe 150 lines of plumbing.
- Chrome, Edge and other Chromium browsers have shipped a system color picker as a Web API, window.EyeDropper, since late 2021.
Compiled by The EngineerSomething wrong?How this is made
Why it matters
- decision The decision is the support matrix: with Firefox and Safari absent and no polyfill the post will endorse, somebody has to choose what those users see where the button would have been.
- constraint The gesture requirement rules out opening the picker from a timer, a promise chain or page load, so any flow that loads state first has to put the user's click last.
- cost Omitting the AbortError branch puts a fresh uncaught rejection in your error tracker every time a user changes their mind, and that rejection is easy to miss and hard to trace.
- capability A page can now sample a color from another application's window with no prompt and no recording banner, and one hex string per click is the entire extent of what it gets.
The in-sandbox route is a pipeline. Request a display with `getDisplayMedia()`, decode the returned frame into a video element, paint that element into a canvas, then read the copy with `getImageData` [3]. It works. It also puts a permission prompt in front of the user and a capture-in-progress banner on the browser for as long as a color picker is open [3].
`window.EyeDropper` skips all of that. Construct one, call `open()` from a click handler, and the promise resolves with an object whose `sRGBHex` property is a string like `#3b82f6` [5]. The screen reading happens in the operating system, outside the browser sandbox, because the browser asks the OS to hand back one color instead of a frame of pixels the page could keep [6]. The API cannot sample a rectangle and cannot hand you a stream to record [5]. According to the post, that narrowness is why it needs neither the prompt nor the banner, and it is the one common case where a page sees something beyond its own tab without either [12].
Two gates then shape your code. `open()` has to be reached from a real user gesture, so calling it inside a `setTimeout`, a `fetch().then()`, or on page load throws instead of opening, under the same check that guards `requestFullscreen()` and autoplaying audio [7]. The second gate is the reader: pressing Escape while the loupe is up rejects the promise with a `DOMException` named `AbortError` [8]. Without a catch that special-cases `AbortError`, one Escape press prints an uncaught promise rejection in the console [9].
The 150 lines is the post's own estimate, hedged as "maybe", for a `getDisplayMedia` implementation [3]. Its EyeDropper version, the try/catch included, is a ten-line function [13]. That puts the capture route at roughly fifteen times the code for the same hex string [14]. For the comparison to transfer to your project, those 150 lines have to cover what the native API gives you free, which is the magnifier cursor and the fact that sampling keeps working over another application's window and over the taskbar [11]. If you only ever needed to sample inside your own page, the canvas version already did that [1].
The adoption cost is the feature detect. Firefox and Safari do not ship EyeDropper, so `if ("EyeDropper" in window)` decides whether the button renders at all, and the post says there is no polyfill worth using [10]. It does not say what to give the browsers that lack it.
In my view, for a Chromium-heavy audience this is a deletion: the canvas path cannot see other windows by construction [1], the capture path puts up a prompt and a banner for a single hex value [3], and the remaining work is ten lines plus a detect and an `AbortError` branch [13][8][10].
What to watch
- Whether Firefox or WebKit ship EyeDropper, which would retire the feature detect the post recommends.
- Whether the spec ever grows rectangle sampling or a stream, since that would reopen the permission question the current narrowness closes.
- Whether Chromium's gesture gate on open() stays as strict as the one on requestFullscreen().