auto-save 2026-04-23 23:19 (~2)
This commit is contained in:
@@ -1,16 +1,671 @@
|
|||||||
# anime.js 源码解析 源码解析
|
# anime.js 源码深度解析
|
||||||
|
|
||||||
> 创建日期:2026-04-23
|
> **上游**:https://github.com/juliangarnier/anime.git
|
||||||
> 上游版本:待填写
|
> **版本**:v4.3.6(2026-04-23 clone)
|
||||||
|
> **作者**:Julian Garnier
|
||||||
|
> **License**:MIT
|
||||||
|
> **规模**:11,121 行(不含 dist/test),17 个模块
|
||||||
|
> **定位**:轻量级多用途 JavaScript 动画引擎(CSS / SVG / DOM attrs / JS objects),v4 重写了整个内核
|
||||||
|
|
||||||
## 概览
|
---
|
||||||
|
|
||||||
待补充
|
## 0. TL;DR 一分钟看懂
|
||||||
|
|
||||||
## 核心模块
|
anime.js v4 内核是一棵**四层继承树**:
|
||||||
|
|
||||||
待补充
|
```
|
||||||
|
Clock (core/clock.js) ← 107 行,只管 time/fps/speed/deltaTime 和双向链表 children
|
||||||
|
↓
|
||||||
|
Timer (timer/timer.js) ← 535 行,加生命周期(play/pause/seek/reverse/complete/then)
|
||||||
|
↓
|
||||||
|
├── JSAnimation (animation/animation.js) ← 747 行,管 Tween 链表 + 预解析值
|
||||||
|
└── Timeline (timeline/timeline.js) ← 362 行,管 Timer/Animation children + 位置语法
|
||||||
|
|
||||||
## 关键流程
|
Engine (engine/engine.js) ← 181 行,也 extends Clock 的单例,唯一持有 rAF 的节点
|
||||||
|
```
|
||||||
|
|
||||||
待补充
|
渲染全部走 `core/render.js` 的两个函数:`render()`(单 Tickable)+ `tick()`(递归 timeline)。所有 Tween 值在创建时就被**预解析**为 number/unit/color-rgba/complex 四类,运行期只做 `lerp + clamp + round + 字符串拼接`,没有正则。
|
||||||
|
|
||||||
|
单一 `requestAnimationFrame` 驱动整个应用所有动画(engine 单例),通过 `document.visibilitychange` 自动暂停。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. 对外 API 架构
|
||||||
|
|
||||||
|
`src/index.js` 只有 18 行,全是 re-export。每个能力是独立子目录(`index.js` 多为空文件,真实代码在同名 `.js`)。外部 API 按"**工厂函数**"风格暴露:
|
||||||
|
|
||||||
|
| 工厂 | 类 | 文件 |
|
||||||
|
|---|---|---|
|
||||||
|
| `animate(targets, params)` | `JSAnimation` | animation.js:L747 |
|
||||||
|
| `createTimer(params)` | `Timer` | timer.js:L536 |
|
||||||
|
| `createTimeline(params)` | `Timeline` | timeline.js:L362 |
|
||||||
|
| `createAnimatable(targets, params)` | `Animatable` | animatable.js:L160 |
|
||||||
|
| `createDraggable($el, params)` | `Draggable` | draggable.js |
|
||||||
|
| `createScope(params)` | `Scope` | scope.js:L259 |
|
||||||
|
| `createLayout(root, params)` | `AutoLayout` | layout.js:L1607 |
|
||||||
|
| `createMotionPath(path, offset)` | 对象(3 个 FunctionValue) | motionpath.js:L80 |
|
||||||
|
| `createDrawable(sel)` | `Proxy<SVGGeometryElement>[]` | drawable.js:L111 |
|
||||||
|
| `onScroll(params)` | `ScrollObserver` | events/scroll.js |
|
||||||
|
| `stagger(val, params)` | `StaggerFunction` | utils/stagger.js:L82 |
|
||||||
|
| `waapi.animate(targets, params)` | `WAAPIAnimation` | waapi/waapi.js |
|
||||||
|
| `svg.morphTo(path2, precision)` | `FunctionValue` | morphto.js:L25 |
|
||||||
|
| `text.split($el, params)` | 三套 proxy | text/split.js |
|
||||||
|
|
||||||
|
全部支持链式:`.play() / .pause() / .reverse() / .seek() / .stretch() / .revert() / .then()`。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. 核心循环:Engine + rAF
|
||||||
|
|
||||||
|
**engine.js:L47-165** 定义 `class Engine extends Clock`,导出一个**模块级单例** `engine`(L155 IIFE,包 `/*#__PURE__*/` 让 bundler tree-shake)。关键:
|
||||||
|
|
||||||
|
- **Tick 方法选择**(L44-45):浏览器用 `requestAnimationFrame`,非浏览器 fallback `setImmediate`,以支持 Node.js 测试。
|
||||||
|
- **Tick 入口**(L168-175):闭包函数 `tickEngine` 判断 `engine._head`(链表非空)才续命 rAF;空了就把 `reqId = 0` 让循环自然终止。这是**按需暂停**策略 —— 没有正在跑的动画就不消耗帧。
|
||||||
|
- **每帧逻辑**(L62-91 `update`):
|
||||||
|
1. `this.requestTick(time)` 走 Clock 的 fps 限速(L81-94)。
|
||||||
|
2. 遍历双向链表 `_head → _next`,每个 active tickable 调 `tick(activeTickable, localTime, ...)`。
|
||||||
|
3. `localTime = (globalTime - _startTime) * _speed * engineSpeed`(L74) —— 每个 tickable 有独立 speed。
|
||||||
|
4. paused 的 child 直接 `removeChild(this, activeTickable)` 退出循环(L80-85)。
|
||||||
|
5. 最后 `additive.update()` 跑一次叠加动画的 recompose(L89)。
|
||||||
|
- **可见性联动**(L159-162):`doc.hidden` 自动 pause/resume,可通过 `pauseOnDocumentHidden` 关掉。
|
||||||
|
- **timeUnit 切换**(L126-142):支持 `'ms' | 's'`,切换时对所有 existing durations 按 `globals.timeScale=0.001` 缩放。这是 v4 新增特性,作者为此付出了整个代码库 `round(_, 12)` 抗浮点误差的代价。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. Clock 基类:时钟抽象
|
||||||
|
|
||||||
|
**clock.js:L23-107**,仅 107 行。是所有时间驱动对象的基类。
|
||||||
|
|
||||||
|
- **状态**:`_currentTime / _lastTickTime / _startTime / _lastTime / _scheduledTime`(L28-38)。区分这么多时间戳是为了在 fps 限速、speed 变更、seek 跳转时都能正确推进。
|
||||||
|
- **`requestTick(time)` L81-94**:fps 限速的核心。如果 `time < _scheduledTime` 直接返回 `tickModes.NONE`(跳帧),否则把 `_scheduledTime` 前推至少一个 `frameDuration`。算法可以跳多帧(L92 `frameDelta < frameDuration ? frameDuration : frameDelta`),避免 tab 切回后疯狂补帧。
|
||||||
|
- **链表**:`_head / _tail` 就是直接的 Tickable 链表头尾(L47-50)。helpers.js:L255-263 的 `addChild(parent, child, sortMethod?)` 支持**有序插入**(`sortMethod` 用于让 Tween 在 siblings 链里按 `_absoluteStartTime` 排序)。
|
||||||
|
|
||||||
|
Clock 本身不感知动画,纯粹就是"会走的钟"。Engine 继承它就自动有了 child 管理;Timer 继承它再加生命周期;Animation/Timeline 再继承 Timer 加业务语义。**一条继承线四级语义分层** —— 这是 v4 架构最干净的地方。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. Tween 值系统:预解析四象限
|
||||||
|
|
||||||
|
`core/values.js` 是整个库的"解析中心"。
|
||||||
|
|
||||||
|
### 4.1 tweenTypes 五分法(consts.js:L17-23)
|
||||||
|
|
||||||
|
根据**目标类型 × 属性类型**分五档:`OBJECT`(普通 JS 对象)/ `ATTRIBUTE`(SVG/非 CSS 的 DOM 属性)/ `CSS`(样式属性)/ `TRANSFORM`(transform 六件套)/ `CSS_VAR`(CSS 变量)。
|
||||||
|
|
||||||
|
决策在 `getTweenType` values.js:L94-107:
|
||||||
|
```js
|
||||||
|
!target[isDomSymbol] ? OBJECT : // 非 DOM
|
||||||
|
target[isSvgSymbol] && isValidSVGAttr ? ATTRIBUTE : // SVG attr
|
||||||
|
validTransforms.includes(prop) || shortTransforms.get(prop) ? TRANSFORM : // x/y/z/rotate/scale/...
|
||||||
|
prop.startsWith('--') ? CSS_VAR :
|
||||||
|
prop in style ? CSS :
|
||||||
|
prop in target ? OBJECT :
|
||||||
|
ATTRIBUTE
|
||||||
|
```
|
||||||
|
|
||||||
|
注意 **TRANSFORM 独立分支**(不走 CSS)—— 这样 `translateX / scaleY / rotate` 可以独立补间,再在 render.js:L268-274 合并成一条 `transform: translateX(...) scaleY(...) rotate(...)` 字符串。缓存在 DOM 节点的 Symbol 属性(`transformsSymbol`, consts.js:L52)上,避免每帧重读。
|
||||||
|
|
||||||
|
### 4.2 valueTypes 四分法(consts.js:L26-31)
|
||||||
|
|
||||||
|
- `NUMBER` —— 纯数字
|
||||||
|
- `UNIT` —— 数字+单位,如 `100px` `30%` `2turn`(unitsExecRgx 解析,consts.js:L114)
|
||||||
|
- `COLOR` —— hex/rgb/hsl(colors.js 转 RGBA 数组 `[r,g,b,a]`)
|
||||||
|
- `COMPLEX` —— 任意混合字符串如 `rgb(0,0,0) 10px 10px 20px / inset calc(100% - 10px)`
|
||||||
|
|
||||||
|
`decomposeRawValue` values.js:L170-218 就是把原始值拆成:
|
||||||
|
```
|
||||||
|
{ t: valueType, n: 主数字, u: 单位, o: 运算符(+=/-=/*=), d: 数字数组, s: 字符串片段数组 }
|
||||||
|
```
|
||||||
|
|
||||||
|
**精妙之处**:COMPLEX 类型会把字符串按数字切分成 `s[]`(字符串碎片)+ `d[]`(数字数组),运行期只需 `lerp(d_from[i], d_to[i], t)` 然后和 `s[]` 交错拼接(render.js:L214-224)—— **正则只在创建期执行一次,帧率临界路径是纯算术**。
|
||||||
|
|
||||||
|
### 4.3 Operators(+=/-=/*=)
|
||||||
|
|
||||||
|
values.js:L146-150 的 `getRelativeValue` 处理 `+=10` `*=2` 这类相对值,在 composition 阶段(animation.js:L460-473)基于 prevSibling 的 `_toNumber` 或原值计算。
|
||||||
|
|
||||||
|
### 4.4 Tween 结构(animation.js:L524-562)
|
||||||
|
|
||||||
|
每个 Tween 是个 plain 对象(非 class,省 prototype 开销),29 个字段。关键:
|
||||||
|
- `_ease`:已经 parseEase 过的函数,运行期直接 `_ease(progress)`
|
||||||
|
- `_fromNumbers / _toNumbers / _strings`:预解析数组
|
||||||
|
- `_startTime / _changeDuration / _updateDuration / _absoluteStartTime`:时间四元组(支持 composition 剪切)
|
||||||
|
- `_prev / _next`:在 Animation 内部链表
|
||||||
|
- `_prevRep / _nextRep`:在 target-property siblings 链表里(用于 replace composition)
|
||||||
|
- `_prevAdd / _nextAdd`:additive 链表(用于 blend composition)
|
||||||
|
|
||||||
|
一个 tween 同时存在于**三条链表**里。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. 渲染管线 render.js
|
||||||
|
|
||||||
|
398 行单文件。`export const render = (tickable, time, muteCallbacks, internalRender, tickMode)` 是内核中的内核。
|
||||||
|
|
||||||
|
### 5.1 时间推进(L66-104)
|
||||||
|
|
||||||
|
- L88 `~~(tickableCurrentTime / (iterationDuration + _loopDelay))` —— **位运算 NOT 取整**比 `Math.floor` 略快(作者注释 L87)。
|
||||||
|
- L97 `isReversed = _reversed ^ (_alternate && isOdd)` —— XOR 一行处理 `reversed`、`alternate`、`isOdd` 三状态组合。
|
||||||
|
- L99 `iterationTime = isReversed ? iterationDuration - iterationElapsedTime : iterationElapsedTime`
|
||||||
|
- L100 playbackEase(Timeline 级别的总体 easing)作用于**整个时间轴**:`iterationTime = iterationDuration * _ease(iterationTime / iterationDuration)`
|
||||||
|
- L101 反向方向感知:`parent.backwards` 和 `time - prevTime` 两条路径推断方向
|
||||||
|
|
||||||
|
### 5.2 Tween 循环(L156-278)
|
||||||
|
|
||||||
|
- **跳帧优化**(L166-174):只有当前时间还没走完 + 当前 tween 没被 overridden + 前/后 siblings 没覆盖 + `forcedRender` 时才写入。
|
||||||
|
- **值类型分支**(L193-224):
|
||||||
|
- NUMBER:`lerp + modifier + round`
|
||||||
|
- UNIT:NUMBER 的基础上加 `${n}${_unit}`
|
||||||
|
- COLOR:对 RGBA 四路分别 lerp 再 clamp(0,255)
|
||||||
|
- COMPLEX:遍历 `_toNumbers` lerp + 交错拼接 `_strings`
|
||||||
|
- **Transform 批写**(L242-275):
|
||||||
|
- 每个 transform tween 写入 `target[transformsSymbol][property]`(缓存属性)
|
||||||
|
- Animation 构造时标记链中**最后一个 transform tween** 的 `_renderTransforms = 1`(animation.js:L601-616)
|
||||||
|
- 只有 `_renderTransforms=1` 的 tween 被处理时才拼完整 `transform:` 字符串(L268-274)—— **一帧一写**,而不是每个 tween 写一次
|
||||||
|
- **DOM 写入分派**(L236-254):OBJECT 走属性赋值;ATTRIBUTE 走 `setAttribute`;CSS 走 `style[prop]`;CSS_VAR 走 `style.setProperty('--x', ...)`;TRANSFORM 先存 Symbol cache
|
||||||
|
|
||||||
|
### 5.3 tick 递归(L338-398)
|
||||||
|
|
||||||
|
- L341 如果是 Timeline,按 children 链表递归
|
||||||
|
- L353-372 跨 iteration 时的 skipped callback 补触发(forward 时强制 onComplete,backward 时补 onComplete)
|
||||||
|
- L376 `childTime = (tlChildrenTime - child._offset) * child._speed` —— child 有自己的 offset 和 speed
|
||||||
|
- L386-395 所有 children `completed` 才触发 Timeline `onComplete`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 6. Timer 基类:生命周期
|
||||||
|
|
||||||
|
**timer.js:L106-530**。
|
||||||
|
|
||||||
|
- **scope 注册**(L137):构造时自动注册到 `scope.current`(如有),这让 Scope.revert() 能级联清理所有 Timer。
|
||||||
|
- **offset 计算**(L162-170):Timeline child 用 parent-relative 位置;顶层 Timer 用 `(engine._lastTickTime - engine._startTime) * globals.timeScale` 作为初始偏移,保证"创建即生效"的动画时间线连续。
|
||||||
|
- **play/pause/resume/reverse/alternate**(L371-450):语义完备。`play()` 必要时 alternate 一次再 resume;`alternate()` 通过 XOR 切 `_reversed` 然后 seek 到对称时刻。
|
||||||
|
- **seek**(L410-420):关键是 `reviveTimer(this)` 先把取消的 tween 重新 compose 回 siblings 链(L86-99)。否则 seek 一个已 cancel 的动画会渲染到空。
|
||||||
|
- **stretch**(L470-482):等比缩放 duration / offset / delay —— 用于 timeline 动态"压缩/拉伸"已添加的 children。
|
||||||
|
- **Promise 集成**(L512-528):`timer.then(cb)` 返回 Promise,resolve 时机是 onComplete。L516-518 的 `this.then = null` hack 防 `async/await` 返回这个 thenable 导致的无限递归(引用 GitHub issue #26)。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 7. JSAnimation 构造器:747 行大工程
|
||||||
|
|
||||||
|
**animation.js:L210-740**,整个构造函数就是 442 行的流水线:
|
||||||
|
|
||||||
|
### 7.1 输入归一
|
||||||
|
|
||||||
|
1. `registerTargets(targets)` 把 `'.btn'` / Element / NodeList / ReactRef 等归一成数组(targets.js)。
|
||||||
|
2. 如果有 `keyframes`,generateKeyframes(L127-208)把**数组形式**或**百分比对象形式**的 keyframes 展开成 per-property 的数组。
|
||||||
|
3. `getTweenType` 分类每个属性。
|
||||||
|
4. 每个 property 的 value 归一成 `keyframes[]` —— `{to: v}` / `[from, to]` / `[v1, v2, v3]` / `{to, from, duration, ease}[]` 等各种语法最终都化简到同一结构(L305-332)。
|
||||||
|
|
||||||
|
### 7.2 composition 处理(L393-409)
|
||||||
|
|
||||||
|
若 `composition === replace`:
|
||||||
|
- 先 `siblings = getTweenSiblings(target, propName)` —— WeakMap<Target, {prop: {_head, _tail}}>(composition.js:L45-69)
|
||||||
|
- 遍历 siblings 链找插入点,**后续 siblings 直接 override**(标记 `_isOverridden=1` + `_changeDuration=minValue`)。这让**重复对同一属性的动画自动接管前一个**(类 GSAP 的 overwrite)。
|
||||||
|
|
||||||
|
### 7.3 值解析(L411-512)
|
||||||
|
|
||||||
|
- `decomposeRawValue(fromValue, fromTargetObject)` / `decomposeTweenValue(prevTween, ...)`(先用 sibling 作 from 省一次 getComputedStyle)
|
||||||
|
- **类型不匹配自动对齐**(L475-494):
|
||||||
|
- complex vs number → 把 number 拍成 complex(所有数字位都填这个 number)
|
||||||
|
- unit 不同 → `convertValueUnit` 调一次 getComputedStyle 换算(units.js)
|
||||||
|
- color vs non-color → 假色填 `[0,0,0,1]` 占位
|
||||||
|
- **长度不等的 complex**(L506-511):把短的补齐到长的长度,空位填 0。
|
||||||
|
|
||||||
|
### 7.4 Tween 创建(L524-576)
|
||||||
|
|
||||||
|
字面对象工厂,非 class,省 prototype 开销;创建后 `addChild(this, tween)` 挂到 Animation 链表,若需要 compose 则 `composeTween(tween, siblings)` 挂到 siblings 链。
|
||||||
|
|
||||||
|
### 7.5 大量 targets 的优化(L263)
|
||||||
|
|
||||||
|
```js
|
||||||
|
const tComposition = isUnd(composition) && targetsLength >= K
|
||||||
|
? compositionTypes.none : ...
|
||||||
|
```
|
||||||
|
当 targets >= 1000,默认关掉 composition(不做 overwrite 处理)。这是"**stagger 3000 个点**"这种场景的救命配置。
|
||||||
|
|
||||||
|
### 7.6 修正 iterationDelay(L624-635)
|
||||||
|
|
||||||
|
所有 tween 都扫一遍 `startTime`,把最小的 delay 从整个 Animation 提取出来当 `this._delay`,其他 tween 减掉这个值。这样 Animation.duration 等于"真实的有效动画时长",不把前导 delay 算进去。**这个 trim pass 非常重要**,否则 `iterationProgress` 会不准。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 8. Composition 三态 + Additive
|
||||||
|
|
||||||
|
**composition.js:L95-262**。三种模式(consts.js:L41-45):
|
||||||
|
|
||||||
|
- **replace (0)**:默认。新 tween 进 siblings 链,override 掉后续 siblings + 把前面 siblings 的 `_changeDuration` 剪到 overlap 点(L142-158)—— "前面的动画被截短,后面的被覆盖"。
|
||||||
|
- **none (1)**:不 compose,纯粹写入。超多 targets 或性能关键场景用。
|
||||||
|
- **blend (2)**:叠加式。多个动画同时影响一个属性(Animatable 用这个实现"推送力合成")。
|
||||||
|
|
||||||
|
### blend 的精妙(composition.js:L216-258)
|
||||||
|
|
||||||
|
叠加模式下:
|
||||||
|
1. 给 target-prop 建一个额外的 `_add` WeakMap siblings 链。
|
||||||
|
2. 第一个 tween 时创建 lookupTween(相当于"累加器")。
|
||||||
|
3. 每个新 tween 的 `_fromNumber = lookup._fromNumber - toNumber`,`_toNumber = 0` —— 本 tween 补间的其实是**相对于上次状态的 delta**。
|
||||||
|
4. engine loop 每帧结束调 `additive.update()`(engine.js:L89)—— 遍历所有 additive siblings,把 `_number` 累加到 lookup tween 的 `_toNumber`,然后 force-render lookup tween 一次(additive.js:L53-78)。
|
||||||
|
|
||||||
|
这就是**多动画叠加不互相覆盖**的实现机理。典型应用:鼠标悬停 scale up + 持续 idle wobble + 点击 shake,三者同时生效。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 9. Timeline:位置语法 + stagger add
|
||||||
|
|
||||||
|
**timeline.js:L135-356**。继承 Timer,主要加三件:
|
||||||
|
- `labels: Record<string, number>` —— 具名锚点
|
||||||
|
- `add(targetsOrTimerParams, paramsOrPosition, positionOrStagger)` —— 多态 add
|
||||||
|
- `defaults: DefaultsParams` —— 子项默认值
|
||||||
|
|
||||||
|
### 位置语法(timeline/position.js:L50-73)
|
||||||
|
|
||||||
|
`parseTimelinePosition` 支持:
|
||||||
|
- 数字 → 绝对位置
|
||||||
|
- `undefined` → tlDuration(追加到末尾)
|
||||||
|
- `'label'` → `labels[label]`
|
||||||
|
- `'<'` → 上一个 child 的**起点**(`_offset + _delay`)
|
||||||
|
- `'<<'` → 上一个 child 的**终点**(`_offset + _delay + duration`)
|
||||||
|
- `'+=500'` `'label-=200'` `'<*=2'` → 运算符 + 基准
|
||||||
|
- 基准选择:有 sibling 就 sibling 起点;有 label 就 label;否则 tlDuration
|
||||||
|
|
||||||
|
### add 的 stagger 分支(timeline.js:L186-215)
|
||||||
|
|
||||||
|
当 `a3` 是函数(stagger 返回值)时,遍历 targets,每个 target 都:
|
||||||
|
1. 新建 `staggeredChildParams`(避免 mutate 原 params)
|
||||||
|
2. **重置** `this.duration = tlDuration; this.iterationDuration = tlIterationDuration`(关键,否则每轮 stagger 起点漂移)
|
||||||
|
3. 用 stagger 函数计算这个 target 的位置,调 `addTlChild`
|
||||||
|
4. 每个 target 都创建一个独立 JSAnimation
|
||||||
|
|
||||||
|
这样 `tl.add('.item', { opacity: [0,1] }, stagger(100, { from: 'center' }))` 就为 N 个元素创建 N 个 Animation,每个有独立的 start time。
|
||||||
|
|
||||||
|
### sync(L256-265)
|
||||||
|
|
||||||
|
能同步原生 `globalThis.Animation`(WAAPI)和自己的 Tickable —— 创建一个 tweening `currentTime` from 0 to duration 的 Animation 驱动外部对象。非常机智。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 10. Stagger:复合生成器
|
||||||
|
|
||||||
|
**utils/stagger.js:L82-142**。返回一个 `(target, index, total, timeline?) => number|string` 函数。支持:
|
||||||
|
|
||||||
|
- **起始位置**:`'first' | 'last' | 'center' | 'random' | index number`(L94-97)
|
||||||
|
- **栅格模式**:`grid: [cols, rows]` + `axis: 'x' | 'y'` —— 欧几里得距离或单轴距离(L117-126)
|
||||||
|
- **range stagger**:`stagger([0, 100])` 值域等分而非固定间距(L100-103)
|
||||||
|
- **easing**:把索引归一化 [0,1] 后过 easing 再映射回去(L130)
|
||||||
|
- **单位继承**:自动从 val 提取单位 `'2s'` → `'2s'`(L139)
|
||||||
|
- **custom use**:`params.use: 'data-delay'` 从元素属性读 index 值(L108)
|
||||||
|
- **从属 timeline**:传入 `timeline` 时会把 stagger 的 base offset 结算为 timeline 位置语法(L135,`parseTimelinePosition`) —— 支持 `tl.add('.item', ..., stagger(100, { start: 'label1' }))`
|
||||||
|
|
||||||
|
values 数组是**懒初始化 + 缓存**(L112),整个 stagger 生成器只算一次距离 map。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 11. Animatable:超高频 setter
|
||||||
|
|
||||||
|
**animatable.js:L41-153**。场景:鼠标跟随、陀螺仪、3D 控制这类"**逐帧被外部驱动**"的动画。
|
||||||
|
|
||||||
|
用法:
|
||||||
|
```js
|
||||||
|
const a = createAnimatable(el, { x: 200, y: 200, ease: 'outQuad' })
|
||||||
|
document.addEventListener('mousemove', e => a.x(e.clientX).y(e.clientY))
|
||||||
|
```
|
||||||
|
|
||||||
|
实现:
|
||||||
|
- 构造时为**每个 property 创建一个独立的 autoplay:false JSAnimation**(L107)
|
||||||
|
- 每个 property 的 setter 函数(L110-139):
|
||||||
|
- 无参时 return 当前值(`_number / _numbers`)
|
||||||
|
- 有参时更新所有 children tween 的 `_fromNumber = 当前值`,`_toNumber = 新目标`(L129-130)
|
||||||
|
- `animation.reset(true).resume()` —— 从头开始补间(L136)
|
||||||
|
- 有个隐藏的 `callbacks` JSAnimation(`{v: 0, v: 1}` dummy,L90)用来统一触发 begin/pause/complete —— 因为真实动画有 N 个独立 Animation,哪一个跑完不代表全部完成。`pauseHandler` L52-65 扫所有 children paused 状态,全 paused 才 complete。
|
||||||
|
|
||||||
|
这个 API 配合 blend composition,就能实现"多输入源推同一个对象,平滑叠加"。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 12. Scope:React/Angular 生命周期桥
|
||||||
|
|
||||||
|
**scope/scope.js:L41-253**。核心概念是 `scope.execute(cb)` 压栈式**上下文**:
|
||||||
|
|
||||||
|
```js
|
||||||
|
scope.current = this
|
||||||
|
scope.root = this.root // DOM 查询 fallback root
|
||||||
|
globals.defaults = this.defaults // 这个 scope 内创建的动画继承此默认
|
||||||
|
cb(this) // 回调里创建的 animation 全注册到 this.revertibles
|
||||||
|
// 还原
|
||||||
|
```
|
||||||
|
|
||||||
|
Timer constructor 里(timer.js:L137)自动 `scope.current.register(this)`,所以在 scope.add 的 cb 里创建的动画会自动被 scope 管理。
|
||||||
|
|
||||||
|
### 关键 API
|
||||||
|
- `.add(fn)` —— 每次 refresh 时重跑
|
||||||
|
- `.add(name, fn)` —— 注册方法(调用时会用 scope context)
|
||||||
|
- `.addOnce(fn)` —— 只跑一次(cache in `constructorsOnce`)
|
||||||
|
- `.keepTime(fn)` —— 返回一个 tickable,refresh 时**保留 currentTime**不重置(L201-213,配合 utils/time.js:keepTime)
|
||||||
|
- `.refresh()` —— revert + re-run constructors(media query 变化时触发)
|
||||||
|
- `.revert()` —— 倒序 revert 所有 revertibles(L226-252)
|
||||||
|
|
||||||
|
React 集成:`root: useRef(ref)` 会被识别(L49,`ReactRef.current`)—— 在 `useEffect(() => { const scope = createScope({ root: ref }).add(...); return () => scope.revert() }, [])` 就完成了。
|
||||||
|
|
||||||
|
### mediaQueries(L85-91 / L218-223)
|
||||||
|
`{ mediaQueries: { mobile: '(max-width: 800px)' } }` + `change` event → refresh → 不同 media 下重建动画。不用手写 resize listener + 重启动画。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 13. SVG 三件套
|
||||||
|
|
||||||
|
### 13.1 morphTo(svg/morphto.js:L25-65)
|
||||||
|
|
||||||
|
变形不同数量顶点的 path。算法:
|
||||||
|
1. 两个 path 的 `getTotalLength()`
|
||||||
|
2. 目标采样点数 `maxPoints = Math.ceil(max(L1, L2) * precision)`(默认 precision=0.33)
|
||||||
|
3. 按 t ∈ [0,1] 等间距调 `getPointAtLength(L*t)` 得到新顶点
|
||||||
|
4. 返回 `[v1Str, v2Str]` 给 anime 做字符串 tween
|
||||||
|
5. 把 v2 缓存到 `$path[morphPointsSymbol]`(consts.js:L53,Symbol 属性)供下次使用
|
||||||
|
|
||||||
|
**precision 权衡**:小 = 更平滑但更贵;0 = 用原始 d 属性(命中相同 command 结构时最省)。
|
||||||
|
|
||||||
|
### 13.2 motionPath(svg/motionpath.js:L80-88)
|
||||||
|
|
||||||
|
返回三个 FunctionValue:`{ translateX, translateY, rotate }`。每个都是**闭包**绑定 path,运行期 `modifier: progress => getPathPoint + ctm 变换`。
|
||||||
|
|
||||||
|
- L61-64:rotate 算法是采样前后两点 `atan2(dy, dx) * 180/PI`(中心差分)
|
||||||
|
- L66-69:坐标变换处理 SVG vs HTML —— 如果目标在 SVG 内就用 path 局部坐标;否则乘 CTM 投到 viewport 坐标(`p.x * ctm.a + p.y * ctm.c + ctm.e`)
|
||||||
|
|
||||||
|
### 13.3 drawable(svg/drawable.js)
|
||||||
|
|
||||||
|
画线效果(模拟 Vivus.js)。精妙之处:
|
||||||
|
- 强行设置 `pathLength=1000`(L47 `K=1e3`,L96-97) —— 这样 `stroke-dasharray` / `stroke-dashoffset` 不管 path 真实长度多少,都在 [0, 1000] 规范化空间里操作。
|
||||||
|
- 用 **ES6 Proxy 拦截** `setAttribute('draw', '0 0.5')`(L58-85)—— 不是 polyfill 新属性,而是运行时代理:用户写 `drawable.setAttribute('draw', '0 0.5')`,实际写入的是 `stroke-dasharray` + `stroke-dashoffset`。
|
||||||
|
- `vector-effect: non-scaling-stroke` 的补偿(L31-35,getScaleFactor):从 CTM 解出缩放系数,补偿回去。
|
||||||
|
|
||||||
|
Proxy 让 anime 直接动画 `draw: [0, 1]` 像 CSS 属性一样用(`proxyTargetSymbol` 在 consts.js:L54,让 anime 能认出这是 proxy)。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 14. Text Split
|
||||||
|
|
||||||
|
**text/split.js**(512 行)。把一段文本拆成 line / word / char 三层元素:
|
||||||
|
|
||||||
|
- **Intl.Segmenter**(L41)可用时用 Unicode 正确分词(中日韩、emoji),否则 fallback 到空格分割。
|
||||||
|
- `setAriaHidden` 给拆出来的副本加 `aria-hidden="true"`(L81) —— **可访问性考虑**:原文保留,拆的是副本供屏幕阅读器看不到的动画视觉层。
|
||||||
|
- `filterLineElements`(L109-126)按 line 重排时,把不属于此行的 elements 以及相邻的空白 textNode 加入 bin(避免重组后出现孤零零空白)。
|
||||||
|
- 模板字符串 `{value}` / `{i}` 占位(L42-43),允许用户自定义包装模板。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 15. WAAPI 适配
|
||||||
|
|
||||||
|
**waapi/waapi.js:L85-120**,540 行总量。核心亮点是 **custom easing → CSS linear() 降级**:
|
||||||
|
|
||||||
|
```js
|
||||||
|
const easingToLinear = (fn, samples = 100) => {
|
||||||
|
const points = [];
|
||||||
|
for (let i = 0; i <= samples; i++) points.push(round(fn(i / samples), 4));
|
||||||
|
return `linear(${points.join(', ')})`;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
anime 的自定义 easing(比如 `'outElastic(1, 0.3)'`、`spring({mass, stiffness})`)在 WAAPI 侧没有对应语法。作者采样 100 个点,用 CSS Level 4 的 `linear(v0, v1, ..., v100)` 合成一条分段线性 easing —— **无需 JS 驱动就能 off-main-thread 跑任意曲线**。WAAPIEasesLookups 缓存结果(L91)避免重复采样。
|
||||||
|
|
||||||
|
这招只在支持 `linear()` 的浏览器上有效(Safari 17.2+ / Chrome 113+)。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 16. Draggable(agent 深读,1286 行)
|
||||||
|
|
||||||
|
**不继承 Timer**,而是**组合 3 个 Timer + 1 个 Animatable**(draggable.js:L384-400)。
|
||||||
|
|
||||||
|
### 状态机
|
||||||
|
- `grabbed` (L403):指针按下
|
||||||
|
- `dragged` (L404):已移动超阈值
|
||||||
|
- `released` (L406):已松开
|
||||||
|
- `updated` (L405):本帧有更新
|
||||||
|
|
||||||
|
### 事件流(`handleEvent` 分发 L1248-1278)
|
||||||
|
- `pointerdown → handleDown` L888-953:绑所有监听 → 初始化 pointer 坐标 → onGrab
|
||||||
|
- `pointermove → handleMove` L958-1020:`normalizePoint` 父元素 transform 反演 → touch 祖先可滚动检测(L972-984)→ drag threshold 门限 → 启动 updateTicker → onDrag
|
||||||
|
- `pointerup → handleUp` L1022-1151:速度采样 → 弹道预测 → spring/easing 二选一驱动回位 → onRelease
|
||||||
|
|
||||||
|
### 物理
|
||||||
|
- **速度栈 3 帧循环缓冲取最大值**(L363-365 + L489-493)—— 不是线性平均,是取 max,避免最后几帧减速导致惯性偏弱。
|
||||||
|
- **spring 模式**(L274-284):mass=1, stiffness=80, damping=20 默认。
|
||||||
|
- **过冲双阶段**(L1090-1109):非 spring 模式下若反弹超边界,先动画到物理计算的过冲点(65% 时间),再反弹到最终点 —— 让"撞墙回弹"自然。
|
||||||
|
|
||||||
|
### 约束
|
||||||
|
- **临时清空祖先 transform**(L593 `transforms.remove()`)才能用 `getBoundingClientRect` 拿到准确边界,然后再 revert。同 scroll.js 处理 sticky 一样的思路。
|
||||||
|
- **snap**(L509, L527, L702-703):number/array/function 三态。
|
||||||
|
- **containerFriction**(L700-701):默认 0.8,应用在边界外推压计算(`cf = (1 - friction) * dragSpeed`,L789)。
|
||||||
|
|
||||||
|
### 性能
|
||||||
|
- `updateTicker` 只在 dragged 期间 resume(L1010),release 后改由 Animatable 的 onRender 驱动(L426),减少无用 rAF。
|
||||||
|
- ResizeObserver 节流到 150ms `resizeTicker`(L453)。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 17. Layout FLIP(agent 深读,1607 行)
|
||||||
|
|
||||||
|
是 anime.js 里**最大的单文件**,实现的是 **F**irst / **L**ast / **I**nvert / **P**lay 动画。
|
||||||
|
|
||||||
|
### 触发
|
||||||
|
**显式三步 API**(无 MutationObserver):
|
||||||
|
```js
|
||||||
|
layout.record() // First
|
||||||
|
// 用户自己改 DOM
|
||||||
|
layout.animate(params) // Last + Invert + Play
|
||||||
|
```
|
||||||
|
或一步到位:`layout.update(cb, params)`(L1595-1599)。
|
||||||
|
|
||||||
|
### 测量
|
||||||
|
每个节点 `properties` 六维(layout.js:L318-328):
|
||||||
|
- `transform` 字符串
|
||||||
|
- `x/y` 相对父坐标
|
||||||
|
- `left/top` 绝对屏幕坐标
|
||||||
|
- `width/height` 盒模型尺寸
|
||||||
|
- 用户自定义的 `opacity/color/fontSize` 等
|
||||||
|
|
||||||
|
**测量前临时清除 transform**(L382-392 `style.transform = 'none'`)才能拿到"未变形"的 bbox。
|
||||||
|
|
||||||
|
### Invert 双引擎(最精彩处)
|
||||||
|
- **位置+尺寸** 用 anime.js Timeline(animejs 原生补间)
|
||||||
|
- **transform** 用 WAAPI **并行跑**,`timeline.sync(transformAnimation, 0)` 同步(L1566-1584)
|
||||||
|
|
||||||
|
注释写明:`transform` 如果也走 Timeline 会和 `translate` 抢 style 属性,所以必须拆开。这是一条深水炸弹级的实现细节。
|
||||||
|
|
||||||
|
### 难点处理
|
||||||
|
- **嵌套 FLIP**:`animatedParent` 链(L1296-1300)—— 子元素继承最近被动画的祖先的 timing,自动"跟随"祖先而不独立 invert。
|
||||||
|
- **display:none ↔ block**:`hasVisibilitySwap` + `measuredDisplay` 换脸(L1219-1230)。
|
||||||
|
- **内联文本**:`hasAdjacentText` → `isInlined=true` → 跳过位置动画(L368-379, L1406)—— 否则 inline span 包 translate 会全乱。
|
||||||
|
- **display:grid 干扰**:动画期间强行改 block(L1408)。
|
||||||
|
- **swapAt 中途改变**:双段 easing,后半段用 `inverseEased = t => 1 - ease(1 - t)`(L1530-1545),在 50% 时用 `tl.call()` 切换 DOM 值(L1505-1528)。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 18. Scroll / 滚动驱动(agent 深读,986 行)
|
||||||
|
|
||||||
|
**混合架构**:原生 scroll 事件 + 3 层 Timer 合批 + **主动轮询**而非 IntersectionObserver。
|
||||||
|
|
||||||
|
### 三层 Timer(events/scroll.js)
|
||||||
|
- `scrollTicker` L162-170:rAF Timer,合批 all observers
|
||||||
|
- `dataTimer` L172-189:30Hz 独立定时器算速度/方向,不抢 rAF 带宽
|
||||||
|
- `wakeTicker` L201-210:500ms 防抖,scroll 事件**不直接**触发 scrollTicker,而是 restart wakeTicker,它再启动 scrollTicker —— 滚动停止后延迟休眠
|
||||||
|
|
||||||
|
scroll 事件本身只做 `wakeTicker.restart()`(L284-285),真实工作在 rAF 帧里批量处理。
|
||||||
|
|
||||||
|
### Scrub
|
||||||
|
进度映射纯函数(L581-584):
|
||||||
|
```js
|
||||||
|
const p = (this.scroll - this.offsetStart) / this.distance
|
||||||
|
return round(clamp(p, 0, 1), 6)
|
||||||
|
```
|
||||||
|
单向:scroll → timeline.seek,**不支持反向驱动 scroll**。
|
||||||
|
|
||||||
|
### Offset 解析
|
||||||
|
字符串模板 `'end start'` / `'top 50%'` 等 —— parseBoundValue(L358-387)支持百分比、单位、相对运算符;`'top 50%'` → `offset = rect.top + viewportHeight * 0.5`。
|
||||||
|
|
||||||
|
### Sticky 处理
|
||||||
|
updateBounds 里**临时禁用祖先 sticky**(L774-781)→ 准确 getBoundingClientRect → revert(L840-841)。同一思路在 Draggable 里也用(父 transform 反演)。
|
||||||
|
|
||||||
|
### 方向感知
|
||||||
|
`enterForward / enterBackward / syncEnter` 四套回调(L873-921),根据 container.backwardX/Y 判断方向,触发对应分支。
|
||||||
|
|
||||||
|
### 共享容器
|
||||||
|
`scrollContainers = new Map()` 全局缓存(L116),多个 observer 共享一个 `ScrollContainer`,unsubscribe 到空才 revert 容器(L965-978)。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 19. 精彩设计合集
|
||||||
|
|
||||||
|
### 1. `/*#__PURE__*/` pragma 满天飞
|
||||||
|
IIFE 创建的 maps / singletons 都带 `/*#__PURE__*/`(engine.js:L44-45, consts.js:L68-74)让 Rollup / Esbuild tree-shake —— 你只用 `animate()`,整个 timeline/draggable/scroll 都能被干掉。
|
||||||
|
|
||||||
|
### 2. 双向链表全家桶
|
||||||
|
Engine children / Timeline children / Animation tweens / siblings replace / siblings add / Timer 队列全部用同一套 `_prev / _next` 链表(helpers.js:L217-263)。helpers.js 里 80 行实现的原语复用整个库。**每次 addChild 可选 sortMethod 做有序插入**(L255-263),Tween 按 `_absoluteStartTime` 排序也是用这个。
|
||||||
|
|
||||||
|
### 3. WeakMap 缓存 + Symbol 标记
|
||||||
|
- `transformsSymbol` 存 transform tween 缓存(不污染正经属性)
|
||||||
|
- `isDomSymbol / isSvgSymbol` 标记 target 类型(不每次 instanceof)
|
||||||
|
- `morphPointsSymbol` 缓存上次 morph 结果
|
||||||
|
- `proxyTargetSymbol` 让代理能被识破(drawable)
|
||||||
|
- `lookups._rep = new WeakMap()` / `_add = new Map()` —— 主动回收
|
||||||
|
|
||||||
|
### 4. decomposeRawValue 的四类 + 运行期零正则
|
||||||
|
创建期解析一次,运行期只算术。最典型是 COMPLEX:字符串 `rgb(0,0,0) 10px 10px 20px` 被切成 `s[]` + `d[]`,60fps 下只做数组 lerp + 字符串模板拼接。
|
||||||
|
|
||||||
|
### 5. render.js:L268 的 transform 字符串一帧只写一次
|
||||||
|
多个 transform tween 通过 `transformsSymbol` cache + 链尾 `_renderTransforms=1` 的 marker tween 触发最终写入。对 60 个 transform 的大 stagger,每帧只有 1 次 `style.transform = ...`。
|
||||||
|
|
||||||
|
### 6. Engine 按需 wake
|
||||||
|
rAF 不空转:tickEngine 看 `_head` 空就把 `reqId=0` 自然终止(engine.js:L168-175)。新 Animation resume 时 `engine.wake()` 才重启 rAF。**没有动画时零开销**。
|
||||||
|
|
||||||
|
### 7. visibility 自动 pause
|
||||||
|
`visibilitychange` listener 自动挂(L159-162),可关 `pauseOnDocumentHidden = false`。
|
||||||
|
|
||||||
|
### 8. alternate 的 XOR 一行
|
||||||
|
```js
|
||||||
|
const isReversed = _reversed ^ (_alternate && isOdd) // render.js:L97
|
||||||
|
```
|
||||||
|
三状态组合用 XOR 解决。
|
||||||
|
|
||||||
|
### 9. Proxy 实现的 drawable
|
||||||
|
不引入新 CSS 属性,用 Proxy 拦截 `setAttribute('draw', ...)` 转译成 `stroke-dasharray` + `stroke-dashoffset`。用户写得像在用原生属性。
|
||||||
|
|
||||||
|
### 10. WAAPI 的 `easingToLinear(100 samples)`
|
||||||
|
自定义 easing 降级到 CSS `linear(v0,...,v100)`,让 GPU 线程跑 spring / elastic 成为可能。
|
||||||
|
|
||||||
|
### 11. **1000 targets 自动关 composition**(animation.js:L263)
|
||||||
|
```js
|
||||||
|
const tComposition = isUnd(composition) && targetsLength >= K
|
||||||
|
? compositionTypes.none : ...
|
||||||
|
```
|
||||||
|
巨量元素 stagger 时自动关闭昂贵的 sibling lookup —— 用户无感的性能兜底。
|
||||||
|
|
||||||
|
### 12. Scope.keepTime
|
||||||
|
`scope.keepTime(cb)` 在 media query refresh 时保留 timeline 的 currentTime(不从 0 开始)。这个 UX 细节很少有动画库做。
|
||||||
|
|
||||||
|
### 13. **playbackEase**:Timeline 级别的 warp
|
||||||
|
render.js:L100 `iterationTime = iterationDuration * _ease(iterationTime / iterationDuration)` —— 不是单个 tween 的 easing,而是把 timeline 的**整个时间流**扭曲。playback 特效用。
|
||||||
|
|
||||||
|
### 14. additive 的反向累加
|
||||||
|
multi-input 同时影响 scale,不会互相覆盖,反而**叠加**。blend composition 把每个 tween 变成 delta,engine 每帧聚合一次(additive.js)。
|
||||||
|
|
||||||
|
### 15. stagger(customTotal) + stagger(use)
|
||||||
|
`stagger(100, { total: 10 })` 和 `stagger(100, { use: 'data-delay' })`:前者支持"N 个元素但假装是 M 个",后者支持"从元素属性读 index"(data-attribute 驱动)。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 20. 可能的坑 / 可借鉴点
|
||||||
|
|
||||||
|
### 坑
|
||||||
|
- **毫秒 vs 秒切换** (timeUnit):切到 's' 后内部全部乘 0.001,造成一堆 round(_, 12) 抗浮点 —— 不要中途切换单位,应用启动时定好。
|
||||||
|
- **seek 取消的动画需要 reviveTimer**:timer.js:L86-99 说明 cancel 后再 seek 要先重建 siblings 链,否则渲染空白。库内部已处理,但如果你自己 hack 可能踩。
|
||||||
|
- **Timer 构造器里直接读 engine._lastTickTime**(timer.js:L167):如果在动画循环外手动 `new Animation()` 然后立刻检查 currentTime 可能拿到"冷"数据 —— L166 特地 `engine.requestTick(now())` 热身。
|
||||||
|
- **Draggable 在 transformed 祖先里**:依赖 `transforms.remove()` 临时清 transform 测量(draggable.js:L593),有 CSS 变量或复杂 transform 时可能不完美。
|
||||||
|
|
||||||
|
### 可借鉴
|
||||||
|
- **linked-list children + addChild sortMethod** 这套 40 行原语,可以直接拷到任何需要排序链表的地方。
|
||||||
|
- **预解析 + 运行期无正则**:任何需要字符串补间的场景(比如 CSS gradient 动画、SVG path 补间)都适用这个模式。
|
||||||
|
- **按需 rAF**(engine.js tickEngine 模式):`_head` 空就自然终止循环,有需求时再启动。大多数自研动画库漏掉这个。
|
||||||
|
- **visibility auto-pause**:一行事件监听,避免后台 tab 疯狂吃 CPU。
|
||||||
|
- **Proxy 做虚拟属性**(drawable 的 'draw' 属性):在不能扩展原生 API 时给 API 设计者的后门。
|
||||||
|
- **WAAPI linear() 降级**:任何需要 "main-thread 动画 + off-main-thread 采样" 协同的库都能学。
|
||||||
|
- **scope 作用域 + revertibles**:给框架集成(React/Vue/Angular)用的标准 pattern,值得抄。
|
||||||
|
- **阈值自动降级**(targets >= 1000 关 composition):性能护栏,用户不用自己做决定。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 21. 模块依赖图(概要)
|
||||||
|
|
||||||
|
```
|
||||||
|
consts.js (enums + regex + Symbols) ← 所有模块
|
||||||
|
helpers.js (math/type/linked-list) ← 所有模块
|
||||||
|
globals.js (defaults/scope.current) ← timer/animation/scope
|
||||||
|
|
||||||
|
core/values.js (decompose/getTweenType) ← animation/animatable
|
||||||
|
core/render.js (render + tick) ← timer/engine
|
||||||
|
core/clock.js ← engine/timer
|
||||||
|
|
||||||
|
timer/timer.js ← animation/timeline
|
||||||
|
engine/engine.js ← timer/animatable/scroll/draggable
|
||||||
|
animation/animation.js ← timeline/animatable
|
||||||
|
animation/composition.js ← animation/timeline
|
||||||
|
animation/additive.js ← engine/composition
|
||||||
|
|
||||||
|
timeline/timeline.js ← 用户
|
||||||
|
timeline/position.js ← timeline/stagger
|
||||||
|
|
||||||
|
easings/* (parser) ← animation/timeline/waapi/stagger/draggable
|
||||||
|
utils/stagger.js ← 用户
|
||||||
|
utils/target.js (revert utilities) ← timeline/animatable
|
||||||
|
|
||||||
|
scope/scope.js ← 用户(通常 React)
|
||||||
|
animatable/animatable.js ← 用户(mousemove 场景)
|
||||||
|
draggable/draggable.js ← 用户
|
||||||
|
events/scroll.js ← 用户
|
||||||
|
layout/layout.js ← 用户(FLIP 场景)
|
||||||
|
svg/* ← 用户(svg 场景)
|
||||||
|
text/split.js ← 用户
|
||||||
|
waapi/waapi.js ← 用户(off-main-thread 场景)
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 附录:文件规模
|
||||||
|
|
||||||
|
| 模块 | 行数 |
|
||||||
|
|---|---:|
|
||||||
|
| layout/layout.js | 1607 |
|
||||||
|
| draggable/draggable.js | 1286 |
|
||||||
|
| events/scroll.js | 986 |
|
||||||
|
| animation/animation.js | 747 |
|
||||||
|
| types/index.js | 652 |
|
||||||
|
| waapi/waapi.js | 540 |
|
||||||
|
| timer/timer.js | 535 |
|
||||||
|
| text/split.js | 512 |
|
||||||
|
| core/render.js | 398 |
|
||||||
|
| animation/composition.js | 390 |
|
||||||
|
| timeline/timeline.js | 362 |
|
||||||
|
| scope/scope.js | 259 |
|
||||||
|
| core/values.js | 235 |
|
||||||
|
| engine/engine.js | 181 |
|
||||||
|
| utils/chainable.js | 171 |
|
||||||
|
| core/targets.js | 138 |
|
||||||
|
| utils/stagger.js | 142 |
|
||||||
|
| core/styles.js | 118 |
|
||||||
|
| svg/drawable.js | 118 |
|
||||||
|
| core/helpers.js | 263 |
|
||||||
|
| core/consts.js | 118 |
|
||||||
|
| core/clock.js | 107 |
|
||||||
|
| core/colors.js | 103 |
|
||||||
|
| svg/motionpath.js | 88 |
|
||||||
|
| utils/number.js | 84 |
|
||||||
|
| waapi/composition.js | 84 |
|
||||||
|
| animation/additive.js | 81 |
|
||||||
|
| core/globals.js | 74 |
|
||||||
|
| timeline/position.js | 72 |
|
||||||
|
| svg/morphto.js | 65 |
|
||||||
|
| utils/random.js | 63 |
|
||||||
|
| core/units.js | 63 |
|
||||||
|
| utils/time.js | 57 |
|
||||||
|
| core/transforms.js | 45 |
|
||||||
|
| svg/helpers.js | 24 |
|
||||||
|
| 其他 | 27 |
|
||||||
|
| **total** | **11,121** |
|
||||||
|
|||||||
@@ -27,6 +27,13 @@
|
|||||||
"message": "auto-save 2026-04-23 23:08 (~1)",
|
"message": "auto-save 2026-04-23 23:08 (~1)",
|
||||||
"hash": "8e8f977",
|
"hash": "8e8f977",
|
||||||
"files_changed": 1
|
"files_changed": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ts": "2026-04-23T23:14:02+08:00",
|
||||||
|
"type": "commit",
|
||||||
|
"message": "auto-save 2026-04-23 23:13 (~1)",
|
||||||
|
"hash": "764b395",
|
||||||
|
"files_changed": 1
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user