Passing Your CSS Theme to `canvas`
While working on a recent project I noticed an issue with a canvas-based audio visualization when I toggled between light and dark modes. When I���d originally set it up I was browsing in dark mode and the light visualization stroke showed up perfectly on the dark background, but it was invisible when viewed using the light theme (which I���d neglected to test). I searched around, but didn���t find any articles on easy ways to make canvas respond nicely to user preserences, so I thought I���d share (in brief) how I solved it.
# The CSS SetupThe themeing of this particular project uses CSS custom properties. For simplicty I���m going to set up two named colors and then use two theme-specific custom properties to apply them in the default light theme and the dark theme:
:root{���color-dark: #222;���color-light:rgba(255, 255, 255, 0.5);���color-background:var(���color-light);���color-foreground:var(���color-dark);}@media(prefers-color-scheme: dark){:root{���color-background:var(���color-dark);���color-foreground:var(���color-light);}}# Applying the Theme to CanvasTo get the theme into my canvas-related code, I set up a theme object to hold the values:
const theme ={};Next, I wrote a function to pull in the theme colors using window.getComputedStyle(). After defining the function, I call it immediately to populate the theme object:
functionimportTheme(){theme.foreground =window.getComputedStyle(document.documentElement).getPropertyValue(������color-foreground���).trim()||���black���;theme.background =window.getComputedStyle(document.documentElement).getPropertyValue(������color-background���).trim()||���white���;}importTheme();I set this up with just two theme colors, but you can import as many (or few) as you like. Be sure to set a sensible default or fallback for each color though, just in case your theme���s custom property names change.
With this in place, I can set my canvas animation���s colors by referencing them from the theme object. For example:
context.fillStyle = theme.foreground;# Keeping Things in SyncThe final bit of magic comes when you add an event listener to a MediaQueryList:
const mediaQuery = window.matchMedia(���(prefers-color-scheme: dark)���);mediaQuery.addEventListener(���change���, importTheme);Here I���ve used matchMedia() to get a MediaQueryList object. Typically we use the matches property of this object to establish whether the media query currently matches or not. A lesser-known option, however, is that you can attach an event listener to it that will be triggered whenever the query���s status changes. So cool! With this in place, the canvas contents will update whenever the user���s theme changes. Here���s an example of that:
https://www.youtube.com/watch?v=pALIuO5uHUA
This video demonstrates how a canvas element rendering a dark sine wave against a light background can miraculously transform into a light sine wave against a dark background using CSS custom properties and a bit of JavaScript.
# DemoI put together a quick demo of this in a fork of Alvin Shaw���s Canvas Sine Wave Experiment:
Hopefully this is helpful to someone out there. Happy themeing!