必须用 change 事件触发 requestanimationframe 重绘 canvas 滤镜,而非依赖 matchmedia 自动更新;需缓存 isdark 状态、解耦滤镜为参数驱动、避免帧内重复查询,并做好兼容性降级。

不能靠 window.matchMedia 自动更新 Canvas 滤镜——它只通知主题变了,不触发重绘,也不改像素。真正生效的关键,是把 change 事件当作“刷新指令”,在回调里更新控制参数,并驱动 Canvas 下一帧用新参数重算图像。
监听系统模式并缓存状态
用 (prefers-color-scheme: dark) 创建媒体查询对象,立即读取当前状态并缓存为布尔值:
const mql = window.matchMedia('(prefers-color-scheme: dark)');-
let isDark = mql.matches;—— 首屏渲染前就确定初始值,避免跳变 - 绑定标准事件:
mql.addEventListener('change', e => { isDark = e.matches; requestAnimationFrame(draw); }); - 保存 handler 引用,卸载时调用
mql.removeEventListener('change', handler)防止内存泄漏
滤镜逻辑解耦为参数驱动
Canvas 滤镜(如反色、亮度调整、对比度增强)应由 isDark 控制分支,而不是每帧查系统偏好:
- 把滤镜系数写成函数参数:
processPixel(r, g, b, isDark) - 深色模式下调低亮度增益、提升对比度阈值,浅色模式用默认值
- 避免在
requestAnimationFrame循环里重复调用mql.matches或getComputedStyle - 首次绘制前同步执行一次
draw(),确保首帧就应用正确滤镜
安全重绘与性能控制
change 回调本身不能做高开销操作,要交由渲染循环处理:
- 回调中只更新
isDark和标记“需重绘”,不调getImageData或putImageData - 在
draw函数开头检查是否需重绘,再批量处理像素数据 - 若 Canvas 正在播放视频流,可在
video.onloadeddata后同步主题再启动动画循环 - 对高频切换(如 macOS 深色滑块拖动),用
requestIdleCallback延后非关键更新
兼容性与降级处理
不是所有环境都支持 prefers-color-scheme,需主动兜底:
- 检查支持性:
if (!window.matchMedia || !window.matchMedia('(prefers-color-scheme)').media),不支持时跳过监听 - 旧版 Safari(≤13.1)不支持
addEventListener,需回退到addListener - Safari 对
ctx.filter支持有限,iOS 16.4+ 才较可靠;降级可用globalAlpha叠加半透明层模拟压暗效果 - 开发期加版本参数(如
?v=2)避免图标或资源缓存导致的样式错乱











