Zane-Cody's Hoard
回到归档

手感:一套动效参数表和三次翻车记录

4 分钟#设计#动画#CSS

切页时的卡片起伏、跑马灯的错位、3D 场景嵌页的缩放——所有让人觉得"顺手"的东西都是参数堆出来的。这篇把参数、帧率和翻车现场都摊开。

「顺手」是个很不精确的词,但它确实是做站时最想追的东西。

这篇不聊理念,只聊参数——每一条动效到底用了多少个毫秒、什么曲线、最后为什么改成了别的值,以及三次把自己整不会了的翻车。

一、一张参数表

在哪 时长 曲线 位移
切页入场(整块) 500 ms cubic-bezier(.22,1,.36,1) 上移 16px,缩放 0.986
切页入场(卡片) 580 ms 同上 同上
卡片错峰间隔 45 ms / 张 — 第 7 张起统一 300 ms
悬停抬起 400 ms 同上 上移 3px
头像光环 6 s 一圈(悬停 1.6 s) linear 旋转
跑马灯一行 46 s / 54 s 一圈 linear 反向两行
灯箱淡入 300 ms ease-out 背景模糊 20px

两条经验:

  1. 入场曲线不能是 ease。ease 收尾太软,卡片会像被人按下去一样"陷"进画面。cubic-bezier(.22,1,.36,1) 前段猛、尾段稳,看起来才像"弹到位"。
  2. 错峰有上限。45 ms 一级的话,第 20 张卡片要等 0.9 秒才出现——用户早就往下滚了。所以第 7 张之后统一压到 300 ms。

二、跑马灯为什么复制两遍

无缝滚动的标准做法:把列表在 DOM 里复制两份,整条轨道向左平移 50%,到点瞬间归零。

只复制一份会怎样?跑到末尾会露出空白,然后"啪"一下跳回开头——那一帧的跳动能被眼睛抓得死死的。

代价是 DOM 节点翻倍。现在这排有 30 多项,复制后两百多个胶囊。能接受,因为它只动 transform,不触发重排。

另外两端要加渐隐:

mask-image: linear-gradient(to right, transparent, #000 6%, #000 94%, transparent);

没有这层遮罩,胶囊被切成半截,看起来像出了 bug。

三、翻车一:给静态场景冻结阴影,整页白屏

为了省性能,我给 3D 场景加了这一行:

renderer.shadowMap.autoUpdate = false;

想法很朴素:房子和树都不动,阴影贴图算一次就够了,之后每帧复用。

结果整个场景渲染成一片天空。

原因:autoUpdate = false 之后,阴影贴图从来没被渲染过第一帧,所有依赖阴影的材质都拿到了无效数据。正确写法得先 needsUpdate = true 再冻结。

最后的处理是直接删掉这个优化。省下来的那点开销,不值得冒"全画面空白"的风险——能跑对,比跑得快重要。

四、翻车二:z-index 打不过一个父级

照片墙的灯箱写着 fixed z-[70],按道理该压住一切。但实际效果是:竹叶在照片上面飘,首屏的下滚箭头也浮在照片上。

查了很久才想明白:灯箱外面套着一层带 transform 的容器(页面入场动画用的),而 transform 会创建新的层叠上下文——灯箱那 70 的 z-index 只在那一层内部有效,在更外层看来,它和背景没有区别。

解法不是提 z-index,而是换战场:

body:has(.photo-lightbox) .leaf-layer,
body:has(.photo-lightbox) .hero-arrow {
  display: none;
}

打不过就藏起来。 用户在看照片,本来就不需要竹叶。

五、翻车三:滚轮缩放"没用"

3D 场景嵌进页面后,用户反馈两件事:缩放没反应、转到别的角度会自己回弹。

第一件查出来是:片头那 3.6 秒的推进镜头每帧都在覆盖相机。你在这段时间里滚轮、拖拽,全部会被下一帧抹掉——不是没生效,是被立刻撤销了。

第二件更有意思:松手后有阻尼惯性。60 fps 下惯性 0.3 秒就收住,但掉帧的机器上一帧两百毫秒,同样的动量要飘二十秒。用户看到的就是"镜头自己往回跑"。

两条修法都不复杂:

// 松手即停:记机位 → 关阻尼跑一次 update(内部清零残余动量)→ 机位写回
const pos = camera.position.clone();
controls.enableDamping = false;
controls.update();
camera.position.copy(pos);
controls.enableDamping = true;

外加一排实体按钮(+ / - / 复位)——因为嵌在别人页面里时,滚轮事件可能根本不是你的。

六、帧率的账

这台机器没有独显可测,只能跑软件渲染。同一套参数下的实测:

条件 帧率
3D 场景,全画质(软件渲染) ~7–10 fps
同上,自适应降级后 ~20 fps
静态页面切页动画(仅 CSS) 满帧

所以场景里留了一套自适应画质:开头两秒量实际帧率,低于 45 就把泛光关掉、像素比降到 1;低于 26 再砍植被、阴影降到 512。

宁可少一圈光晕,也不让访客看幻灯片。

七、最后:手感是删出来的

回头看,这篇里三条翻车有两条的正确答案是"删掉"——删掉那个"聪明"的优化,删掉那个盖不住的层级。

动效也一样。第一版我给每个卡片都加了入场、悬停、聚焦三套动画,看着热闹,用起来累。最后留下的只有一类:你正在做的那件事,给你一点回应。别的都是噪音。

顺手的反面不是丑,是"多"。