Svelte svelte/easing 模块详解:31 个缓动函数的实现原理与在 motion、transition、animate 中的实战用法
本文以 Svelte 官方参考文档 svelte/easing 模块说明为骨架,结合仓库中 缓动函数源码、motion 模块实现 与 类型声明 展开:读完你将掌握 svelte/easing 提供的全部 31 个缓动函数的分类体系、统一的 (t: number) => number 契约、每个函数的核心数学实现,以及它在 tweened/Tween、内置 transition 和 flip animate 中的真实调用链路。
模块定位:一套操纵时间值的函数库
官方参考文档对 svelte/easing 的定义是:该模块提供一组函数,允许你以不同方式操纵时间值(manipulate time values),与 motion 模块组合使用时对动画尤其有用。
缓动函数的本质是"时间的重映射":给定动画进度 t ∈ [0, 1],返回一个同样位于 [0, 1] 附近的进度值,从而改变动画的速度曲线——起点/终点减速、回弹、抖动、指数加速等都是通过对同一时间变量的不同变换实现的。
该模块的发布入口定义在 svelte 包的 package.json 的 exports 字段中:
"./easing": {
"types": "./types/index.d.ts",
"default": "./src/easing/index.js"
}
即:运行时实现位于 packages/svelte/src/easing/index.js,TypeScript 声明位于 packages/svelte/types/index.d.ts 中的 declare module 'svelte/easing' 块。源码文件头部注明其改编自 mattdesl 的 eases 项目(MIT 协议)。
函数全集:10 个函数族 × 3 种变体 + linear
从源码逐行清点,svelte/easing 共导出 31 个具名函数,除 linear 外按"运动学族"组织为 10 个函数族,每个函数族都有 In / Out / InOut 三种变体:
| 函数族 | 运动学描述(源码注释) | In | Out | InOut |
|---|---|---|---|---|
| linear | 原样返回,匀速 | — | — | linear |
| back | 起点/终点有回弹(overshoot/rebound) | backIn |
backOut |
backInOut |
| bounce | 弹球式反弹 | bounceIn |
bounceOut |
bounceInOut |
| circ | 圆弧形速度曲线 | circIn |
circOut |
circInOut |
| cubic | 三次方缩放 | cubicIn |
cubicOut |
cubicInOut |
| elastic | 弹性振荡 | elasticIn |
elasticOut |
elasticInOut |
| expo | 指数级加减速 | expoIn |
expoOut |
expoInOut |
| quad | 二次方缩放 | quadIn |
quadOut |
quadInOut |
| quart | 四次方缩放 | quartIn |
quartOut |
quartInOut |
| quint | 五次方缩放 | quintIn |
quintOut |
quintInOut |
| sine | 正弦式加减速 | sineIn |
sineOut |
sineInOut |
所有函数签名一致,与 types/index.d.ts 中的声明对应:
export function cubicOut(t: number): number;
// 统一契约:输入 t ∈ [0, 1],输出映射后的进度值(边界满足 f(0)=0、f(1)=1)
命名约定与行为方向的对应关系(可对照源码注释逐一验证):
*In:开头阶段"加速"(accelerate on start),先慢后快;*Out:结尾阶段"减速"(decelerate towards end),先快后慢;*InOut:两头分别应用 In 与 Out 的拼接,通常是先加速后减速的平滑曲线;back*/bounce*/elastic*:输出会超出 [0, 1] 区间(回弹/过冲),这是与其他族的关键区别,视觉上表现为"过头再拉回"。
源码级解析:代表性实现的数学细节
以下全部取自 packages/svelte/src/easing/index.js,用于说明各族的实现思路。
linear:恒等函数
export function linear(t) {
return t;
}
它是多处动画 API 的默认缓动函数(见下文 tweened 的实现)。
cubic 族:最简单的幂函数缩放
export function cubicIn(t) { return t * t * t; }
export function cubicOut(t) {
const f = t - 1.0;
return f * f * f + 1.0;
}
export function cubicInOut(t) {
return t < 0.5 ? 4.0 * t * t * t : 0.5 * Math.pow(2.0 * t - 2.0, 3.0) + 1.0;
}
InOut 变体的通用手法是:前半段取 In 公式并乘以 2 倍速,后半段取 Out 公式(先平移 t 到 [0,1] 再缩放),保证曲线在中点 C1 连续。
sine 族:三角函数 + 浮点边界修正
export function sineIn(t) {
const v = Math.cos(t * Math.PI * 0.5);
if (Math.abs(v) < 1e-14) return 1; // t=1 时 cos(π/2) 存在浮点误差,强制收敛到 1
else return 1 - v;
}
export function sineOut(t) { return Math.sin((t * Math.PI) / 2); }
export function sineInOut(t) { return -0.5 * (Math.cos(Math.PI * t) - 1); }
注意 sineIn 中对 Math.abs(v) < 1e-14 的特判:t = 1 时理论上应精确返回 1,但浮点计算会引入微小偏差,源码显式将其钳制,避免动画末帧出现可见的"跳一下"。
expo 族:端点短路
export function expoIn(t) { return t === 0.0 ? t : Math.pow(2.0, 10.0 * (t - 1.0)); }
export function expoOut(t) { return t === 1.0 ? t : 1.0 - Math.pow(2.0, -10.0 * t); }
export function expoInOut(t) {
return t === 0.0 || t === 1.0
? t
: t < 0.5
? +0.5 * Math.pow(2.0, 20.0 * t - 10.0)
: -0.5 * Math.pow(2.0, 10.0 - t * 20.0) + 1.0;
}
指数族在端点用严格相等短路,既保证边界精确性,也避免 Math.pow 在极小值域的数值噪声。
bounceOut:分段多项式拼出"弹跳"
export function bounceOut(t) {
const a = 4.0 / 11.0;
const b = 8.0 / 11.0;
const c = 9.0 / 10.0;
const ca = 4356.0 / 361.0;
const cb = 35442.0 / 1805.0;
const cc = 16061.0 / 1805.0;
const t2 = t * t;
return t < a
? 7.5625 * t2
: t < b
? 9.075 * t2 - 9.9 * t + 3.4
: t < c
? ca * t2 - cb * t + cc
: 10.8 * t * t - 20.52 * t + 10.72;
}
bounceOut 用四段抛物线在 t = 4/11、8/11、9/10 处拼接,模拟物体落地反弹的三次衰减;bounceIn 则是其镜像:1.0 - bounceOut(1.0 - t),bounceInOut 同样以 0.5 中点做对称缩放。back 与 elastic 族同理——backIn/backOut 使用常数 s = 1.70158 控制过冲幅度,elastic* 用 Math.sin 与 Math.pow(2, ±10t) 的乘积构造衰减振荡。
实战一:与 svelte/motion 组合——tweened 与 Tween
文档明确指出该模块"与 motion 模块组合使用尤其实用"。在 packages/svelte/src/motion/tweened.js 中可以看到完整的消费链路。
默认值与覆盖优先级
import { linear } from '../easing/index.js';
let {
delay = 0,
duration = 400,
easing = linear, // 默认匀速
interpolate = get_interpolator
} = { ...defaults, ...opts }; // 构造参数为默认,set() 参数可逐项覆盖
三个关键事实(均可在源码中确认):
easing的默认值是linear,不是某个"更好看"的曲线;想要平滑减速必须显式传入;duration支持数字或(from, to) => number函数(按起止值动态计算时长);duration === 0时直接跳变,不启动动画任务;- 覆盖优先级为
defaults(创建 store 时传入)<opts(每次set时传入)。
缓动函数在每帧 tick 中的位置
task = loop((now) => {
// ...
const elapsed = now - start;
if (elapsed > duration) {
store.set((value = new_value));
return false;
}
store.set((value = fn(easing(elapsed / duration)))); // 缓动在这里生效
return true;
});
每帧先算出线性进度 elapsed / duration,再交给 easing 重新映射,最后由插值器 fn 计算当前值写入 store——这正是文档所说"操纵时间值"的落地方式。新的 Tween 类(Svelte 5.8+)在 tweened.js 中以同样的参数结构与调用顺序实现,easing(elapsed / duration) 位于其 set() 的 raf 循环中。
完整示例:用 cubicOut 让数值平滑减速
<script>
import { tweened } from 'svelte/motion';
import { cubicOut } from 'svelte/easing';
const count = tweened(0, { easing: cubicOut, duration: 600 });
</script>
<button onclick={() => count.set(Math.random() * 100)}>随机目标值</button>
<p>当前值:{$count.toFixed(2)}</p>
由于 tweened 的插值器 get_interpolator 支持数字、数组、对象(含 Date)的递归插值,缓动函数同样适用于这些复合类型的补间。
实战二:svelte/transition 内置过渡的默认缓动
easing 也是所有 transition 配置项之一(见 packages/svelte/src/transition/public.d.ts,每个 *Params 接口都有 easing?: EasingFunction)。内置过渡实现 各自选定了不同的默认缓动函数,这解释了不同内置动画"手感"的差异:
| 过渡函数 | 默认 easing |
|---|---|
blur |
cubic_in_out |
fade |
linear |
fly |
cubic_out |
slide |
cubic_out |
scale |
cubic_out |
draw |
cubic_in_out |
flip(animate) |
cubicOut |
自定义覆盖写法:
<script>
import { fly } from 'svelte/transition';
import { backOut } from 'svelte/easing';
let visible = $state(true);
</script>
{#if visible}
<p transition:fly={{ y: 200, easing: backOut }}>带回弹地飞入</p>
{/if}
实战三:svelte/animate 与自定义 animate 函数
packages/svelte/src/animate/index.js 中的 flip 默认 easing = cubicOut、duration = (d) => Math.sqrt(d) * 120(时长按位移距离开方缩放)。animate: 指令允许传入自定义 animate 函数,返回的动画配置同样接受 easing 字段——仓库测试用例 animation-js-easing 演示了这一点:
<script>
export let things;
export function linear(t) {
return t;
}
function flip(node, animation, params) {
const dx = animation.from.left - animation.to.left;
const dy = animation.from.top - animation.to.top;
return {
duration: 100,
easing: linear, // 自定义 animate 函数中直接指定缓动
tick: (t, u) => {
node.dx = u * dx;
node.dy = u * dy;
}
};
}
</script>
{#each things as thing (thing.id)}
<div animate:flip>{thing.name}</div>
{/each}
选型与使用建议
基于上述源码事实,给出可验证的选型参考:
- 数值补间(tweened/Tween):想要"先快后慢"的自然感,选
cubicOut/quadOut一类 Out 变体;追求极简匀速保留默认linear; - 元素位移/缩放(transition:fly/slide/scale、animate:flip):仓库默认值即
cubic_out系,一般无需改动; - 需要戏剧性过冲:
backOut(小幅度回弹)、elasticOut(多次振荡)、bounceOut(落球反弹)——注意这三者输出会越过 1,在opacity这类有物理上限的属性上可能被运行时钳制,用于transform位移最为直观; - 写自定义曲线:只需实现
(t) => number并保证f(0) ≈ 0、f(1) ≈ 1,即可直接传给easing选项(如测试用例中的局部linear函数)。
小结
svelte/easing 是 Svelte 动画体系的"底层积木":31 个缓动函数以统一的 (t: number) => number 契约组织成 10 个运动学族;motion 的 tweened/Tween 以 easing(elapsed / duration) 的方式在每帧消费它,transition 与 animate 内置实现 则预置了 linear、cubic_out、cubic_in_out 等默认值。理解"缓动 = 时间重映射"这一核心模型后,你可以在 Svelte 的任意动画入口——补间 store、过渡函数、自定义 animate 函数——按需替换速度曲线,并获得源码可追溯的行为保证。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0625
Hy4-previewHy4 preview 是由腾讯混元团队研发的新一代混合专家(MoE)旗舰模型。模型总参数量 770B,每个 token 激活 49B,主干共包含78层,第一层采用标准 FFN,其余 77 层均为 MoE 结构,每层包含 256 个路由专家与 1 个共享专家,每个 token 激活 top-8 路由专家及共享专家。主干之外原生内置 1 层 MTP(总参数量 10B,激活 0.7B)以支持投机解码。Python00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
GLM-5.3-FlashGLM-5.3-Flash (320B-A18B),是GLM-5系列的首个原生多模态模型。320B总参数,能力超过GLM-5.2Jinja00
Spark-X2.5-4BSpark-X2.5-4B 旨在让强大的 AI 更实用、更高效、更易获得。在广泛日常任务中表现强劲,涵盖对话、写作、翻译、推理、编码、工具调用以及智能体工作流,并在同等规模的开源模型中取得领先成绩。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00