Release Notes

版本日志

这里记录的是本站真实发生过的问题,以及它们的成因。

121 个版本 · 当前 v2.20

多数缺陷只在特定平台出现(iOS Safari 居多),因此每条都写清了触发条件,便于日后在同类设备上复现与回归。

  1. v2.20加图管线产出错误结构:占位图写法回退、srcset 缺档、图片索引失真
    • 占位图写成 background-image→应为 --lqip-src。LQIP 由 .img-wrap::before 承担(background-image:var(--lqip-src) 配 filter:blur 与延迟淡出);写成 background-image 会直接铺在 .img-wrap 自身,那正是 v2.01 修掉的「iOS 衔接处闪黑一两下」的成因(全屏纹理从一份变两份)。且 .loaded::before 只作用于伪元素,写在自身上永不淡出。后果是:按旧工具每加一张图,就把已修掉的闪黑带回这一张
    • srcset 只列到 1200 档→网格 sizes 是 (max-width:600px) 90vw, 46vw,1440 视口下 46vw ≈ 662 CSS px,DPR 2 需 1324 物理 px > 1200,裁掉 1800 会让 Retina 桌面选不到足够大的图而被拉伸发虚。误判来自「1800 是灯箱专用」—— data-full 负责灯箱,与 srcset 是否列 1800 是两件事
    • alt 用图注顶替→figcaption 是短图注(横幅 02),alt 是读屏用的完整画面描述,线上 37 张无一相同。新增 --alt 参数;不给时退回图注并提醒,体检列为警告
    • manifest.json 长期失真→117 条 srcset 声明里 74 条指向 <编号>-1400.webp,而磁盘上 1400 档一份都没有(现役是 480/900/1200/1800)。新增 regen_manifest.py 以磁盘为准重建;add_photo 加图后自动同步,顺序必须是先落 lqip 再全量重扫 —— lqip 是画面内容的产物,无法从 webp 反推,只能从旧索引继承
    • 体检拦不住这三项→此前 check.py 与 verify.py 对旧产物都是 45/0/0 全绿 —— 它们只校验「引用的文件存在」,不校验「写法与其余 37 张一致」。新增 E(占位图写法)、F(srcset 档位)为失败级,G(alt 与图注)为警告级;反向验证过:用修复前的产物测试,E/F 均如实报错
  2. v2.19灯箱连续单击缩放出现横向色块:合成层反复重建导致的纹理块撕裂
    • 画面出现横向矩形色块与白块→旧实现每次放大都在动画结束后撤掉 will-change 补一次「清晰重栅」,再在下一次输入起始装回 —— 于是每一个 1×↔2× 循环都要销毁并重建一整张合成层纹理(1800px 图带 filter,98vw/96vh 框架下数千像素见方)。Safari 会把超过阈值的合成层拆成多个纹理块渲染,反复重建时各块被光栅化的时机与比例互不一致,画面就出现「部分块新、部分块旧」—— 横向矩形正是纹理块的形状,与 v1.41 的「纹理块撕裂」同源。单次单击只重建一次通常能自愈,连续单击来不及自愈,故稳定复现
    • 这次重栅几乎没有收益→源图最大只有 1800px,而 fitFrame() 会把图放大到接近视口(上限 ZOOM_FIT_MAX=2.2),即 1× 时光栅尺寸已接近甚至超过源图像素。放大到 2× 后,无论「拉伸 1× 位图」还是「按 2× 重栅」,最终相对源图的放大倍数完全一样(都是 最终设备像素 ÷ 源图像素),唯一差别是少一次几乎 1:1 的中间重采样。拿几十 MB 纹理的反复重建换这个,不划算
    • 不变式:缩放路径永不写 inline will-change→提示只由 .lb.open .lb-frame 常驻提供,随灯箱打开建立、随关闭释放,整个会话只建一次 —— 这正是该条 CSS 注释原本就写下的要求,旧实现用 JS 反复加撤等于破坏了它。缩放路径现在只负责写 transform 与过渡结束后摘掉 .zooming
  3. v2.18流光移除高光:整圈单一浓度、亮度恒定、色标等间距
    • 高光始终在「太显眼」与「太暗」之间摆→v2.12 起的双层结构让亮度沿圆周起伏(整圈低浓度底纹 + 两处满浓度光泽),v2.14 压浓度、v2.15 按文字亮度重定标,都只是在调那两处峰的高低 —— 起伏本身就是「两盏灯」的来源,调高低只是换一个摆的位置。按调研的三条判据(窄幅品牌色带读作平静、窄幅色对缓慢旋转读作 aurora、边框太忙时改用亮度相近的颜色)一次性换掉表达方式:整圈单一浓度,流动只由色相承担
    • 三色 alpha 必须各不相同→暖金 / 青蓝 / 柔玫的满值亮度并不相等(0.61 / 0.58 / 0.51),同一个 alpha 下转起来仍会忽明忽暗 —— 那是「两盏灯」的另一种形式。按 alpha ∝ 亮度^(-1/2.4) 反解出 0.69 / 0.71 / 0.76,合成后三处亮度 0.254~0.259,极差比 1.018。柔玫同时提亮,三色亮度跨度由 0.23 收到 0.10
    • 色标等间距→旧色标间距在 8%~16% 之间不等,色相推进忽快忽慢,读作「抖」而不是「漂移」。改为每 16.7% 一个色标、一圈仍是两组色序
    • 整圈亮度定在 0.255→比 v2.15 的谷位(0.041)高六倍 —— 旧版整圈大部分是暗区,本轮整圈都是这个亮度,整体观感反而更亮;峰值则由 0.355 降到 0.255,即「移除高光」的量化结果。该值略低于站内最弱的一档正文 --fg-3(0.27),光环不会盖过它下面的文字
  4. v2.17去掉区域高斯模糊,只留边缘流光
    • 内部光雾整块移除→v2.16 在卡片内加了一层 filter:blur(20px) 的环形彩雾。整块删除而非调淡 —— 淡一点只会得到「一层看不出是什么的灰」:既没有玻璃感,又白留着一次全卡片的模糊合成。同时删掉为它服务的两条规则:光环两层虽也是绝对定位,但被 mask-composite 裁成环形、中间全透明,并不遮挡文字
    • bloom 的 blur(4px) 保留→它是流光两层里「泛光」那一层自身的弥散,不是区域模糊;去掉会让 7px 环变成硬边实色线。悬浮层次也保留 —— 反馈只指向模糊
  5. v2.16新版入口补上悬浮层次
    • 常态没有投影→此前 box-shadow 只在 hover 才有,常态完全贴地 —— 这是「太平面」的直接成因。悬浮不靠位移:常态位移会被 reveal 的 animation 终态覆盖,.anim-done 撤动画那一下会从终态跳到常态,哪怕 1px 也是跳。改为阴影层次:远投影给离地感、近阴影给接触感、顶部 inset 高光加底部 inset 暗边给厚度。方向沿用全站 --glass-inner,但只取两条 —— 四条在这条矮卡上会显脏
  6. v2.15流光亮度按站内文字实测值重定标(v2.14 压过头)
    • 峰位比最弱的一档文字还暗一个数量级→v2.14 把光泽压到 0.3 量级,合成后峰位相对亮度只有 0.034,而全站最弱的一档正文 --fg-3 都有 0.272。按实测三档(--fg 0.91 / --fg-2 0.46 / --fg-3 0.27)重定标:光泽 → 0.76/0.80、底纹 → 0.34、两层不透明度 → 0.80/0.92,峰位亮度升到 0.355,落在 --fg-3 与 --fg-2 之间。刻意不取三档算术平均 0.55:那一档比被判「过于明显」的 v2.12(峰位 0.49)还亮
    • bloom 的模糊峰值保留率此前没算→7px 环经 blur(6px) 后峰值浓度只剩 erf(7/(2·6·√2))≈0.44,旧公式按满值算,峰谷比被高估。补上该系数;「常态打折」的判据也由绝对值上限改为「常态 < 交互」的结构关系 —— 旧阈值与本轮整体提亮直接撞车
    • 提亮只走边缘→圈内那层斜向染色铺在 --fg-3 文字底下,浓度上限由对比度决定(实测 5.87:1),不随光环同步抬高
  7. v2.14流光高光柔化:光泽降浓度、峰坡展宽、锐度让位于弥散
    • 两处高光过于明显→那两个峰当时是 alpha 1.0 的满饱和色,纯黑页脚里读作「两盏灯扫过」。按调研判据(多数色标低于 0.4)压到 0.3 量级,峰坡由正负 6% 展宽到正负 15% —— 峰越窄越像亮点划过,展宽后才读作亮度起伏。峰谷比 3.7 → 2.3 倍
    • 锐度让位于弥散→core 是 2px 锐线、同等浓度下最扎眼;bloom 是 7px 模糊环、负责光的体量。旧分配 core 0.82 / bloom 0.30 是锐线主导,所以显眼。改为 core 0.50 / bloom 0.62,blur 4px → 6px
  8. v2.13流光恢复流动
    • 光环完全不流动→v2.11 起 betaSweep 只声明在 .beta-entry 上,靠 @property 的 inherits:true 把角度传给两个伪元素。语法与规范都成立,但实测不流动:角度推进到了宿主元素,伪元素的重绘却未必被触发 —— 各引擎对「继承而来的注册属性变化」的失效处理并不一致。改为两层在共用块里各自声明同一条动画:消费方自己推进 --beta-a,重绘是必然的;两层同批创建、同时长同起点,同步仍是结构性的
    • reduced-motion 仍列三层→动画从宿主挪到伪元素后,只关伪元素已足够,但选择器保留 .beta-entry 一并列出 —— 日后若有人把角度源挪回宿主,这里照样关得掉
  9. v2.12打开菜单不再闪;流光改为只在边缘流动
    • 打开菜单会闪一下→旧实现用 @keyframes 把 blur 半径从 0 动画到 8px:半径每帧变一次就要重做一次全屏采样,更关键的是 backdrop-filter 由 none 变成 blur() 是一次层结构变化(元素开始需要背景快照),而此刻底色已渐显到一半、元素完全可见,WebKit 上第一帧常常还没采样完。改为半径恒定、由独立的 ::after 承载、只动 opacity —— 属性不再在 none/blur 之间跳变,层结构从打开起就是终态,且 opacity 从 0 起步正好把「刚建立、未采样完」的那几帧藏住
    • 滑动期间仍在采样→模糊层基础态用 visibility:hidden 而不只是 opacity:0 —— 全透明元素仍可能参与背景采样。hidden 期间彻底不绘制,面板滑动那 0.5s 零全屏模糊成本(v1.89 修掉的手机端掉帧不能因为改了结构就退回去)
    • 拆层后的层级→模糊层 ::after 在 z-index:0、底色 ::before 提到 1、菜单项提到 2。底色必须压在模糊之上,否则 0.86 的黑被盖住,视觉与旧版不一致
    • 重启保险用了 cancel()→cancel() 会立刻移除 animation 的 fill,那一瞬元素回到 .open 的 opacity:1,而菜单项在面板滑动期间本应停在起始态 opacity:0(靠 both 的 backwards 填充)。改为 currentTime = 0 只拨回播放头,fill 全程在位,不存在无 fill 的窗口
    • 流光太显眼→v2.11 是整圈满浓度 + 圈内再铺一层同速流光,两条都过头。圈内那层删掉:它铺在标题与 CTA 文字底下,动态色底让正文持续轻微变色(可读性下降),且两层同色同速读作「整块在转」,稀释了边缘流动的优势。边缘则由「色相推进」改为「亮度起伏」—— 整圈铺低浓度底纹 --beta-d*(仍不透明、不断裂),只留 12% / 62% 两处满浓度光泽,相隔 50% 对径
    • 周期 6s → 8s、常态再压一档→6s 是按 Apple shimmer 的线速度换算来的(约 300px/s),保证的是「跟得上」而不是「优雅」;放到 8s 后一次呼吸只走半圈,视线追不上也就不会被勾住。整圈平均浓度降下来后,core 0.92 → 0.82、bloom 0.45 → 0.30
    • 测试自身两处失效→层次断言按「含 z-index:1」判定,而注释里同样写着这个值,改坏了也不报错(假绿);「@keyframes drawerBlur 已删除」与「不得用 cancel()」则在注释里命中字面量而永远失败(假红)。统一改为先剥注释再断言
  10. v2.11整圈实心 + 圈内补一层流光(两项 v2.12 已改)
    • 一圈大半是空的→旧版是一道道光带扫过、其余透明(暗段占 30%~50% 与 80%~100%),看着像彩边断成几截。改为整圈填满颜色,流动只由色相沿圆周推进承担;首尾同色(0% 与 100% 都是 d1),接缝处才没有硬边
    • 角度只有一个来源→三层若各跑一条 animation,只是「同一时刻起步」,伪元素一旦被重建就各自重新计时而失步。当时改为 @property --beta-a 设 inherits:true、动画只声明在 .beta-entry 上由伪元素继承 —— v2.13 实测该写法光环不流动,已改为挂在消费方
  11. v2.10流光回调为静态克制、交互才满血
    • 常态满血在纯黑页脚里过亮→2px 的高饱和光轨满不透明、再叠一层常态彩色光晕,在纯黑背景上几乎没有其它亮源可比,一给就跳出来。回调 v2.08 的分档:core 0.95 / bloom 0.7,hover 与 focus-visible 才满血;常态不放光晕
    • focus-visible 与 hover 同权→键盘操作拿不到 :hover,只写 :hover 会让这处入口在键盘走查时永远停在打折档
  12. v2.09流光常态满血(v2.10 已回调为静态克制)
    • 最强的一档藏在 hover 后面→core 常态 0.95 / bloom 0.7,hover 才升到 1 / 0.88 —— 静态下永远差一档。两层常态直接给到最终值,hover 不再提亮光环;层次改由外发光承担(常态补一层约六成的双色光晕,hover 只加强它)
  13. v2.08页脚新版入口:Apple 式内侧边缘光
    • 单层细环只是「描了条彩边」→Apple 的边缘光是多层叠加(紧核 / 泛光 / 大晕)才有光的层次。改为 core + bloom 两层同心:::after 在上做 2px 细光轨,::before 在下做 7px 宽环 + blur(4px) 向内渐隐
    • 光压在描边上而不是内侧→旧版 inset:-1px 让光环与元素自身那条 1px 描边并排,看着像 2px。absolute 的包含块是 padding box,改为 inset:0 后光落在描边内侧;圆角同步 -1px 才同心
    • 一圈只有一组色序,扫完要干等半圈→改为一圈排两组色序。淡出必须写同色 alpha 0:transparent 等价于 rgba(0,0,0,0),插值会先朝黑走再谈透明,光带尾部会出现发灰的脏边(v2.11 起整圈实心,不再需要淡出)
    • 周期按线速度换算,不照搬 Apple 的 1.8s→Apple 的 1.8s 是按小卡片给的(周长约 600px ≈ 330px/s)。本按钮横跨页脚整行,周长约 1800px,同线速度换算约 5.4s,取 6s。短于约 3s 会被读成「加载中」—— CTA 上的快速循环是可用性陷阱(v2.12 放慢到 8s)
    • 不做性能降级→循环动画的成本出在「同时跑好几个」,全站只有这一处实例,绘制面积仅一条横条的边缘;它又是通往新版的唯一入口,停掉等于退回普通卡片 —— 是功能损失。唯一仍会让它停下的是 prefers-reduced-motion(前庭敏感,无障碍要求)
  14. v2.07菜单动画对齐 Apple:删除回弹,改为「面板就位 + 内容错落淡入」
    • 回弹手感不对、不符合物理(最终否决)→回弹是两级运动叠加出来的:文字起步时面板仍在下行,把它带到终位下方,面板停稳后文字才浮回来。幅度 = 面板停住那一刻文字还剩多少行程,于是随视口高度与「面板收回多少」漂移,跨设备恒定只能靠凑比值。参考 Apple 后确认:弹性只属于手势驱动的弹簧(拖拽 sheet、滑动返回),菜单展开是触发式内容动效,Apple 的做法是面板就位 + 内容错落淡入,不带 bounce。已整体删除
    • 改法:两段严格串行→--drawer-lead 由 0.30×面板时长改为 = --drawer-in,面板停稳后文字才起步,位移不可能与面板叠加 → 回弹恒为 0,且与视口高度、剩余行程完全无关(tools/drawer_rebound.py 16 组组合实测全 0)
    • 文字时长 0.8s → 0.35s、位移 32px → 14px→面板停稳后画面已静止,0.35s 足够读出错落节奏;0.8s 会把「打开菜单」拖到 1.4s,违背 HIG「不要让人等动画完成」。末条落定 1.12s → 0.97s。位移取 14px 与全站 @keyframes fadeUp 同值,此前 32px 与站内其它淡入上浮不是一个节奏
    • 附带的收益:手表端模糊抖动自行消失→模糊在面板停稳那一刻(0.5s)恰好满值,而菜单项 0.5s 才起步 —— 不再有时间重叠,弱 GPU 的「backdrop-filter 与 transform 动画并发被降级」无从触发。此前靠降半径(8px→4px)去扛,现在根本不冲突
  15. v2.06菜单回弹回到 follow-through(v2.07 已整体删除回弹)
    • 菜单打开时文字「往上弹一下」→v2.05 用 @keyframes 的 78% 帧让文字冲过终位 12px 再回落。物理上不成立:文字跟随面板下滑、自身没有独立动量,过冲只能顺着自己的上浮方向走,方向因此变成向上。已删除该帧
    • 过冲帧破坏速度连续性→三段关键帧各自套用 ease-out,极值点上前段末端斜率 0.109、后段起点斜率 1.84,速度瞬时跳约 17 倍 —— 「弹一下」的突兀感来自这个拐角,并非掉帧
    • 回弹改由 follow-through 单独产生→文字在面板走完 30% 时起步、时长 --t-slow(0.8s),面板停住那一刻文字仍在终位下方 9.34px。幅度只由两条时长的比值决定,实测跨视口高度与剩余行程 9.3~9.7px(离散 <5%)
    • 代码与注释曾不一致,回弹只剩 2.8px→注释写 --t-slow、代码仍是 --t-mid(0.5s)。回弹量由两条时长的比值决定,改其一必须同步另一个
  16. v2.05Safari 浮现两次、菜单回弹两次、筛选栏加入场
    • 浮现播完到位后又从下往上闪一次(只 Safari)→.reveal 基础态的入场 transition 一直没删,与 animation 同时作用于 opacity/transform。animation 优先级高、决定视觉,transition 在后台照常推进但终点不一致;iOS 会推迟滚动期间的样式重算,animationend 撤掉 animation 的瞬间元素跳回 transition 的半路插值,再被推回终位。Blink 不推迟,故不复现。改法:动画进行期间 transition:none,animation 独占
    • 菜单字体出现两次回弹,第二次更弱→叠加来源与关键帧过冲并存,方向相反、一强一弱(该过冲帧 v2.06 已删除)
    • 切到作品集时栏目按钮没有入场动画→7 个按钮与计数未挂 reveal。不能加在 .filters-bar 上:它的 ::before 带 backdrop-filter,祖先 opacity<1 会创建 backdrop root,玻璃在动画期间失效、结束时突然恢复 = 闪一下。按钮自身无 backdrop-filter,各自淡入不受影响
  17. v2.04菜单回弹时有时无、快滚文字闪现
    • 第一次打开有回弹,连续打开没有,等一会又有→根因是收起状态残留:drawer-closing 会强制 animation:none,若在收起动画未走完时重新打开,该锁尚未解除,文字直接显示、没有入场。已确保打开前先解除,并保留 cancel+play 的重启保险
    • 快速滚动时文字浮现仍闪现→PRE_NEAR(160px) 小于快滚时的 revealLead(可达 420px),揭示边界先于 .pre 命中,元素被揭示时才同帧补加,层还没建好动画已开跑。改为取二者较大值
  18. v2.03全平台文字闪现、菜单回弹丢失
    • 全部字体的浮现变成从下往上闪现→合成层提前量 PRE_LEAD 取「一屏」(桌面 900px 视口达 810px),等于让整屏 .reveal 同时持有 will-change,每个都是独立合成层。GPU 层数超限后浏览器频繁做「提升↔降级」调度,每次降级都要重绘一次。新增 PRE_NEAR(160px) 供 .reveal 专用,快滚时再取二者较大值,保证 .pre 必定先落
    • 菜单字体的回弹不见了→回弹依赖「文字在 delay 期间停在起始位移上」,而面板从 visibility:hidden 变 visible,浏览器不保证为未渲染过的子元素启动带 delay 的 transition;delay 一失效,两级运动不再重叠。改回 animation:animation-name 生效即按 from 帧起步,delay 由合成器计时
  19. v2.02揭示停用滚动驱动
    • 正文揭示不再随滚动位置渐显→scrub 下文字长期停在半透明,一边滚一边读到的是 60% 不透明度的字,可读性反而变差;十几个元素同时随手指明暗,画面持续躁动。Apple 只对 pinned 叙事区块用 scrub,正文属内容动效,应一次性播完即静。两条驱动开关恒为 false —— 用开关而非注释停用,本站已有「注释说已关闭、代码却还开着」的教训
  20. v2.01揭示回归平台可靠性、Hero 衔接闪黑
    • 移动浏览器上 view() 的 progress 会算错→progress 取自 scrollport 与元素的相对几何;移动地址栏滚动中收缩、停手后又展开,高度变化几十到上百 px,progress 随之跳变:元素刚进视口被判成 1(动画不见),松手时再变一次(文字往下闪)。iOS 地址栏在屏幕底部,影响最重。scroll-driven 与 JS 位置驱动一并限定桌面(pointer:fine),移动端落到 revealIn
    • 收尾摘除不再等静止→旧写法要连续 3 帧 scrollY 不变才摘除,而回跳恰发生在松手那一瞬,摘得比闪得晚。改为滚动中每帧用缓存坐标判定(纯算术、零几何读取);坐标与文档高度只在静止时缓存,避免每帧强制同步布局
    • Hero 衔接处闪黑→.img-wrap 自身那层背景图已被清晰层完全盖住却仍以全屏纹理参与合成,叠加 heroIn 的 scale 后,WebKit 把大尺寸背景拆成多个 GPU 纹理块、更新步调不一致 → 部分块瞬时空,露出底色。改为背景图只挂 ::before(--lqip-src),全屏纹理从两份减到一份
  21. v2.00v2 大版本:从「修不完的动画 bug」到「确立平台策略」—— 并附 v1 全版本总结
    • v1 回顾 · 性能分级(v1.01→v1.80)→分级系统从「判定分支永远走不到」起步,历经可恢复、形态因子、GPU 画像、分平台定标,到 v7 不再误降高性能设备。核心教训:静态画像(核数/内存)区分不了中端机,CPU 基准又会被小核调度污染(同一台旗舰跑在小核上耗时可翻倍),最终改为「不够快不预降级 + 内容加载期间豁免差评」
    • v1 回顾 · 动效与材质(v1.14→v1.82)→缓动曲线对齐 Apple、材质对齐 Liquid Glass、动效与圆角确立分级规则(时长按元素短边取、三条曲线按运动语义定用途)。核心教训:同类 hover 曾有四档时长并存(0.35/0.5/0.8s),这是「不像一套」最直接的来源;交互反馈的上限是 0.5s,再长会被感知为迟钝
    • v1 回顾 · iOS / Safari 专项(v1.08→v1.90)→滚动由合成器驱动而 JS 写入必经主线程,这一结构性滞后是所有 iOS 动画问题的共同源头:文字硬闪、纹理块撕裂、地址栏闪黑、切页后下滑失效。修法始终是「让动画走合成路径」,而不是调参
    • v1 回顾 · 菜单与抽屉(v1.78→v1.93)→移动端菜单确立两段式:容器先就位、内容再依次浮现。手表与手机的全部差异都源于「全屏高斯模糊的像素量」—— 手表屏只有手机的 1/6,所以掉帧只在手机显形、抖动只在手表显形
    • v1 回顾 · 揭示动画(v1.95→v1.99)→入场动画四易其主:transition → JS 位置驱动 → CSS scroll-driven → 回归 animation。每一版都是为解决上一版在 iOS 上的失效,最终证明 scrub 在移动端不可靠:地址栏高度变化会让 progress 跳变
    • v1 回顾 · 工程化与无障碍(v1.07→v1.87)→模块地图、全站审计、运行时冒烟、对比度逐像素实测、焦点可见性与读屏播报。核心教训:语法合法 ≠ 能跑,静态检查抓不到只在运行时暴露的问题
    • v1 回顾 · 一次重大事故(v1.77)→变量定义在 1623 行却在 1066 行被使用,暂时性死区让主 IIFE 当场中断 —— 整站图片停在模糊、菜单无反应。此后新增运行时冒烟测试,用 jsdom 真实执行全部 7 个页面并模拟滚动、点菜单、开灯箱
    • v2 的定位 · 可靠性优先于可中断性→v1.95~v1.98 曾追求「动画跟手、随时可中断」,但本站既无 pinned 叙事区块,移动地址栏又会让 progress 跳变,scrub 只会让文字长期停在半透明、十几个元素同时明暗。v2 回到 Apple 对内容动效的做法:一次性播完即静,可中断性只留给 Hero 视差这类图形动效
  22. v1.99安卓松手闪现与菜单毛玻璃过慢
    • 安卓松手瞬间文字从上往下闪一段→view() 的 progress 由 scrollport 与 subject 的相对几何算出,Blink 在滚动结束时做一次几何同步,这期间 progress 短暂回跳、文字退回 range 中段
    • 静止后把已完成元素摘下 timeline→连续 3 帧 scrollY 不变才执行一次:加 .rv-done 令 animation:none,元素回到基础态=终态,此后引擎再怎么重算几何它都不动。摘除本身不可见,因为基础态与终态逐值一致
    • 只在静止时读几何→滚动期间读 rect 会触发强制同步布局,正是本站已修过的 iOS 掉帧成因;滚动只负责武装 rAF 盯梢,不设节流
    • 判据必须用各自的 --rv-off 反推→错落让每个元素的 range 终点都不同,用统一阈值会早摘 —— 进度没到 1 就被摘除,表现为可见跳变。阈值 = 视口高 − 行程 − off,另留 4px 余量宁晚勿早
    • 菜单毛玻璃比回弹还慢→延迟原本直接写 --drawer-in(0.5s):面板停稳后才起步,到 0.75s 才满,整段压在菜单项的过冲回弹上。改为由 --drawer-blur 反推起步,使满值时刻恰为面板停稳的 0.5s —— 即回弹开始之前
    • 敢与滑动末期重叠的根据→--ease-out 后 40% 时间只剩很小剩余位移,重采样成本与「变了多少」相关,不单与「是否在动」相关。手表端同式,半径仍 4px
  23. v1.98揭示改走 CSS scroll-driven:进度交还引擎
    • JS 位置驱动在 iOS 上必然滞后→WebKit 对 scroll 事件防抖+批处理,惯性滚动期间间隔 85~110ms,rAF 里读到的 scrollTop 比合成器真实偏移落后 1~3 帧(flick 时误差 ±23px)。每帧算出的进度本身就是 1~3 帧之前的位置,再优化写入也无用。Blink 间隔 42~65ms、误差 ±8px,故桌面与安卓正常 —— 结构性差距,不是代码问题
    • 进度交还引擎→支持 animation-timeline:view() 时加 html.rv-sda,进度由引擎按合成器线程的实时偏移计算,不受主线程节流影响。Safari 26.4 起 threaded,完全跑在合成线程。opacity 与 translate 都是合成器友好属性,这条路径天然就是「动画是交互的附属」
    • range 必须用长度而非百分比→长度偏移从 range 起始边(scrollport 底边)起算,与 subject 自身尺寸无关;百分比的 entry range 长度等于元素自身高度 —— 一行 30px 的字会在 30px 内播完,快到看不出过程
    • 基础态必须可见→view() 在 subject 高度为 0、被 contain:paint 截断或祖先 height:0 时会放弃计算,animation 不运行;若基础态是 opacity:0,内容就永久不可见。改为 opacity:1、由 from 帧提供 0
    • animation-delay 对 scroll-driven 无效→错落改由 --rv-off 偏移 range 起点承担,语义即「多滚一点才轮到它」。JS 只在初始化时写一次,此后滚动路径上不再有 JS 参与
  24. v1.97iOS 下滑位置驱动掉帧:合成层空转 560px、will-change 漏 translate
    • 元素持有 will-change 却没有动画在跑→沿用了 transition 时代的「提前一屏落 .pre」,位置驱动下元素从进入窗口到开始渐显之间横着 560px 空档,那一整段白占合成层。iOS 层数量是硬约束,超限后 WebKit 频繁做「提升↔降级」调度,每次降级都要重绘 —— 正是「下滑掉帧、滑过一段才恢复」。改为按需提升,锁定即释放,持层者从十余个降到 2~5 个
    • will-change 漏了 translate→位置驱动写的是 translate 独立属性(transform 要让给桌面 3D 倾斜),而 .reveal.pre 只声明 will-change:opacity,transform。规范未明确 transform 是否连带优化 translate,实现也不一致;一旦不算进优化范围,每帧写位移就退化成重绘
    • 同时渐显的元素加上限→桌面 8 / 移动 5,超出直接就位 —— 不让每帧写入次数与常驻层数无界增长
  25. v1.96位置驱动的两处静默失效:图片不显现、末尾停在半透明
    • 桌面端 37 张作品图永不显现→揭示主循环的排除条件写成 kind !== mask,而 rvTick 只接管 kind === reveal —— .shot 被两条路径同时排除,既不被位置驱动接管,也进不了一次性入场的 hits。.shot 基础态正是 opacity:0,于是整页图片空白。改为只排除 kind === reveal。该 bug 语法合法、静态检查全绿、运行时不抛异常,只有真实加载页面并检查可见性才能发现
    • 文档末尾的文字永久停在半透明→滚到底时可见底部最多等于文档高度,因此距底部不足一个行程的元素进度永远到不了 1,会一直卡在几成不透明度。移动端尤其容易命中(页脚加 section 的 padding-bottom 常不足 360px)。改为按「滚到底刚好完成」缩短行程,行程不足 40px 或直接到底则立即就位
    • 旋转屏幕后揭示快慢不对→行程按视口高度取,却只在脚本载入时算一次。竖屏 844 → 360、横屏 390 → 180,不重算的话旋转后仍按旧行程换算。改为随视口变化重算
    • 新增回归测试→rv_shot_test.js 用真实页面加受控几何,分别验证位置驱动开与关两种模式下图片都会显现,并做反向验证:把判据退回旧写法必须被检出。末尾可完成性同样带反向验证(回退后确有元素滞留)
  26. v1.95入场动画改为滚动位置驱动:可停住、可倒放
    • 入场动画与滚动脱钩→进度原本只由时间推进(越过边界即加 .in,随后 0.8s animation 从头播到尾),与滚动位置无关。于是往回滚时正在渐显的字不会回退、停住时它仍自己走完、快速滑过时整屏同时起跑。改为把进度绑到位置:p = (可见底部 - 元素顶边) / 行程,动画因此成为交互的附属而非独立的播放过程
    • 做成完全双向会让已读内容重新消失→位置驱动天然双向,但那意味着往回看时读过的内容会重新变透明 —— 是更差的体验。故规定:进度到 1 即锁定终态并移出活跃集合,此后不再回退。二者不矛盾:只有尚未完全显现的元素跟手,读完的部分永远在
    • iOS 正文浮现硬闪被彻底移除→v1.93 已把入场从 transition 换成 animation,但那只是绕开:transition 的启动前提是浏览器能拿到与终态不同的 before-change style,而 iOS 会把滚动期间的两次样式更新合并到同一帧。位置驱动每帧直接写终值、不依赖该前提,根因消失
    • 位移改用 translate 独立属性→桌面 3D 倾斜(模块 ⑭)要写 transform,两者共用一个属性会互相覆盖。translate 是独立变换属性,与 transform 各占一条通道。全程禁用 transition 与 animation:位置驱动每帧写值,再叠加任何过渡都会变成「写值→过渡」的二次延迟
    • 错落由时间改为滚动距离→原本的错落是固定毫秒(--rd),位置驱动下没有时间轴。改为把毫秒折算成「多滚一点才轮到它」,于是滚得快时错落自然压缩、滚得慢时更舒展,比固定毫秒跟手
    • 不满足条件时完整退回一次性入场→不支持 translate 独立属性、或开启减少动效 → rvLive 为 false,不加 html.rv-live,行为与改动前完全一致。图片(.shot)与行标题(.line-mask)始终走原路径:前者绑定了 LQIP 交叉淡化与灰度,后者是「裁切区外整行上移」且基础态即可见,没有对应的自然映射
  27. v1.94自定义光标:触屏笔记本永不出现、载入即空窗、移出窗口后消失
    • 带触摸屏的桌面设备永远没有自定义光标→启用门槛写的是 !("ontouchstart" in window),一次性否决:只要设备带触摸屏(Windows 触屏笔记本、Surface 极常见),即便全程用鼠标也永不创建。而纯鼠标设备测试永远复现不了。改为只按 pointer:fine 建元素(不可见、零成本),是否接管交由 pointermove 的 pointerType 判定,只有 mouse 才接管
    • 触摸点按不能误触发接管→手指点按时浏览器会合成一套兼容性 mouse 事件(含 mousemove),旧写法正是靠 ontouchstart 挡住它。改由 pointerType 挡:pointermove 报 touch/pen 直接返回;无 PointerEvent 的老浏览器才退回「mousemove 即视为鼠标」
    • 页面载入后鼠标静止时一个光标都没有→旧逻辑在脚本执行时就加 .cursor-ready 藏掉系统光标,而自定义光标要等首次 mousemove 才显形,中间那段(只读、或只用滚轮滚动)就是空窗,最长到 1.5s 兜底才恢复。改为接管只发生在确认收到鼠标输入之后,且与 snapIn 在同一帧完成,既无空窗也不闪
    • 指针移出窗口后光标消失→mouseleave 只摘 .on、留下 .cursor-ready,于是「系统光标已藏、自定义已隐」,在移回之前一个光标都没有。移到地址栏、开右键菜单、拖到窗口外都会走进这里。改为 release() 一并交还系统光标
    • main.js 未执行时永久无光标→<head> 的前置脚本会提前加 .cursor-ready,却没有任何兜底:main.js 一旦被拦截、404 或报错,系统光标被永久藏掉而自定义光标从未创建。现在前置脚本 1200ms 内若没收到 main.js 的 cursor-live 回执就自行撤销
    • 兜底改为每次接管时重新武装→旧的一次性 setTimeout 只覆盖「载入后没动鼠标」一种情形,覆盖不了中途掉进空窗。改为 armWatchdog(),接管即武装,任何时刻空窗都能自愈
    • 前置接管改为只对确认过的鼠标会话生效→前置脚本如今要求 sessionStorage 的 xc-cursor-mouse 标记;触屏设备不再被提前藏掉系统光标,切页防闪的效果在鼠标会话里保持不变
  28. v1.93手表模糊改回同步登场、iOS 衔接处闪黑与下滑文字硬闪
    • 手表端模糊比字体晚近 1 秒→v1.92 用「推迟到菜单项全部落定后再渐入」规避抖动,抖动确实消失,但起步被推到 ≈0.945s,与菜单项脱节。改为同时登场(delay 回到 --drawer-lead),改从降负载入手:模糊半径 8px → 4px,光栅化成本 ∝ r²、约为原来的 1/4,弱 GPU 在并发期不再触发低质量采样降级。极小屏上 4px 依然可辨,观感损失很小
    • iOS 首页清晰图与模糊层衔接处偶发闪黑→两层时间轴严丝合缝:清晰层淡入在 t=--t-reveal 结束,模糊层恰好同刻开始淡出。只要清晰层晚到一帧(iOS 主线程正在解码/光栅化,极常见),中间就露出 .img-wrap 底色 #0d0d0f —— 近黑,不明显且时有时无。统一加 --lqip-gap(0.12s) 余量(桌面与移动两份规则),JS 移除 LQIP 背景的时刻同步后移,避免早于模糊层 hidden
    • iOS 往下滑时正文文字硬闪,行标题却正常→transition 启动的前提是能拿到与终态不同的 before-change style;iOS 滚动期间主线程样式更新被推迟,.pre 与 .in 是在 scroll 回调里同一批写下的,一旦合并到同一帧,浏览器看到「值没变」就跳过过渡、直接跳终态。animation 没有这个前提,而「IN FULL COLOR · 8 幅」那类行标题用的正是 animation,故一直正常。正文入场改由 animation(revealIn) 承担,.in 的终态声明保留为兜底
    • 合成层提前提升改走 scroll 路径→揭示前一屏落 .pre(will-change),此前依赖 IntersectionObserver,而 IO 回调在 iOS 惯性滚动期间同样会被推迟;改由同一条 scroll 路径按缓存坐标判定,只写不读,及时性才有保证
    • 入场动画结束后必须撤掉 animation→fill-mode:both 会让 transform:none 以动画优先级长期生效,高于一切普通声明,该元素此后再也无法被 hover 规则改变 transform —— 首页 color-feature 上 .reveal 与 .glass-hover 同时出现,正是「悬停没反应」。.anim-done 撤掉 animation 后回到普通声明,视觉一致但 transform 重新可覆盖
  29. v1.92安卓手表抽屉模糊仍抖:改为动画结束后再渐入
    • 安卓手表上模糊依旧跟着弹跳变化→上轮的恒定模糊没有覆盖到安卓手表 —— 旧条件「短边 ≤280」漏掉了常见的 360×360 CSS px 方屏,修复根本没在安卓上生效
    • 恒定模糊在安卓上照样抖→根因不在渐变:只要 backdrop-filter 处于活动状态、而页面同时有 transform 动画在跑,弱 GPU 就会把它降到低质量采样;菜单项入场加回弹是 transform 动画,即便模糊元素在面板停稳后已静止,仍在降级窗口里
    • 改为全程无活动模糊→基础态保持与手机一致的 none,只把 drawerBlur 的起步延迟推到菜单项全部落定之后(lead + step×4 + --t-mid ≈ 0.945s),静止后 0.25s 内浮现。展开与回弹期间没有活动的 backdrop-filter,弱 GPU 无东西可降级
    • 断点放宽到两边均 ≤440→近似正方的极小屏才命中;最小手机长边 480(iPhone 4)不会误伤。CSS 媒体查询与 JS isWatch 同步调整,保持一致
  30. v1.91手表端抽屉毛玻璃不再跟着弹跳抖动
    • 手表端模糊跟着菜单项弹跳减弱,弹跳完才恢复→v1.89 的分段模糊把 0.25s 渐变(0.5~0.75s)与菜单项入场加回弹(0.325~0.945s)完全重叠;手表 GPU 在多层并发动画期间把 backdrop-filter 降到低质量采样以保帧率,动画结束才恢复全质量
    • 手机端不显形→同样的并发下手机 GPU 有余量,不会触发降级 —— 所以只有手表看到模糊在抖
    • 手表端改为恒定模糊→基础态即 blur(8px)、不做渐变。v1.89 之前手表本就是全程模糊,242px 屏的采样量只有手机的 1/6;分段方案是为手机的每帧全屏高斯模糊设计的
    • 断点与形态因子一致→短边 ≤280 且长边 ≤400 才命中;只写 max-width 会误伤 320px 的老手机
  31. v1.90修 iOS 切页后往下滑文字硬闪
    • 切页后一滑文字就闪现,滑一段又正常→iOS 的 scroll 事件在惯性滚动期间不是逐帧送达:起步先沉默,再把积压位移连同密集时间戳一起补发
    • 补发样本让速度严重虚高→补发回调 dt 只有 1~2ms,dy 却是几十毫秒里滚过的总和,dy/dt 轻易算出数千 px/s 并顶到 3000 的上限
    • 首次回调同样不可信→rLastT 初值 0、rLastY 初值 -1,dt 被算成「页面加载到现在的时长」,dy 还多算 1px;切页后手指已在动时这条就会命中
    • 提前量被顶到上限→24 + 速度×120ms 在 3000px/s 时为 384px,叠加二次确认的 240px 容差,屏幕外一整屏半的元素在滑到之前就把 0.8s 动画播完,滑过去只剩终态。后段速度降下来测量恢复准确,动画才回来
    • 只采信可信样本→首次回调只立基准;dt<8ms(快于 120Hz)视为补发不采信;dt>120ms 或 dy>1200 归零;其余做一次平滑,单帧尖峰不再直接决定提前量
    • 双 rAF 在 iOS 上会失效→Safari 滚动期间节流甚至挂起 rAF,停下时集中补发,两次回调落在同一帧 —— .pre 与 .in 同帧生效,浏览器认为值没变,transition 直接跳终态
    • 改为同步强制一次样式计算→布局是文档级的,读一次即刷新整棵树,故每批只付一次代价而非每元素一次。不再依赖帧时序,.in 当帧就位,动画立刻开跑
    • 正好解释「切页后当屏有动画」→页面加载与静止状态下 rAF 正常,当屏的字照常浮现;一进入滚动就被节流,于是只有下滑时没有动画
  32. v1.89菜单回弹同步为手表手感,并消除滑动期掉帧
    • 手机上回弹生硬且掉帧,手表端却很好→两件事其实同源:底色伪元素是移动中的抽屉的子元素,面板每帧位移它覆盖的背景区域就每帧变化,backdrop-filter 只能每帧重采样再做一次全屏高斯模糊。手表屏 242px 高的像素量只有手机的 1/6,所以手表流畅、手机掉帧
    • 按是否在动分段而不是简单删掉→面板滑动的 --drawer-in 期间无模糊(基础态 none),停稳后用 @keyframes drawerBlur 在 0.25s 内渐变到 blur(8px),forwards 保持。此时面板已停、主线程空闲,不会掉帧;静止时背景不变,模糊结果本就可复用
    • 起点写 blur(0px) 而非 none→none 到 blur() 不可插值,写成 0px 这 0.25s 才是渐变而非跳变
    • 过冲量随屏幕尺寸漂移→回弹是「面板剩余行程」与「文字自身位移」叠加出来的,而面板行程等于视口高度,于是手表 12.8px、手机 10.6px,离散 21%
    • 起步比例 0.76 改 0.65、文字时长改 --t-mid→文字自身运动越慢越久,它在面板停住那一刻的剩余行程就越不依赖面板还剩多少,等于稀释掉设备差异那一份。离散降到 6%,回弹段由 0.228s 延长到 0.304s,落回去更从容
    • 位移 26px 改 32px→26px 不到字号的 1.1 行,在手机的大幅面板运动里偏弱;32px 约 1.3 行,配合加长的时长让文字自身运动更读得出来
    • 测试脚本从注释里读参数→位移取自 .nav-links a 基础态,而那块里的大段注释提到过历史值 translateY(26px),正则命中了注释字面量——CSS 早已改成 32px、测试却一直按 26px 演算并通过。这类缺陷比没有断言更糟:永远绿且永远陈旧。改为从 @keyframes 的 from 帧解析
    • 测试脚本把收起时长当成入场时长→v1.89 之前两者同为 --t-fast,读错也不显形;入场改到 --t-mid 后读错就会按 0.35s 推演 0.5s 的动画。已分开解析
    • 跨设备判据改用相对离散度→旧判据是绝对差 ≤6px,10/16 同样是差几像素却能放过,而那已不是一个手感。改为相对离散 ≤12%
  33. v1.88撤掉异步样式表,消除加载与切页闪现
    • 首屏与每次切页都会闪一下→完整表改为 media=print + onload 异步加载,晚于首帧到达;内联关键块只覆盖了一部分规则,未覆盖的属性先回落浏览器默认值,于是露出一段未样式化的渲染
    • 最明显的两处→.skip-link 靠 transform 隐藏,未加载时以普通链接停在左上角;按钮里 .arw svg 靠 CSS 设 fill:none,未加载时 SVG 默认 fill 为黑,箭头变成实心黑三角
    • 覆盖面补不全→关键性判定按类名精确匹配,.btn-glass、.hero-cta-row、.display-2 等都不在集合内;只要漏一条就会闪,等于要求内联块与首屏完全等价
    • 改为单一阻塞样式表→55KB 单表在二次访问即命中缓存,异步省下的那一个 RTT 换不来可见收益,却带来必然的闪;恢复此前表现
    • critical_css.py 加 --revert→一键还原为阻塞加载,避免日后重跑 --apply 又把异步写回去
  34. v1.87无障碍:对比度实测、焦点可见与读屏播报
    • 次级文字在 Hero 照片上不达 AA→一直按纯黑底估算,而首屏文字压在照片上。改读 Hero 原图逐像素采样、叠遮罩算真实对比度:--fg-3 仅 4.16:1、说明文字 2.64:1,均低于 AA 的 4.5;同两色在纯黑底是 10.18 与 6.45——差距全在「照片之上」
    • 只在首屏局部提亮→不动全局 --fg-*,否则整站层次被削平;改为 Hero 作用域内重定义。改后实测 --fg-3 最低 5.16:1、--fg-2 最低 6.29:1,四个采样带全部达标
    • 筛选后读屏听不到结果→作品计数是纯文本节点,切换栏目后屏幕阅读器无任何反馈;加 aria-live=polite
    • 计数在一次筛选里被写两次→筛选函数与计数同步各写一次,live region 会重复播报同一句;改为值未变则不写
    • 跳过链接按标准判定→WCAG 2.4.1 只要求「重复出现的区块」前提供跳过链接,404 与 offline 无导航栏,本就不需要 —— 改的是判定规则而非强行补一个无用链接
  35. v1.86菜单回弹、导航硬切与首屏关键 CSS
    • 移动端菜单字体回弹且掉帧→线上跑的是旧压缩版,源码修复从未重建进产物。旧基础态带 translateY(26px),收起靠「0s 时长 + 等同于淡出的 delay」复位;delay 按时间触发而 opacity 按帧推进,抽屉展开时全屏 backdrop-filter 正忙易掉帧,到点 opacity 未归零,26px 下跳被看见
    • 位移整个交给 keyframes→基础态不声明 transform,入场位移写在 @keyframes drawerItemIn 的 from 帧;收起时 animation 一移除,终态(none)回到基础态(none),无跳变。另:压缩产物须与源码同步,否则修复不生效
    • 开启减少动效后菜单文字空白半秒→该查询只压 animation/transition-duration,delay 原样保留;drawerItemIn 是 fill:both,延迟期间停在 from 帧(opacity:0),面板已就位而文字要等 0.38~0.54s
    • 该查询内补 delay 归零→animation-delay 与 transition-delay 一并置 0s !important,延迟不属于 duration,只压 duration 管不到它
    • 点导航跳转是硬切→navGlass 为「先关菜单再跳转」调了 stopPropagation,把 pageLeave 里 document 上的离场淡出一并挡掉,只剩入场那一段;拦冒泡本为避开跨文档 VT 快照问题,而 @view-transition 早已移除,拦着只剩副作用
    • 本处自己补上离场淡出→抽屉收起与 <main> 淡出并行,跳转取两者较长者 +10ms,并保留导航被拦下时撤掉离场类的兜底
    • 4 条降级规则从未生效→`-webkit-backdrop-filter:none` 后漏分号,紧接又写 backdrop-filter:none——CSS 把两条解析成一条(值 = none backdrop-filter: none),值不合法整条丢弃且不报错;.perf-lite 的 glass/to-top、.perf-min 一组、.hero .btn-glass 全部空转
    • 补分号并加检测→verify.py 新增「值里出现括号外冒号」检测——分词级解析器不校验值,这类错误它发现不了
    • 首屏关键 CSS 内联→27KB 内联进 <style>,其余改 media=print + onload 异步,首屏少一个阻塞 RTT;带 noscript 兜底,注入脚本就地替换已有块
  36. v1.85预览带:第二轮清晰度与优雅渐停
    • 第二轮图片变成高斯模糊→克隆体由 cloneNode 得来:⑨ 的加载连线在脚本前段一次性挂给当时的 .img-wrap,克隆体不在其列;且它带 loading=lazy 又在横向滚动区视口外,浏览器不发起加载——清晰层始终 opacity:0,只剩 LQIP
    • 克隆体改为 eager 并重新连线→强制 loading=eager(URL 与原图完全相同,走缓存、零新增流量),克隆后就地再接一次加载连线;instant 跳过最短模糊停留,第二轮滚过来直接清晰
    • hover 时带子突然停住→pointerenter 直接 cancelAnimationFrame,速度在一帧内从 38px/s 归零
    • 起停改为速度渐变→hover 时在 --t-slow 内匀减到 0(匀减速即均匀摩擦),静止后 tick 自行停帧;移开用 smoothstep 在 1.5 倍时长内加速回常态,一律从当前速度起算,中途移开可平滑反向
    • 无 IntersectionObserver 时带子永不启动→inView 置位写在初始 stripSync() 之后,那次 arm 被拒后再无人启动;有 IO 时由观察器回调补上,没有就永久静止
    • 置位后立即补一次 arm→else 分支里 inView=true 之后直接 stripArm()
    • 分级落定晚于带子进入视口时永久静止→那一刻 html 仍带 perf-min,arm 被拒;IO 不会因元素未再进出视口而二次回调,采样结束移除 perf-min 也无人知晓
    • 监听 html 的 class 变化→MutationObserver 仅在「是否启用」真正翻转时才 sync,is-scrolling 这类每帧切换的 class 不会反复触发
  37. v1.84移动端菜单:两段式时序
    • 菜单项动画看不见→面板与菜单项是并行的:面板位移一整个身位(约 780px)而菜单项只走 14px,文字的相对运动仅占面板的 2.8%,被完全淹没 —— 观感就是文字随面板一起到位,没有动画
    • 改为面板先就位、文字后浮现→面板缩到 --t-mid(0.5s),菜单项等面板走完 76% 才起步、逐条错开 40ms;最差一条的相对速度比从 0.028 提到 0.558
    • 起步延迟由面板时长推导→--drawer-lead = --drawer-in × 0.76,改面板时长时文字自动跟随,避免父子时长各写一份导致的失配
    • 关闭太快→收起面板原本 0.5s 而展开 0.8s,落差 0.3s;放慢到 0.62s,仍快于展开的总感知 0.89s(含文字错开),满足退出快于进入
    • 收起时文字不再反向位移→位置重置用 0s 时长 + 等于淡出时长的 delay,推迟到完全透明之后;否则文字向下走而面板向上滑,与展开时的掩盖同源
    • 位移 14px 提到 26px→14px 不到字号(1.5rem)的六成,即便面板静止也偏弱;26px 约等于一行高度
  38. v1.83移动端菜单:恢复自上而下展开
    • 菜单变成模糊渐变→为规避 Safari 26 的 Omnibox 硬切,上一轮把面板位移一并去掉、只留原地淡入 —— 抽屉因此丢掉「展开」这个最核心的手感
    • 位移与 Omnibox 并不冲突→Safari 采样 fixed 元素自身 background-color 的计算值而非逐帧像素;底色已搬到 ::before(伪元素不在采样范围),故 transform 怎么写,色调都恒回落 body 纯黑
    • 底色随面板下滑并叠一层淡入→面板走 --t-slow 揭示档、底色 --t-mid 淡入,菜单项 0.14s 起步逐条 60ms 错开;收起 --t-mid,点菜单项跳转压到 --t-fast
    • 弱机上抽屉被叠了两层模糊→兜底规则 .perf-lite .nav-glass 未设断点,(0,2,0) 压过移动端 backdrop-filter:none——全屏抽屉又带上 16px 模糊,与 ::before 的 8px 叠加;收进 min-width:861px
  39. v1.82动效与圆角:确立分级规则,消除同语义多值
    • 三条缓动曲线凭手感取用→三者同属 ease-out 家族、差异只在尾部,此前无选用规则;按运动语义定死边界:反馈用 swift、揭示与循环流动用 out、带 filter 的复合过渡才用 ease
    • 同类 hover 快慢不一→大卡片 0.8s、玻璃按钮与小标签 0.5s、筛选键 0.35s 四档并存;按元素短边收敛为两档(≤64px 用 t-fast,其余 t-mid),交互反馈上限锁定 t-mid
    • 大卡片 hover 抬升偏迟钝→0.8s 会让指针离开后卡片仍在爬;改 t-mid 0.5s,位移幅度改由 --lift 补偿而非拉长时长
    • 两条引导流动线节奏不一致→首屏 drip 2.4s 配 --ease、页脚 guideDrip 3.4s 配 --ease-out,快慢差 42% 且曲线不同;统一 --t-loop 2.4s + --ease-out
    • 循环动画未纳入分级说明→--t-loop 刻意不乘 --dur-scale:压时长等于加快循环频率,降级时反而更躁;正确做法是停掉而非加快
    • Hero 沉降 2.4s 超出规范却未登记→规范称 1.1s 为最慢档,实际存在 2.4s 的 heroIn;单列 --t-drift 属氛围档(此时在读标题,无人等它缩完),仍乘 --dur-scale 减轻首屏压力
    • 按压时长裸写 0.12s→与 --t-exit 同值却各写一份,调整该档时必漏改;改为引用 token
    • 跳转定时裸写 360ms→与 CSS 的 --t-fast 失配;改读 cssMs(),早跳会让收起动画被页面卸载截断
    • 圆角分级只有数值没有规则→补容器语义边界与同心不变式,并修正一处引用了不存在类名的注释
  40. v1.81灯箱左右切换键:全分级满血
    • 降级后切换键质感变差→L1 规则写的是 .lb button,特异性高于 .lb .lb-prev,把切换键连带降到 blur(8px) 并抹掉 brightness 与外层投影
    • 切换键豁免全部降级→身后静止(灯箱打开时滚动已锁定)使 backdrop-filter 只采样一次而非每帧,且两个 48px 键不到视口 0.3% —— 成本可忽略,收益是灯箱唯一的导航控件
    • 关闭键仍按原样降级→L1 降半径、L2 深色底补偿都只保留关闭键;深色底那条若包含切换键,会把刚豁免的满血亮玻璃重新覆盖掉
  41. v1.80性能分级 v7:不再误降高性能设备
    • 中端安卓进页面就掉质感→静态画像把「6 核 + 4GB」直接打成 L1,而它跑本站往往毫无压力;改为中端区间一律交 CPU 基准,绝不预降级
    • CPU 门槛按移动端重标→旧值按桌面 V8 实测拍定,未计入小核调度与 JIT 编译深度 —— 中高端手机常落在 0.5~1.2 却被判「不够快」即降 L1;改为只有明确慢才收敛
    • Safari 与 Firefox 少算一分→modernEngine 只认 Chrome 与 Safari 的版本号,Firefox UA 里两者皆无;4 核桌面因此落 mid,而 mid 降级门槛低得多
    • 桌面 4 核被判 mid→Safari 把核数 clamp 到 4 或 8、且不实现 deviceMemory;补一条桌面核数达标的加分,内存读不到不等于内存不足
    • 图片解码被当成设备卡顿→解码完成即标记一小段内容工作期,期间只跳过差评、好评照常累计 —— 解码不在渲染管线上,降动画治不了它
    • 加载期定性过于容易→20 帧里 11 帧慢就降 L1,一次字体替换就够;提高到 32 帧且需 68%,只有绝大多数帧都超时才定性为弱机
  42. v1.79iOS 26 打开菜单时地址栏闪现变黑
    • 抽屉从上方滑入,下边缘最后才被盖住→面板 translateY(-100%) 归位要 0.8s,而淡入仅 0.5s —— 地址栏位于屏幕下边缘,等面板扫到那里时淡入早已播完,看到的像素就在那一瞬硬切
    • 底色改为原地淡入→去掉面板整体位移,底色交给 ::before 只做 opacity 过渡;菜单项本就有错开上浮,观感不变而上下边缘的像素都是连续的
    • 底色与模糊移到伪元素上→Safari 26 采样紧贴边缘的 fixed 元素自身 background-color 作为 UI 色调,该色调按 CSS 推导、不可过渡;伪元素不在采样范围,色调恒回落 body 纯黑
    • 收起与跳转定时对齐→底色退出 --t-fast 快于进入 --t-mid;点菜单项跳转仍与 main.js 的 360ms 定时一致
  43. v1.78移动端菜单项依次浮现
    • 打开菜单是整块一次性出现→抽屉只有面板整体的位移与淡入,文字随之一起到位,没有节奏
    • 菜单项错开淡入上浮→逐条 opacity + translateY(14px) 归位,延迟 0.14s 起步、每条 +60ms;只动这两个属性——子项加 filter 会各自成为 backdrop root,采不到抽屉的模糊
    • 错开只写在展开态→关闭时目标态是基础态、delay 为 0,条目随面板一起收走;反向逐条消失会拖沓。点菜单项跳转时淡出压到 --t-exit
    • 减少动效下延迟归零→全局只压 transition-duration,错开量属 delay 不受影响;不归零会出现「菜单卡一下才加载完」
    • 页脚细白线删除→border-top 在近黑底上仍形成一条可见亮线,把页面横切成两段;改为只靠留白区分,padding 与圆环对齐所依赖的 --footer-pad 不变
  44. v1.77整站卡死:暂时性死区中断主脚本
    • 整站卡死、图片停在模糊、文字不出现、菜单无反应→onScroll 注册即执行一次,导航栏那条要读 wideMQ.matches;wideMQ 定义在 567 行之后,首次执行命中暂时性死区,主 IIFE 中断
    • 四个症状同一根因→noscript 只在禁用 JS 时生效,脚本抛错它不管:reveal 停在 opacity:0 即文字不出现;blur-up 的 load 监听未挂上,图片永远停在 LQIP 模糊占位
    • 静态检查全绿却线上崩溃→该代码完全合法:node --check、CSS 解析、引用检查、SW 预缓存校验一律通过,只有真实执行才暴露
    • wideMQ 前移并加注→移到所有 onScroll 注册之前,注释写明勿再下移;它不依赖任何东西,前移零风险
    • 测试须捕获 rAF 回调异常→jsdom 中 rAF 回调抛错走 Node 的 uncaughtException 而非 jsdomError,只打印会误报通过;已计入当前场景错误列表
    • 已做反向验证→把 wideMQ 移回远处重新压缩,测试确实报出 Cannot access before initialization(退出码 1);恢复后 21 组全通过,证明测试有效而非形同虚设
    • 改源码后必须重压缩→此前只改源码未跑 minify,压缩版仍是旧的有问题代码;已重新压缩并核对压缩版中定义位于使用之前
  45. v1.76iOS 滚动同步、光标接力与菜单淡入
    • iOS 下滑时除图片外都掉帧→iOS 滚动由合成器驱动,图片随页面搬运、永不滞后;而 --hero-p 由 JS 逐帧写入,主线程一忙就晚几帧送达,文字于是相对图片抖动
    • 窄屏改为不写 --hero-p→让文字随页面一起滚,与图片走同一条合成路径必然同步;代价是移动端失去淡出,与之前关闭图片视差同一取舍。宽屏缩到窄屏时复位为 0,避免停在半透明中间值
    • 导航栏量化放粗→--nav-p 每次写入都要重绘并重新采样身后内容,是 iOS 滚动最贵的一笔;窄屏由 0.02 改 0.05,写入减少六成,渐变本就跨 112px 看不出台阶
    • 切页后原生光标闪一下→新页面必须等首次 mousemove 才知道指针在哪;点完菜单鼠标常是静止的,1.5s 兜底判定等不到指针就交还系统光标,指针一动又被替换
    • 光标位置跨页接力→上一页最后坐标存 sessionStorage,新页面就地显形;只在真见过指针后写入,坐标越界则不用;已显形后兜底不再触发,系统光标不再回来
    • Safari 地址栏开菜单时闪黑→Safari 26 起不再读 theme-color,地址栏改为实时采样屏幕边缘像素;抽屉一展开边缘像素瞬变,浏览器 UI 无法用 CSS 过渡
    • 菜单抽屉叠加淡入→在滑动的同时加 opacity 过渡,整屏含边缘一起渐暗,地址栏随之平滑;只加 opacity,滑动时长与曲线不变
  46. v1.75真实体验监控(RUM)与 AVIF 评估
    • 性能判断长期依赖主观描述→站内分级只能观测帧率与主线程阻塞,回答不了实际等多久看到主图、点了多久有反应;新增 RUM 采集 LCP/INP/CLS 并按 p75 统计
    • 样本只存本机不上传→静态站无后端、全站无第三方脚本,故不引入上报服务也不引 web-vitals 库;数据进 localStorage,最近 30 次,?rum 打开面板查看
    • Safari 上 INP 会失真→Safari 至今不支持 interactionId,只能取 FID(仅首次交互、数值偏乐观);面板标为 FID 并注明,不与 INP 混算以免误判
    • AVIF 实测不省反增→5 张样片基准:等效质量(SSIM≥0.99)下 900 档整体仅 -2.3%,1800 档 +10.6%;彩色组与虚拟光影全档位达不到等效质量
    • 放弃 AVIF 迁移→单张编码 1.9~5.3 秒,155 个文件重编成本极高而收益为负;保持 WebP,优化重心转向少加载而非压得更小
  47. v1.74揭示提前量改为时间预算
    • iOS 上 Hero 之后那一屏没有浮现动画→揭示提前量含一段与速度无关的固定值(14% 视口高),页面静止时也照加,等于让屏幕外的元素把 0.8s 动画白白播完
    • 只有 iOS 中招→iOS 地址栏覆盖页面,Hero 的 100lvh 几乎等于可见高度,下一节首个元素距折线仅 .section 的 72px 下限;安卓与桌面间距都在 100px 以上
    • 表现为只有旁白那一行还在动→间距更近的两行在加载瞬间就被揭示,间距更远的旁白与后续各节逃过,于是看起来只有那一处正常
    • 慢速下滑同样会错过动画→固定提前量在慢速下折算成 1~2 秒,远长于动画时长;改为按速度折算的约 120ms 预算,静止与强制复查时为 0
  48. v1.73圆环与页脚平行、预览带自动循环
    • 桌面端圆环比页脚那一行低一截→bottom 与页脚 padding 各写一个 clamp,改一个忘另一个;改为 --fab-bottom 引用 --footer-pad,两者垂直中心恒等高
    • 桌面端六个栏目看不到后面几张→横向滚动条被隐藏、鼠标滚轮不横向带、拖拽无从发现;桌面端改为缓慢自动循环滚动,悬停与聚焦时暂停
    • 循环滚动可能停在末尾再跳回→一整轮宽度大于可滚距离时累加器绕不回来;加守卫,量不够就不启用,回退为可手动横滑
  49. v1.72圆环让位的时机与落点
    • iOS 上滑时让位动画掉帧→盯梢每 2 帧读一轮矩形,滚动期间触发强制同步布局;改为只看时间戳,滚动期零几何读取
    • iOS 上滑时让位闪现→位移过渡在滚动中播完,中间帧被 Safari 推迟的主线程重绘整段丢掉;改为静止后才开始动画
    • 长惯性滚动后不再让位→盯梢累计 90 帧即放弃,超过 1.5s 的滚动必然耗尽它;改为只在静默够了时结束
    • 上滑瞬间圆环提前归位→归位此前不要求静止,与抬起不对称;两者统一等静止
    • 移动端抬升过长、跑出页脚→受保护的是页脚容器空白而非文字;标记下移到版权行与导航行
    • 抬升量被量化推离对齐位→16px 档位使落点偏离版权行基线;优先精确对齐到版权行底边,不可用才退回完全让开
  50. v1.71极小视口让位的量化回退
    • 手表端仍不让位→抬升量向外取整到 16px 后超出可抬空间,整轮判失败归零;超限即退回精确值
  51. v1.70让位上限随视口、缩放过渡与动画解耦
    • 手表端被遮挡也不让位→上限与安全距离写死 64/160px,按手机定的;改为按视口高度取小
    • 快速开图再放大过渡消失→入场动画与缩放过渡同抢 transform;改为输入起始先收动画,隔帧再写变换
    • 放大时高清图淡入吃掉头几帧→高清层未独立成层,淡入触发父层重绘;改为灯箱打开期间单独提升
    • 反复开合后整站无响应→关闭清理是顺序语句,中途抛错则解锁与解惰性不执行;改 try/finally 兜底
    • 滚动锁靠副作用判定→以 body 的 position 当已锁判据,多调一次即永久锁死;改为引用计数
    • 反复开图预取重复发起→同一张 1800 档被反复请求占满带宽;按 src 去重
  52. v1.69防遮挡改为静止判定
    • 回弹时无遮挡也上移→回弹期间矩形是瞬时的;改为连续 3 次采样一致才算静止,再测量
    • 检测时有时无→滚动调度只在 y 变化时跑回调,停下后无帧消费;改用独立 rAF 盯梢
    • 抬起后闪现回原位→resize 直接写 0 导致硬拉回;改为作废后重新测量,走同一条过渡
    • 底部判定复用触底状态→atEnd 已带距离与时间双滞回,不再写第二套到底标准
  53. v1.68补齐响应式图源档位与 sizes 精度
    • 网格缺 1800 档,2 倍屏偏软→文件已生成却未被引用;补齐后仅宽屏 2 倍屏取用
    • 首页预览图 sizes 高估近三倍→按 clamp 三段精确映射,与实际渲染宽度吻合
    • 头像在 3 倍屏差 18%→补出 1200 档;仅 3 倍屏手机会选中
  54. v1.67新增减少透明度偏好支持
    • 毛玻璃对部分人是可读性障碍→处理方向是去掉透视而非调小半径,与 Apple 换不透明实色一致
    • 前缀版只能靠选择器覆盖→前缀为字面量,改变量关不掉;用通配选择器加 !important 覆盖
    • 底色保持在已有区间内→该偏好下取 --nav-scrim 为 1,即设计中最实的一档
  55. v1.66站内链接改为干净 URL、导航预取与预加载
    • 每次点菜单多走一次 301→链接写 .html 被重定向;改为直接链接干净 URL
    • 改链接后当前页高亮失效→两种写法字面不等;两侧同用归一函数
    • SW 启动挡在导航请求之前→启用 navigationPreload;不支持时自动回落
    • 离线时标题字体退回系统字体→字体未预缓存;它恒定不变,已加入 CORE
    • 导航预取改用浏览器原生实现→用 Speculation Rules 的 prefetch;不做 prerender 以免执行目标页脚本
  56. v1.65性能分级 v6:按平台分别定标
    • 安卓与 iOS 共用阈值必然误判→iOS 核数与真实性能无关;改为按平台分别定标
    • 6G 与 4G 读到同一个数→Device Memory 舍入到 2 的幂;同步阶段放行,交 CPU 基准定性
    • iPhone 8 与 iPhone X 分不开→同代 SoC 致 CPU 基准失效;补屏高辅助
    • iPad 被落到最弱档→UA 无 OS 字段致版本为 0;用 Safari 版本做代理
  57. v1.64性能分级 v5:形态因子、GPU 画像与版本修正
    • 手表与旗舰手机判进同一档→新增形态因子:短边 ≤280 判 micro,直接 L2 并锁定
    • 软件渲染的机器被当高性能→本站开销在 GPU 合成而旧版只测 CPU;新增 GPU 探测抓软件渲染
    • 窗口拉窄的桌面误判成手表→形态判定须用 screen 而非 innerWidth
    • 4 核 4GB 中端安卓被预降级→旧判据覆盖约三成中端机;收紧后交回实测判定
    • 桌面 6 核 6GB 进不了高性能档→8 核 / 8GB 门槛过严;放宽到 6 核 / 6GB
    • 老浏览器与新机器同一套门槛→分别取 iOS 与 Safari 版本,老引擎不给高端信任
    • GPU 探测成了新的卡顿源→创建上下文会唤醒 GPU 进程;排在空闲期且用完即释放
  58. v1.63版本日志悬停过渡、唯一高亮与移动端圆环防抖动
    • 标题的灰变白仍是硬切→与版本号同条规则同时变色却只补了一处;两者须同曲线
    • 悬停时出现两个高亮行→首条恒定提亮且特异性更高;用 :not(:hover) 使其唯一
    • 橡皮筋回弹时圆圈来回挪→抬升本质是状态而非几何量;一旦抬起就保持,离开底部才归位
    • 迟回死区小于量化步长→死区 8px 小于步长 16px;移动端死区改为不小于步长
    • 旋屏后停在旧抬起位置→resize 时须把变量与 DOM 同时归零重测
  59. v1.62二维码三级同心圆角与移动端圆环防遮挡
    • 二维码白色区域是直角→同心圆角规则下卡片半径减间距为 0;卡片改 36px 构成三级过渡
    • 链接盒与图片盒不重合→.qr-link 是 inline,width:100% 无效;补 display:block
    • 移动端完全不做防遮挡→检测函数开头即返回;改为按策略分支,移动端只抬起
    • 移动端把圆环抬到屏幕中段→桌面上限 240px 在手机上过高;移动端单独取 160px
    • 滚动途中圆圈一路微调→三道措施:仅底部检测、位移量化 16px、连续两次才写入
    • 页脚过度触发避让→受保护元素打在全宽块上;改打版心盒
  60. v1.61版本日志悬停硬切与关于我统计条居中
    • 日志条目灰变白是硬切→.clog-v 无 transition;补 --t-fast 加 --ease-swift
    • 统计条整体偏右→它是非对称两栏的子元素;改为整行子项横跨两栏后居中
    • 三列均分把统计拆散→1fr 把三项推到两端;改 auto 配 justify-content:center
    • 窄屏折叠成竖排→竖排会拆散并列对比且多占两行;改为保持三列收窄间距
    • 居中时文字看着偏左→letter-spacing 在末字后也加字距;左侧补等量 padding 补偿
  61. v1.60版本日志页的圆环防遮挡
    • 滚到底圆环压住页脚与返回区→新增防遮挡:受保护元素打 data-fab-avoid,圆环检测相交后自挪
    • 切换状态时圆环弹回原位→transform 是单一属性不叠加;三条状态规则全部补齐变量
    • 过渡途中圆环来回抖动→基准须减当前实际位移;改从 transform 矩阵取真实平移量
    • 按压缩放对圆环无效→:active 写在 .show 之前被 transform:none 盖掉;调整顺序
    • 窄窗口正文仍被压住→该区间圆环无处可让;改由列表让出 52px 右内边距
  62. v1.59作品集图片清晰度与渲染开销
    • 桌面端作品图偏软→sizes 写 30vw 而实为两列,浏览器只取 480w 再放大 26%;按真实列宽改 46vw
    • 滚动时每帧强制重排全文档→先加类再读 scrollHeight 退化成强制同步布局;改为先读后写
    • 同一帧反复读取视口高度→四处各问一次 clientHeight 且回调间会写样式;改为每轮实测一次
    • 日志页 931 个标签全参与渲染→第 4 条起交给 content-visibility:auto,已揭示的置回 visible
    • 已缓存的图仍后台重发请求→SWR 每次命中都更新;加 24 小时新鲜窗口
    • 无脚本时入场内容全部不可见→.reveal 默认 opacity:0,交还不透明度靠脚本;各页 head 补 noscript 兜底
    • 导航响应把 5xx 也缓存→只要 fetch 成功就写入;改为只缓存成功响应
  63. v1.58切换页面后的系统光标与页面入场过渡
    • 切换栏目后系统光标仍露一下→接管发生在首次 mousemove;改为初始化即接管
    • 提前接管不会让光标跳一下→显形仍等首次 mousemove,届时先写坐标再无过渡置可见
    • 窗口变窄再变宽光标回不来→原逻辑只在隐藏时释放;改为双向同步
    • 页面切换是硬切→新增 main 入场淡入上浮;只动 main,body 会成为 fixed 包含块
    • 减少动效偏好下仍位移→位移属前庭敏感运动;该偏好下取消入场动画
  64. v1.57桌面端自定义光标闪回系统样式
    • 切换页面时系统光标闪一下→cursor:none 无条件生效而自定义光标需等 mousemove;改由 cursor-ready 门控
    • 作品图上系统手型与自定义环同时出现→.shot 的 cursor:pointer 特异性更高盖掉 none;限定未接管时生效
    • html 与根滚动条区域未覆盖→cursor:none 只写在 body 及后代;选择器补上 html
    • 减少动效、窄视口下变成无光标→CSS 会 display:none 光标元素;接管前以计算样式判断
  65. v1.56手表端短页面进度环停在 99%
    • 滑到底环没合上,上滑才合上→哨兵回调异步送达晚于最后滚动帧,无帧消费;改为回调里就地重算
    • 触底判定三处各写一套标准→收敛为唯一入口:算术、几何、异步兜底取并集
    • 纯算术判定在极小视口不稳→三个量各自取整后相减;几何判定直接问布局
    • 滚动收尾时旧值残留→最后一帧可能早于布局稳定;停止时补跑一次回调
  66. v1.55版本日志独立成页,导航统一为三字
    • 同一份日志在四页各内嵌一遍→重复四份撑大 HTML;改为独立页面,入口移到页脚
    • 折叠动画依赖 JS 叠加→open 是二值属性无法插值;独立成页后交给 .reveal
    • 日志页可能进得来出不去→页内保留页头、页脚、文末三处返回路径
    • 导航与页脚文案不一致→「首页」改「主界面」统一三字,面包屑同步
  67. v1.54版本日志动画失效与单击放大闪现
    • 版本日志展开收起动画未生效→写在主 IIFE 外却引用主作用域变量,第一行即抛错;就地声明后修复
    • 单击放大闪现、缩小正常→重栅后恢复时按 1 倍光栅,放大态又变软;缩小终点即 1 倍故不受影响
    • 装回合成层改在输入起始→改由 armLayer 在 pointerdown 等起始时装回,不与过渡撞帧
    • 版本日志文案统一→条目偏长且混入星号标记;统一三段式并加净化兜底
  68. v1.53桌面端放大查看的过渡消失、闪现与掉帧
    • 点击放大过渡偶发消失→will-change 与过渡同帧生效,增删该属性触发重栅化,该帧必然超时
    • 放大后画面偏软→该属性语义是光栅为固定位图,比例变化后不重栅
    • 图层提升提前到灯箱打开时→入场动画本就要提升同一图层,属复用;关闭即释放
    • 动画结束后补一次清晰重栅→落在画面静止后,感知上是对焦完成;滚轮防抖、松手时触发
    • 入场动画的 opacity 提示被覆盖→will-change 是单一属性不叠加;入场规则补 .lb.open 前缀
  69. v1.52预览带白边与日志收起的重复实现
    • iOS 点击预览带冒出白边→并列写 :focus 与 :focus-visible,前者位置靠后压过撤销规则;只保留后者
    • 版本日志收起又快又卡→存在两份折叠实现,旧的那份加 will-change 触发超大纹理分块渲染
    • 新的折叠实现从未生效→位于主 IIFE 外却引用主作用域变量,读取即抛错;就地声明后接管
    • 收起时长过短→退出用 0.35s,高面板会塌得太快;改为与展开同 0.5s
    • 文件头索引里版本日志重复→旧的与新的并列,实际只有后者;合并
  70. v1.51版本日志的展开过渡
    • 展开后再收起是硬切→open 是布尔属性无法插值;JS 接管做高度动画
    • 动画结束末尾多出一截→子元素首外边距折叠到框外;容器常驻 overflow:hidden 建 BFC
    • 高度动画刻意不加 opacity→整块数千像素高,加 opacity 会触发超大纹理分块渲染
    • 收起时箭头要等播完才回转→收起期间 open 仍为真;JS 在点击时同步状态类
    • 性能分级版本号跳号→v1.43 应记 v3 而非 v4
  71. v1.50缩放过渡、日志排版与触底判定
    • 桌面放大过渡偶发消失→入场与切图动画的关键帧带 transform,与缩放争抢;缩放前先撤销动画
    • 放大与平移时掉帧→缩放期间无合成层提示且图片带 filter;加 will-change,回到 1 倍释放
    • 放大后拖动,紧接的单击被吞→mousedown 未放大时提前返回,位移未归零;归零提到分支前
    • 版本日志与页脚版权没对齐→日志写死 820px 而版权用 .wrap;改为共用 .wrap
    • 手机上版本徽标被圆圈压住→徽标原推到行尾落在 fixed 圆圈下;改为排最左
    • 手表端部分页面进度环无法闭合→三个量各自取整,极小视口误差致 y 够不到 max;新增触底哨兵取并集
  72. v1.49首屏文字层抖动与点击焦点环
    • iOS 下滑首屏文字掉帧、图片没事→关闭视差的函数连带降级文字层,致其每帧创建又销毁;拆开两件事
    • 滚动淡出写入范围过大→--hero-p 是继承属性,写在 .hero 上致整棵子树重算;改为写给实际消费者
    • 点击版本日志冒出白色圆角矩形→并列写 :focus 与 :focus-visible,前者压过撤销规则;只保留后者
  73. v1.48层叠一致性与全站审计
    • 筛选栏有与导航同源的层叠隐患→::before 用 z-index:-1 且带 backdrop-filter;改为全正数
    • 全站审计与死代码排查→0 处死代码、11 个 keyframes 全被引用、185 条资源 0 缺失
  74. v1.47旧浏览器兼容
    • Safari 毛玻璃全部失效→前缀值为 var() 会被整条丢弃;11 处前缀改字面量
    • 前缀声明写在标准属性之后→WebKit 后写胜出,失效前缀覆盖有效标准版;改为前缀在前
    • 旧浏览器键盘焦点环不显示→只写 :focus-visible 会被整条丢弃;补 :focus 再由 :not 撤销点击环
    • 不支持 @property 时导航底色失效→--nav-p 无初值致 3 处 calc 无效;补 var(--nav-p, 0)
    • 当前页横线有游离重复声明→删除多余的 background
  75. v1.46可发现性、路由与错误兜底
    • canonical 与实际 URL 不一致→同步为干净 URL,_redirects 固定为 301
    • 结构化数据缺失→新增 JSON-LD @graph:WebSite、Person、CollectionPage、BreadcrumbList
    • 社交分享卡片信息不全→补齐 og:locale、image:alt 与 Twitter Card 各字段
    • sitemap 与 canonical 不一致→改用干净 URL 并补 lastmod 与图片条目
    • 404 页在深层路径下失效→以原请求 URL 呈现,相对路径解析错误;改为根绝对路径
    • 离线时首页内容被当作任意页兜底→SW 兜底原为 index.html 形成软 404;改为 offline.html
    • 图片加载失败留下空洞→捕获阶段委托监听换占位 SVG,保留尺寸;只换一次防递归
    • 图片请求双未命中返回 undefined→改为回退占位 SVG
    • 响应头与缓存策略缺失→新增 _headers,CSS 与 JS 不设 immutable
  76. v1.45按钮图标的光学对齐
    • 按钮箭头各平台长得不一样→箭头原为文本字符,字形随字体变化;改为内联 SVG
    • 箭头未按光学中心对齐→头部墨量集中,重心偏离中心 1.325 单位;平移路径使重心归位
    • 环的选择器误捕获新图标→.to-top svg 是后代选择器;收窄为 .fc > svg
    • 版本号不可读→v32 至 v80 改为语义化 v1.0 至 v1.44
  77. v1.44无障碍与版本日志
    • 灯箱打开后 Tab 会跑到背景内容→背景加 inert 加 aria-hidden,两者必须成对
    • 菜单打开后键盘操作被卡住→补 Esc 关闭并交还焦点,背景同样 inert
    • 读屏下不知当前在哪页→加 aria-current=page,此前的横线是纯视觉的
    • 切栏目后数量变化读屏不知情→计数加 aria-live=polite
    • 亮底按钮没有焦点轮廓→全局白环落在亮底失去轮廓;改内缩深色环
    • 横向预览带只有鼠标能操作→加 tabindex=0 使方向键可滚动
    • 高对比度模式下按钮形状消失→forced-colors 下用系统色重建,不用 forced-color-adjust:none
    • 404 页缺 main 地标→补 main,读屏可直跳正文
    • 本页:记录固化为站点一部分→静态 HTML 加原生 details,由脚本生成可重复运行
  78. v1.43性能分级 v3
    • 4GB 低端机被整批漏判→Device Memory 舍入到 2 的幂,4GB 报 4 而判据为 ≤3
    • 持续卡顿中夹一个好帧就清零→差评改按 0.6 倍衰减,累加快于衰减
    • 加载期信息被完全丢弃→加载期是天然压力测试;改以宽松门槛参与定性
    • 多个小任务合计超时报不出来→Long Task 只看单个任务;改用 LoAF 按整帧计量
    • 120Hz 上稳跑 60fps 被误判→长帧门槛加 20ms 绝对下限
  79. v1.42移动端首屏视差
    • 滑动时 Hero 缩放且掉帧→注释称视差已关、代码仍开启,注释与实现不一致
    • iOS 上更明显→纹理超 1024px 时 Mobile Safari 强制分块渲染;移动端关闭图片视差
  80. v1.41Safari 的纹理块撕裂
    • 滚动时背景图撕裂→Safari 对 will-change 加之大背景图拆成多纹理块;清晰层就位后移除该背景图
    • 程序化下滑不闪→平滑滚动时地址栏一次性收起,无持续 insets 变化
  81. v1.40常驻提升与写入频率
    • 偶发掉帧与大闪→注释要求提升而值为 will-change:auto;统一为加载时提升、滚过后释放
    • 慢速滑动更易触发→量化步长过细,加倍后写入次数减半
  82. v1.39合成层的创建时机
    • 前几次滑动会闪,之后不闪→合成层在首次滚动帧才创建,创建即光栅化满屏图;改为解码、字体定稿后预热
    • 字体加载完文字掉帧→标题带 24px 模糊阴影,字体替换整层重绘;等定稿后再提升
  83. v1.38滚动阈值与点击可靠性
    • Hero 闪现换方向照样触发→阈值用随地址栏变化的 innerHeight;改用 hero 静态高度并加滞回
    • iOS 连点栏目只响应一次→iOS 把连点判为手势不合成 click;主触发改 pointerup
  84. v1.37连点失效与首屏高度
    • 快速连点按钮不变白→选中态过渡 0.5s,连点每次从头开始;改 0.35s 并加 touch-action
    • 首屏下滚 Hero 闪现缩放→改用静态 lvh 铺满,多出部分由 padding 吸收
  85. v1.36栏目切换跟手
    • 点栏目后要等一会才切换→按钮状态与内容切换同在淡出后执行;按钮状态抽到点击同帧
    • 淡出 350ms 真空期过长→新增 --t-exit:0.12s,150ms 为即时感知阈值
    • 连点栏目显示错乱→改单定时器加待提交值,收尾路径唯一
  86. v1.35审计与体检工具
    • 两张图标了高优先级→首屏 LCP 图只能一张;只保留第一张
    • 读屏下切图时不知情→灯箱图注加 aria-live=polite,高频操作不宜用 assertive
    • 样式表索引被注释弄乱→改为只认分节标题行重建,固化为脚本
  87. v1.34切换与冷启动
    • 切换栏目是硬切→display:none 立即生效、剩余项瞬间重排;改为不可见时切换再淡入
    • 从首页点开大图先黑一下→基层固定取 1200 档,首页小图只下载 480 档;铺占位图在背后
  88. v1.33灯箱期的导航栏与毛玻璃
    • 打开大图时顶部闪现、菜单透出→灯箱背景半透明,导航栏既被透出又被采样;改为灯箱期整体退出
    • 停止滚动后毛玻璃硬切→滚动时设为 none,与 blur 不同构不可插值;改 blur(0.01px)
  89. v1.32快速上滑时的配色过渡
    • iOS 快速上滑时配色过渡消失→非合成属性需主线程重绘,iOS 滚动期间会丢弃;改为只动 opacity
    • 只改圆盘会露出错配色→深浅两套内容共同交叉淡化,同曲线同时长
  90. v1.31环闭合与白色统一
    • 仍有 1% 缺口→触底变白用固定容差、进度环用严格比例;统一为 2%
    • iOS 与 Via 白色亮度不同→白色被画两遍,叠加结果依赖引擎;删冗余伪元素
  91. v1.30层级与滚动上限
    • 负 z-index 在 Safari 不可靠→与合成层叠加时会被画到内容之上;改为全正数
    • 进度环差 1% 合不上→max 用 scrollHeight 减 innerHeight,基准不同;改用 clientHeight
  92. v1.29macOS 导航遮罩
    • Safari 滚动后菜单被黑块盖住→::after 同用 opacity 与 backdrop-filter,Safari 按不透明渲染;改由 alpha 参与底色
    • 存在危险的固定值兜底→background 默认不透明黑,calc 失败即成黑带;默认改 transparent
    • -webkit- 前缀从未生效→前缀值为 var() 会被整条丢弃;改字面量并置于标准属性之前
  93. v1.28短页面的回到顶部按钮
    • 短页面圆环出现过晚→阈值 0.9 乘 innerHeight 与可滚距离负相关;改为同时受可滚距离约束
    • 两处可滚距离算法不一致→抽为共享 helper,守卫常量提为命名常量
  94. v1.27回顶部时 Hero 收缩
    • 点回顶部 Hero 缩放三次并掉帧→回顶部时解冻使 dvh 变回动态;冻结后不因滚动解冻
  95. v1.26Hero 衔接与触底配色
    • 模糊结束后图片位置对不上→入场动画只写在 img 上,模糊层不参与;移到容器使两层共用变换
    • 移动端关闭动画断点失效→动画移到容器后原选择器落空
    • 到底变白过于刺眼→纯白改 #f2f2f5,过渡 0.5 延至 0.8s
  96. v1.25首页全屏闪烁
    • iOS 下拉时画面跳变→.hero 用 100dvh,随地址栏收放改变文档高度;改为冻结为 px
    • 回顶部时问题复现→冻结后不因滚动解冻,仅宽度变化时重测
  97. v1.24菜单当前页指示与回弹闪烁
    • 仅首页有当前页横线→/portfolio 与 portfolio.html 字面不等;两侧归一化
    • iOS 下拉回弹闪三次→距离滞回不够,大力过冲会越界;加 120ms 时间滞回
  98. v1.23iOS 回弹黑块
    • 黑块修复后仍残留→降级路径的 .perf-min .to-top 仍是旧半透明渐变;一并统一
    • 黑块只在回弹时出现→iOS 回弹弹簧过冲,y 短暂小于 max;加距离滞回
    • 渐变与纯色无法插值→底色改纯色后可自身过渡
  99. v1.22进度环与 Hero 渐显
    • 安卓快速甩到底环合不上→橡皮筋期间回调被跳过,量化判定落在越界帧;给环回调豁免
    • 桌面 Hero 开播闪一下黑→transition 漏列 visibility,其不可插值即瞬间生效
    • 移动端渐显过快→模糊层撤场时刻须按清晰层时长算,两边同一变量
    • 图片偶发出现两次→decode 在未加载完会提前 reject;改为先等 load
  100. v1.21回到顶部按钮的黑块
    • 下拉时圆圈内出现黑块→底色半透明,橡皮筋时露出 html 黑底;改为不透明
    • 白层比圆小一圈→inset 相对 padding box 而按钮有 1px 边框;改为 inset:-1px
  101. v1.20产物压缩
    • CSS 175KB、JS 102KB 拖慢首屏→压缩至 49.7 与 27.4KB,各减约 72%
    • 压缩可能改坏产物→构建脚本比对语义骨架,不符即拒绝写入
    • 注释被删易踩坑→保留 .src 源文件,改样式请改 .src 再构建
  102. v1.19手表端菜单动画
    • 小屏菜单闪现无滑入→未展开抽屉是 display:none,拿不到 transform 起始值;改用 visibility
    • 关闭时抽屉链接仍可 Tab 聚焦→visibility:hidden 同时不可聚焦、读屏忽略
  103. v1.18综合修复与工程化
    • 玻璃按压高光从未生效→引用作用域外变量,读取抛错致模块中止
    • 三处 passive 配置无效→括号提前闭合,配置成了被丢弃的逗号表达式
    • SW 文件 89KB 中 94.5% 是注释→重写为 6KB,说明保留、流水账移出
    • 样式表索引行号失效→脚本逐条实测校准
  104. v1.17占位图的模糊尺度
    • 首屏加载呈马赛克块状→24px 占位图放大 80 倍;提至 96px,放大降至 20 倍
    • 移动端模糊残留可见→半径 6 升至 8px,图幅更小故成本反降至 0.74 倍
    • 占位图不加 saturate 补偿→实测仅损失 0.8%,加了会让加载完反而变淡
  105. v1.16材质对齐 Apple Liquid Glass
    • 边缘反光是柔光而非玻璃→高光应明亮且狭窄;blur 1 降 0.5px、alpha 0.28 升 0.45
    • saturate 六档且组件改动不生效→移动端降级覆盖组件值;统一为 --lg-sat
    • 滚动中的条不参与厚度分级→身后内容每帧在变,模糊半径是最敏感旋钮,沿用小半径
  106. v1.15交互反馈统一
    • hover 数值散落 12 种→收敛为四个 token
    • .strip-item hover 缩放从未生效→后方同选择器 translateY 整体覆盖;删除失效声明
    • 可点元素缺按压反馈→补齐 :active,玻璃按钮加跟随手指的高光
    • iOS 上按压反馈不触发→Safari 需祖先注册 touch 事件才派发 :active;加空 passive 监听
  107. v1.14缓动曲线对齐 Apple
    • 数字滚动节奏与全站不一致→硬编码 easeOutCubic 与 --ease-out 偏差 12.6%;改用 cssEase 读变量
    • 曲线易两边失配→读变量而非抄常量
  108. v1.13触底改为状态变化
    • 触底弹跳突兀→改为「变实心白」的状态切换,归位无过冲
  109. v1.12下拉白边与 Safari 掉帧
    • 下拉露出白边→html 的 overflow-x:hidden 使其成为滚动容器,中断背景向 canvas 传播;改用 clip
    • iOS Safari 下拉掉帧→background-attachment:fixed 每帧重绘整块背景;改为固定层由合成器搬运
    • 橡皮筋时多余回调→overscroll 期间只跑需要的回调,每帧 6 个降至 1 个
  110. v1.11触底反馈改为弹簧跟手
    • 弹跳一次后再下拉无反应→边沿触发加节流加重播需强制回流;改为弹簧物理
    • 各平台表现不一致→三输入源取最大值,覆盖 iOS、Windows、Android
  111. v1.10底部回弹与触底提示
    • 底部下拉无回弹→防白边的 overscroll-behavior 全局禁用了橡皮筋;改为仅锁滚动时禁用
    • 缺少触底反馈→新增触底圆圈弹跳,节流 4s 且短页面不触发
  112. v1.09筛选计数统一为作品幅数
    • 「全部」显示栏目数,与其余口径不一→统一为作品幅数,右侧统计同步
  113. v1.08iOS 入场动画二次播放
    • 入场动画播放两次→CSS 与 JS 各做一次转场;移除跨文档转场
    • 动画重播反复以不同面貌出现→加 .anim-done 置 animation-name 为 none,结束后重播在机制上不可能
    • will-change 晚于动画开始→设 delay 时同步加 .pre,避免途中重建合成层
  114. v1.07代码组织:建立模块地图
    • 代码功能交错难定位→JS 划 14 个编号模块加文件头地图,CSS 加分区索引
    • 多份矛盾注释叠加→合并为一份,矛盾注释比无注释更危险
    • CSS 不重排规则→层叠顺序即优先级,改用索引标注行号
  115. v1.06取消动画时长分级
    • 降级时动画时长不一致→全站统一满血时长,动效分级保留
  116. v1.05动画时长:CSS 与 JS 统一
    • 降级后揭示动画时长不变→移动端 --t-reveal 写裸值 0.9s,覆盖 calc 分级
    • 降级后关灯箱要停顿才能滚动→LB_CLOSE_MS 加载时算一次即固定;改为每次读取
    • 降级后入场动画白等 900ms→JS 兜底写死 1400ms;改用 cssMs 统一读 CSS 变量
  117. v1.04性能分级 v2:可恢复
    • 一次卡顿即永久降级→原为单向降级;改为连续 10 个流畅窗口回退一级,每级限一次
    • 省电模式 30Hz 设备反复降级→FPS 下限硬编码 45;改为按实测刷新率的 75% 计算
    • JS 阻塞被误判为渲染问题→Long Task 期间窗口不计差评,降动画治不了脚本瓶颈
  118. v1.03灯箱:高清图的加载链
    • 仅一张图清晰,其余久等→基层取 currentSrc,首页小图只挑 480 档被拉伸 3.24 倍;改为只读 src
    • 切换下一张要重新下载→预加载只拉缩略图;改为同时预载高清档
    • 个别图高清层永不显示→decode 失败只绑 onload,报错后其永不触发;补 onerror
  119. v1.02菜单毛玻璃:永不关闭
    • 降级后菜单模糊消失→降级列表误含 .nav-glass;移除
    • 降级后菜单文字不可读→对比度补偿把菜单背景改近透明;将其移出补偿列表
    • 该问题可能复发→性能规则后重申菜单模糊,JS 全程不碰 backdrop-filter
  120. v1.01降级系统首次真正生效
    • 性能分级从未生效→liteOn 取值错误,判定分支永远走不到
  121. v1.00首个公开版本
    • 响应式作品集:六栏目 37 幅→三档断点,栅格随屏宽重排
    • 灯箱大图→四档图源自适应,键盘与手势均可导航
    • 渐进式图片加载→占位图模糊铺底,清晰图解码后淡入
    • 可安装、离线可用→PWA:manifest 加 SW 预缓存关键资源
    • 深色视觉语言→近黑底配液态玻璃的统一材质