CSS Painting API

56 90s_dev 24 5/26/2025, 8:59:06 PM developer.mozilla.org ↗

Comments (24)

jauco · 20d ago
The most fun I had with this was to make initial versions of apps look alpha using https://github.com/pshihn/rough-paint
90s_dev · 20d ago
Amazing example and show-case, great job. Reminded me of my pinstripes:

https://os.90s.dev/#sys/apps/paint.app.js

https://github.com/sdegutis/os.90s.dev/blob/31bf4ec46e02367b...

But yours is much fancier and cooler.

pjmlp · 19d ago
This is part of Project Houdini from Google, it has been years out there without being picked up by other browsers.

https://developer.chrome.com/docs/css-ui/houdini

Anyone remembers the CSS rendering extensions on IE 5?

That's right, the more things change, the more they stay the same.

goranmoomin · 20d ago
If I'm understanding correctly, isn't this introducing a critical dependency on JavaScript during rendering? I get that people might want to draw stuff with code, but why would people use this (and introduce render performance problems inadvertently) instead of using the HTML5 Canvas API?
AgentME · 20d ago
The HTML5 Canvas API can only be used in the main javascript thread, so whenever the page's javascript thread is busy with anything then the browser has to wait on that to finish before being able to have it update anything it rendered. The Paint Worklets allow the browser to run some page javascript just for rendering in one or more separate threads.
MortyWaves · 20d ago
But why can the canvas still only be accessed in the main thread? Why introduce a whole other API as a workaround for this?

Also, as far as I have seen, people have been using WebAssembly for complex stuff and marshalling it to the main thread for the canvas.

AgentME · 19d ago
The worklet API gives the browser the ability to spawn as many threads for the task as it wants when it wants, without needing to communicate and wait on the page's code in the main javascript thread each time.
yladiz · 20d ago
It doesn’t refute your point, because a worklet is a worker and thus not in the main thread, but the canvas API is usable outside of the main thread as well, via OffscreenCanvas or something like transferring to a worker.
90s_dev · 20d ago
We came full circle. We started out overriding draw(), then HTML + CSS took over, and now we're back to overriding draw(). This was a huge inspiration for 90s.dev
reverseblade2 · 20d ago
Here’s a cross browser example I built for my personal site. See the curve: http://onur.works
chrismorgan · 19d ago
That’s badly broken in a variety of ways at a variety of window sizes in both Firefox and Chromium, though worse in Firefox where it goes from being unpleasant to downright unusable at some sizes; and at larger resolutions in Firefox it’s not even managing 3fps. What are you doing? You’re meddling with scrolling, too. :-(
reverseblade2 · 19d ago
I am not seeing any issues on Chrome. To be fair, I mostly cared about Safari and Chrome than Firefox.

Scrolling is about scroll snapping feature.

tyleo · 19d ago
Also broken on iOS. Goes off my viewport horizontally. Scroll doesn’t work as expected.
reverseblade2 · 19d ago
There is scroll snapping. Yes it slightly exceeds the horizontal. My point was about the CSS Painting API though :)
reverseblade2 · 19d ago
Solved the horizontal viewport.
paulryanrogers · 20d ago
Why? Nothing in that article makes the use case clear. Perhaps I just lack imagination?
c-smile · 20d ago
Consider <textarea> that has resizing handle on the corner (try to reply to this message to see it alive). And now try to imagine how would you render those diagonal lines in HTML/CSS ?

While in Sciter, that has such immediate mode rendering form the very beginning, you can simply draw them as:

   resizableElement.paintForeground = function(gfx) { // draw on top of background & content

      let box = this.getBoundingClientRect();
      gfx.moveTo(...); gfx.lineTo(...);
      gfx.moveTo(...); gfx.lineTo(...);
      ...
   }
Easy, right? And does not need to modify DOM and place artificial positioned elements.
andrewingram · 20d ago
A few years ago I tried to use it to implement mesh gradients. It didn’t go well, mainly because you can’t draw individual pixels without cheating with lots of 1x1px rectangles. But it was a fun experiment.
90s_dev · 20d ago
Near the top:

> to set complex custom backgrounds on an element

Basically the same reasons you might need draw() in traditional GUIs.

Do this in CSS: https://camo.githubusercontent.com/b6c45f60f6e450574c4087589...

(image taken from the other guy's comment's gh repo)

dwoldrich · 20d ago
Reusable dynamic canvas code as background image? ¯\_(ツ)_/¯

https://developer.mozilla.org/en-US/docs/Web/API/CSS_Paintin...

hyperhello · 20d ago
But you can do this now with Canvas.toDataURL(). Do we need to support another drawing surface API?
90s_dev · 20d ago
Using toDataURL() has a lot of overhead that this doesn't use. Also this runs in worklets.
Seb-C · 20d ago
Blob URLs should work without much overhead.
esprehn · 20d ago
That won't handle resizing properly. This API lets you hook directly into the painting phase of rendering in the browser so you can both draw the correct size without forcing layout and handle resizing. It also gives the browser the flexibility to not paint the off screen content at all.