直接在 change 回调重绘 canvas 会出问题,因 css 变量未及时生效,应通过布尔标志 isdarkmode 统一管理主题状态,并在 requestanimationframe 中按需重绘,避免 dom 查询和内存泄漏。

为什么直接在 change 回调里重绘 Canvas 会出问题
因为 window.matchMedia('(prefers-color-scheme: dark)') 的 change 事件只告诉你“系统主题变了”,但 Canvas 本身不自动响应 CSS 变量或颜色语义;你得手动触发重绘,且必须确保重绘时读取的是最新主题状态,而不是缓存值或 DOM 未更新的中间态。常见错误是:回调里直接调用 ctx.fillStyle = getComputedStyle(document.documentElement).getPropertyValue('--bg'),结果拿到的是旧 CSS 变量值——因为 CSS 变量可能还没随 data-theme 切换完成而生效。
监听回调中如何安全获取当前主题色并应用到 Canvas
别依赖 getComputedStyle 读变量,它有延迟且受 CSS 级联顺序影响。应统一从单一可信源取值:
- 初始化时就用
window.matchMedia('(prefers-color-scheme: dark)').matches确定初始主题,并存为布尔值(如isDarkMode) - 在
change回调里只更新这个布尔值,不查 DOM、不读 CSS 变量 - 所有 Canvas 绘制逻辑(包括动画帧、重绘函数)都基于该布尔值分支,比如:
ctx.fillStyle = isDarkMode ? '#121212' : '#ffffff' - 若需复用 CSS 变量,改用
document.documentElement.getAttribute('data-theme') === 'dark',它比getComputedStyle更快、更确定
Canvas 滤镜类操作(如 globalCompositeOperation 或 filter)怎么适配深色模式
Canvas 的 filter 属性(如 "brightness(0.8) contrast(1.2)")不是响应式,也不能直接绑定 CSS 变量。必须在每次绘制前显式设置:
- 避免用
ctx.filter = "brightness(0.9)"全局设滤镜——它会影响后续所有绘制,容易漏重置 - 推荐封装一个绘制函数,内部根据
isDarkMode计算滤镜字符串:const filter = isDarkMode ? 'brightness(1.1) saturate(0.9)' : 'brightness(1) saturate(1)' - 对图像绘制,可预生成两套
ImageBitmap或用ctx.putImageData()替换像素数据,比运行时滤镜更稳定 - 注意:Safari 对
ctx.filter支持有限,iOS 16.4+ 才较可靠;降级方案是用globalAlpha或叠加半透明层模拟暗色压暗效果
容易被忽略的渲染时机与性能陷阱
Canvas 重绘若发生在 change 回调同步执行,可能卡住主线程,尤其在高频切换场景(如 macOS 控制中心反复拖动深色滑块)。关键点:
- 不要在
change回调里直接调用draw()—— 改用requestAnimationFrame(draw)延迟到下一帧 - 如果 Canvas 内容复杂(含大量路径或图像),先检查
isDarkMode是否真变了,再决定是否重绘;避免无谓清屏和重绘 -
matchMedia实例本身轻量,但每个addEventListener都要配对removeEventListener;SPA 页面卸载时若没清理,会导致内存泄漏 + 多次触发重复重绘 - 首次加载时,
matchMedia的matches值可能和localStorage冲突(用户手动切过主题),务必以 localStorage 为最高优先级,系统偏好仅作 fallback











