w3c/css-houdini-drafts

[css-paint-api] Math.random usage in CSS Paint API leads to jitter in painted effect.

Opened this issue · 6 comments

There are many examples of paint worklet being used to generate interesting random patterns:

https://paintlets.herokuapp.com/
https://css-houdini.rocks/rough-boxes

However, using Math.random() in your paint worklet means that any time the browser needs to repaint the element the output changes. See for example this demo of running a transition on the paint worklet background color where you can see the rough border jitter while the color is animating. The same effect is observed anytime the window resizes:

https://animated-paintworklet-element.glitch.me

While some demos like https://css-houdini.rocks/random-bubbles-mask take into account random number generation I suspect the majority won't. We should consider whether it is possible to use a seeded random number generator for paint worklets to provide output stability in these cases. While this doesn't solve all cases (i.e. the random numbers may be manipulated differently given different inputs) it is much more obvious why something changes in these cases.

See https://github.com/tc39/proposal-seeded-random which I will at some point actually finish and advance (it's level 1 right now, but stalled a year-ish ago).

If we have seededPRNG would we:

  1. remove Math.random from the scope (is this even something we can do?)
  2. automatically seed it for paint worklet
  3. expect that developers would learn to use that instead of Math.random?

I expect (3), as that's the no-effort result that will happen automatically.

But providing a helper on the worklets that uses a pre-seeded prng might also be an interesting idea?

Wait, hm, we'd have to pass it into the paint() callback, not expose it on the global, because otherwise it would share its state between all the worklets running in a given global, including separate invocations of the same worklet.

So we'd have to add a parameter to the constructor saying you want a prng, and specify how it's passed to the callback; at that point I don't think there's much benefit over just saying var prng = Math.seededPRNG(); yourself.

Well specifically it would have to be reseeded before each call to paint. Given that the code in a particular scope can't run concurrently I think this might be tractable, though it still has a code smell to it.

Yeah, that's legit. Note tho that in my current proposal there is no .seed setter; to "re-seed" a prng you create a new one. That's not a big problem here, it just means that code can stash a prng and re-use it in later invocations.

I'm... not particularly sure why I did that, tho.