ASSET · DUAL THEME TOGGLE

两态主题切换器 · 一个按钮

右上角切一次:整页颜色交叉淡化(视图过渡),选择写进 localStorage,刷新保持。 从没点过的用户不看这段脚本 —— 没有 data-theme 属性时,页面直接跟随系统的 prefers-color-scheme,并且把按钮藏起来:宁可不显示,也不给一个点不动的假开关。

当前状态(活读数)

html[data-theme]
—
html[data-resolved]
—
computed --bg
—
localStorage.theme
—

定长丝滑滚动(同一个文件里的第二件工具)

点下面任一张卡:2 秒缓入缓出滚到卡片区。中途滚轮 / 触摸 / 方向键一动作就交还控制权, prefers-reduced-motion 下直接瞬移。原生 scrollIntoView({behavior:'smooth'}) 的时长由浏览器定(约 300ms),要"能看清的过程"只能 rAF 自算。

↓ 占位区:让卡片落在首屏之外,才看得出滚动过程 ↓

十条踩出来的硬结论

令牌单一出处

主题 token 各页各写一份,第 N+1 个页面必然漏:真实事故是首页和详情页都换了皮肤,专题页还是硬编码的旧色。

html[data-theme] 的元素名前缀

属性贴 <html> 上,而各页自己的 :root 通常写在后面、特异性同为 (0,1,0) —— 选择器不带元素名前缀就会被压掉:属性切了、颜色纹丝不动。

<head> 期贴标

存过的主题必须在 <head> 里先贴一次,否则刷新先闪一帧默认主题。

重映射表要盖满

重映射要覆盖页面用到的全部 token,漏一个就是"切了一半"。

无 JS 跟系统

没有属性时用 @media (prefers-color-scheme) 跟随系统,并隐藏按钮 —— 点不动的开关比没有开关更糟。

小字走 --accent-ink

原色 --accent 只给图形和大字号;彩色小字必须用同色相提亮/压暗一档的 --accent-ink。

图标一个按钮

44px 热区、15px 图标、无边框无胶囊无文字;禁两个圆点并排。

动效随主题停

隐藏画布 ≠ 停渲染:给常驻动效挂主题判断,浅色下别继续烧 GPU。

删交互清五层

DOM / CSS 提示与动画 / JS 绑定 / 过期闸 / 别处仍在用的附带物,一层不落。

冷启动口径

备选主题冷启动时背景帧数应为一帧都没跑 —— 拿计数说话,不靠"看起来停了"。

验收清单(可照抄)
  • 点按钮:html[data-theme] 翻转,上面 computed --bg 跟着变。
  • 刷新:localStorage.theme 还在,首屏直接是选过的颜色(全程无闪)。
  • 清掉存储再刷新:属性消失,跟随系统;按钮仍在,点一下就写回选择。
  • 键盘 Tab 到按钮:可见焦点环 2px;Enter/Space 生效。
  • 1440 / 390 两个视口:顶栏不出现横向滚动条。