Gradient Background With Dithering In A Canvas Element
Why <canvas>?
It's a good question, why am I using a canvas element instead of just using the native gradient function in CSS3? Well, that function is lackluster and causes banding issues on some browsers. I initially thought I might be a little harsh on the spec when I was upset it didn't provide any way of specifying a dither method, but apparently some people were so taken a back by this that they went so far as to state that they need to remove the gradient function until dithering methods are part of it. So, I'm feeling good about calling it “lackluster”.
For me the banding was only a partial issue. I wanted the dithering regardless of whether or not there is color banding in Chrome. I specifically wanted the Bayer (or “ordered”) dither. I like that classic GameBoy era look and thought it might spruce up the website's boring background a bit -- plus I've got something in the works and I'd like it for that as well.
Special Considerations if you're Using Hotwire's Turbo
The canvas element will load on the first visit to the site, but because Turbo intercepts local hrefs, loads the html, and then just swaps in the new stuff, it means that our script isn't retriggered but the canvas is still wiped out. To fix this I added the following to the attribute to the script:
<script ... data-turbo-track="reload">
This tells Turbo to always rerun the script when the page is reloaded. I then also updated the function call that is used to create the gradient to:
document.body.style.background ? undefined : updateCanvas()
Since I am not setting the body's background anywhere other than this script, I know if that style attribute exists (ie isn't an empty string) that the canvas has already been created and the background set, so I can skip creating it, otherwise, let's create it. This cut down my redrawing of the background by a little bit, not a huge amount, but it took care of some of the redraws.
Since Turbo doesn't reload the page, making it essentially an SPA, we might be able to only ever draw it once and then just source that initial drawing on every other view, but I haven't figured that out yet.