morphicons三种使用模式全解析:非受控、受控、命令式——图标变形动画生命周期契约一次讲透
【免费下载链接】morphiconsAny icon morphs into any other — universal morphing for stroke-based icons with spring physics. Zero dependencies, ~7 KB gzip.项目地址: https://gitcode.com/gh_mirrors/mo/morphicons
morphicons 是一个零依赖(约 7 KB gzip)的 SVG 图标变形动画库,支持 React、Vue、Svelte、React Native 和 Web Component 五种绑定,任意线性图标都能带弹簧物理平滑变形为另一个图标。本文面向新手,用最少的心智负担讲透它的三种使用模式:非受控、受控、命令式,以及贯穿所有绑定的生命周期契约——即三种模式如何共存、优先级是什么、切换时动画怎么接。
30 秒看懂:三种模式速览
morphicons 的设计哲学是:状态在你手里,动画是实现细节。三种模式对应三类场景:
| 模式 | 核心属性 / 方法 | 典型场景 | 有无弹簧动画 |
|---|---|---|---|
| ⭐ 非受控 | icon属性 | 菜单 ↔ 关闭按钮切换(覆盖 90% 用例) | ✅ 有 |
| 🎚️ 受控 | from+to+progress | 手势拖拽、滚动进度联动 | ❌ 冻结定格,由你驱动 |
| ⚡ 命令式 | morphTo()/set() | 步骤序列、一次性临时切换 | morphTo有,set无 |
一个 3 行的非受控用法就能跑起来(图标数据来自lucide等数据包的IconNode,不是组件):
import { MorphIcon } from "morphicons/react"; import { Menu, X } from "lucide"; // data, not components <MorphIcon icon={open ? X : Menu} spring="snappy" />状态在外部(open变了),morphicons 检测到属性变化后自动播放弹簧变形,不需要任何 key、from/to 对或动画包装器。
模式一:非受控模式——改属性,动画自动播放
这是默认模式,也是新手入口:只要icon属性换了值,就自动从当前形状飞过去。
它的三个特点:
- 弹簧可选:
spring="snappy"(快、轻微过冲)、"smooth"(临界阻尼无过冲)、"bouncy"(俏皮),或传{ stiffness, damping }自定义。 - 随时可打断:变形到一半再点一次按钮,动画会从当前中间形状重新规划,并保留弹簧速度——连点不会跳变,手感始终连续。
- 属性穿透:
size、color、strokeWidth、className等与 lucide-react 同构,可当静态图标的直接替代品。
适合所有"状态驱动"的场景:开关按钮、加载 → 完成、播放 → 暂停。
模式二:受控模式——把手柄交给手势和滚动
当变形的进度应该由外部连续量决定(拖拽距离、滚动位置、时间轴)时,弹簧反而碍事。这时切换到受控模式:
<MorphIcon from={Menu} to={X} progress={dragProgress} />from/to定义一对端点,progress(0 到 1)把图标冻结在这对之间的任意中间形态——每帧你给多少进度,它就在哪,没有弹簧、没有自己的时钟。
两个行为值得记住:
- 进度正好 0 或 1 时,输出的是端点图标的标准形状(真实曲线,不是折线近似);
- 中途改变
progress是增量 seek,不会每次从from重新走。
模式三:命令式模式——ref 拿出手柄,序列随心
有时目标图标无法用状态表达(比如"先变 A,飞完再变 B,最后停在 C")。这时通过 ref / template ref /bind:this拿到MorphHandle:
const ref = useRef<MorphHandle>(null); <MorphIcon ref={ref} icon={Menu} /> ref.current?.morphTo(Check); // 动画飞过去 ref.current?.set(X); // 直接跳变,不播动画morphTo与set的区别就一句话:前者播放弹簧动画,后者瞬间切换(常用于重置、测试、截图)。Web Component 版本里元素本身就是句柄,el.morphTo(x)直接可用,见 src/element/index.ts。
生命周期契约:三种模式的优先级与切换规则
三个模式混用不是错误——morphicons 在五种绑定中共享同一份契约(源码在 src/dom/controller.ts),规则有且只有三条:
1. 惰性驱动:第一个图标出现时才启动引擎
不带任何图标挂载组件完全合法(服务端输出<path d="">)。真正的动画驱动器在第一个图标出现时才诞生——无论它是晚到的icon属性(比如异步加载的数据)、晚到的from/to对,还是命令式的set/morphTo。首个图标直接渲染、不播动画;而驱动器还没诞生时调用morphTo,行为等同于set(没有"起飞点",也就没有可飞的动画)。
2. 受控优先:成对存在时 icon 被忽略
只要from和to同时存在,这对端点就独占路径:此时改icon会被忽略,弹簧不会触发。把这对撤掉,路径立刻交还icon(并带动画接管)。混用不报错,优先级是显式约定的,这正是"受控模式给手势让路"的语义基础。
3. 干净重入:离开受控模式会作废旧的冻结对
任何一次退出受控模式(命令式调用,或icon接管)都会使冻结的from/to对失效。之后再传回同样的from/to,会基于from重新起算,渲染结果与全新挂载在该progress时完全一致——不会出现"记得上次停在中间"的鬼影。
这三条规则被镜像测试逐条钉死:React 版在 test/react/mount.test.tsx,Vue 版在 test/vue/mount.test.ts,Svelte 版在 test/svelte/mount.test.ts——三份测试覆盖同一组场景,保证五种绑定行为一致。
同一套契约,五种绑定零差异
理解契约后,换框架只是语法切换,语义完全相同:
- React:
useRef拿句柄,逻辑在 src/react/index.tsx - Vue:template ref 拿句柄(
expose出morphTo/set),逻辑在 src/vue/index.ts - Svelte:
bind:this拿句柄,逻辑集中在 src/svelte/shared.ts - React Native:与 React 同构,额外解决 New Architecture 下 Fabric 提交覆盖的问题
- Web Component / Astro:
<morph-icon>元素即句柄,SSR 输出精确静态 SVG、水合零闪烁,见 src/astro/MorphIcon.astro
SSR 是所有绑定的一致承诺:服务端用纯核心(不碰 DOM)算出初始d字符串,客户端水合时字节完全一致,零闪烁、零布局偏移。图标数据契约(IconNode或原始d字符串)定义在 src/core/types.ts。
新手常见问题 FAQ
三种模式怎么选?状态切换选非受控;手势/滚动连续量驱动选受控;无法用状态表达的序列或临时切换选命令式。90% 的场景只碰非受控。
受控模式下改 icon 没反应是 bug 吗?不是,这是契约第 2 条:成对存在时受控优先。撤掉from/to即可恢复icon驱动。
动画太夸张或太慢?换弹簧预设:spring="smooth"无过冲、"snappy"快、"bouncy"俏皮。
需要尊重系统"减弱动态效果"设置吗?默认不降级(图标微动效被认为普遍可接受);全站需要时用reducedMotion="user",此时 OS 开关打开后所有morphTo退化为瞬间切换。
从哪里读源码:一条最短路径
- 生命周期契约的权威实现(框架无关):src/dom/controller.ts
- 动画驱动器与弹簧:src/dom/index.ts、src/core/spring.ts
- 纯几何核心(解析 → 重采样 → 对齐 → 插值):src/core/ 目录
- 三种模式在各框架的落地:src/react/index.tsx、src/vue/index.ts
- 镜像测试套件(契约的"验收标准"):test/react/mount.test.tsx
- 架构决策背景:docs/adr/0001-frozen-core-contracts-adapters-entry.md
- 完整使用说明与性能数据:README.md
结语
一句话带走本文:非受控管状态、受控管手势、命令式管序列;惰性启动、受控优先、干净重入。把这三条生命周期契约记牢,五种绑定下的 morphicons 对你来说就是同一个组件——而它的体积只有约 7 KB。
【免费下载链接】morphiconsAny icon morphs into any other — universal morphing for stroke-based icons with spring physics. Zero dependencies, ~7 KB gzip.项目地址: https://gitcode.com/gh_mirrors/mo/morphicons
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考