跳到主要内容

动效工具Motion

库内部用的动效内核也一并导出:把弹簧参数换算成 linear() 缓动的 spring()、五个预设、WAAPI 的薄封装 animate()、布局位移用的 FLIP,以及处理进退场卸载时机的 usePresence。它们都读全局倍率 --ok-ms,开了减弱动效会自动归零。

先用 CSS

进退场、悬停、落位这类状态过渡,直接写 CSS 过渡并引用令牌。只有需要量位置、或者由事件一次性触发的动画才用脚本。

css
/* 能用 CSS 过渡解决的,优先用 CSS:时长和弹簧都是令牌 */
.thing { transition: transform var(--ok-spring-dur) var(--ok-spring), opacity var(--ok-dur-2) var(--ok-ease-out); }

animate 与弹簧预设

animate(node, keyframes, options) 就是 element.animate(),多做三件事:时长乘以 --ok-ms;options.spring 传预设名或弹簧参数时自动换成对应的时长和 linear() 缓动;默认 240ms、ease-out。

五个预设(presets)

属性类型默认值说明
softstiffness 170 · damping 26587ms无过冲。高度展开、进度;悬浮卡片进场用它的曲线(时长压到 303ms)。过冲约 0%。
springstiffness 260 · damping 24454ms默认。开关、分段、滑块落位。过冲约 3%。
snappystiffness 520 · damping 34325ms和默认一样的过冲,更快。小控件。过冲约 3%。
bouncystiffness 300 · damping 18542ms明显回弹。松手回弹。过冲约 15%。
wobblystiffness 180 · damping 91104ms大幅摆动,只用于品牌时刻(标志、点赞)。过冲约 33%。

上表“默认值”一栏是换算出的时长。CSS 里对应 --ok-spring-* 和 --ok-spring-*-dur 这组令牌。

spring():自己调一条

spring({ stiffness, damping, mass }) 返回 { duration, easing, overshoot, … }。easing 是可以直接贴进 CSS 的 linear(…)。

ts
import { spring } from 'oink-ui';

const s = spring({ stiffness: 300, damping: 18 });
s.duration;    // 542(ms)
s.overshoot;   // 0.148
s.easing;      // 'linear(0, 0.028, 0.101, …, 1)',可以直接写进 CSS

node.style.transition = `transform ${s.duration}ms ${s.easing}`;

想边调参数边看曲线和效果,用“动效”一章里的 弹簧实验台:拖动刚度、阻尼、质量,实时算出时长、过冲和 linear(),可以一键复制。

playFlip:布局变了,元素滑过去

FLIP 分两步:变更之前记下每个节点的位置;变更之后调用 playFlip(first),它量出新位置并把每个节点从旧位置弹到新位置。React 里第二步放在 useLayoutEffect 中。

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

不在 React 的渲染流程里(直接改 DOM)时可以用一步到位的 flip(nodes, mutate, options?)。

usePresence:等退场结束再卸载

条件渲染的元素一卸载就没了,来不及播退场。usePresence(open, exitMs) 让节点多活一会儿:state 用来写 [data-state] 过渡,mounted 决定是否渲染。

mounted = false,state = closed

浮在页面上的容器不需要自己用它:FloatingCard 和各个弹出组件已经处理好了进退场。

API

函数

属性类型默认值说明
animate(node, keyframes, options?) => Animation | null—WAAPI 封装。options 是 KeyframeAnimationOptions 加一个 spring(预设名或弹簧参数)。默认 duration: 240、easing: ease.out,不保留 fill;有 delay 时自动 fill: 'backwards'。
spring(options?) => SpringResult—弹簧参数(stiffness 默认 260、damping 默认 24、mass 默认 1)换算成时长和 linear() 缓动。
presetsRecord<SpringPreset, SpringResult>—五个预设的换算结果。
ease{ out, in, inOut, standard }—四条贝塞尔曲线的字符串,和 --ok-ease-* 一致。
playFlip(first: Map<Element, DOMRect>, options?) => void—FLIP 的第二步:从记录的位置弹到当前位置。位移小于 0.5px 的节点跳过。默认用 spring 预设。
flip(nodes, mutate, options?) => void—一步到位的 FLIP:记录、执行 mutate、播放。
speed() => number—读当前的 --ok-ms(减弱动效时为 0)。
wait(ms: number) => Promise<void>—按 --ok-ms 缩放过的延时,用来串联几段动画。
floatMotion{ in: 303, fade: 100, out: 160 }—悬浮卡片共用的进场位移、进场淡入、退场时长(ms,未乘 --ok-ms),和 --ok-float-in-dur / --ok-float-fade-dur / --ok-float-out-dur 一致。
supportsLinearboolean—浏览器是否支持 linear() 缓动;不支持时 animate 用预设里的贝塞尔回退。

usePresence(open, exitMs?)

属性类型默认值说明
open*boolean—是否应该显示。
exitMsnumber400退场的兜底时长:过渡没有触发 transitionend 时,到点也会卸载。
返回 mountedboolean—是否渲染节点。
返回 state'open' | 'closed'—写到 data-state 上。进场时会先以 closed 渲染一帧,过渡才有起点。
返回 refMutableRefObject<HTMLElement | null>—接到做过渡的那个节点上,用来监听它的 transitionend。

减弱动效。不需要自己判断 prefers-reduced-motion:animate() 的时长会乘以 0,动画直接到位;CSS 里用时长令牌的过渡同理。不要用 setTimeout 写死毫秒数去等动画结束,用 animation.finished 或 wait()。