
图标动画一直是前端的一个尴尬地带:想给菜单按钮加个「变 X」的过渡,要么用封装重型动画库,要么手写 from/to 配对,还要处理中间态「缩了又扭」的鬼畜变形。软件格律诗 今天带来的 Morphicons 这款开源图标变形动画库解决的就是这个痛点:任意描边图标(stroke-based)都能变形为任意另一个,Lucide、Tabler、Heroicons、Iconoir 甚至你自己的路径都行。
核心思路一句话:用封闭形式解出两个形状之间的最优相似变换(2D Procrustes + 极坐标插值),然后让动画在它的自然空间里发生——如果两个图标在旋转下全等,它就旋转过去;否则就在对齐后的坐标系里变形。arrow-right 变成 arrow-down 会自动转出 θ = 90°,完全不需要任何人手声明。零运行时依赖,核心仅 6.5 KB(gzip),MIT 开源。

功能特点
三种使用模式,一套接口通吃
五个绑定(React / Vue / Svelte / React Native / Astro + Web Component)共享同一套控制模型:
1. 非受控模式(90% 的场景):只管换 icon prop,动画自动发生,不需要 from/to 配对、不需要 key、不需要任何配置
2. 受控模式(手势、滚动):显式传 from / to / progress,用进度值「冻住」变形过程,拖动进度条或跟手势走
3. 命令式模式(序列动画):通过 ref 调 morphTo()(动画过去)或 set()(无动画瞬移)
拿 React 举例,完整用法就是上面代码段那四行——状态在外面管理,动画只是组件在 prop 变化时自动拾起的实现细节。
真正可中断的弹簧动画
内置三个弹簧预设:smooth(临界阻尼无过冲)、snappy(快速带轻微过冲)、bouncy(俏皮弹跳),也可自定义 stiffness / damping。动画中途被新指令打断时,会从当前中间形状重新规划,同时保留弹簧的速度——连点鼠标不会跳变,而是感觉「活」的。
旋转是涌现的,不是声明的
这是它最核心的技术差异。传统方案要么对原始坐标插值(中间态收缩、剪切变形),要么为每个图标对手工声明「旋转分组」。它通过 2D Procrustes 计算最优相似变换:如果两个图标在旋转下全等(比如箭头转向),系统自己选择纯旋转;全局混合通道(global hybrid)还保证全等图标在飞行全程保持刚性——箭头的头部不会在中途向柄部「塌」下去 1 像素。
图标库兼容:数据契约,而非库适配器
它消费的是图标数据(Lucide 的 IconNode 结构或原始 d 字符串),不是组件。因此没有 per-library 适配器,满足三个条件的图标库都能开箱即用:描边绘制、几何可用作数据、共享坐标空间。Lucide、Tabler、Heroicons、Iconoir 都画在 24×24 网格上,跨库变形直接成立;其他网格的图标库(Teenyicons 15×15、Carbon 32×32、Heroicons solid 20×20)用 fitIcon 一次性重投影到 24 网格即可。shadcn registry 的 200+ 图标库也兼容——直接把内联 <path d> 传进去就行。
三大格式适配器,突破 SVG 边界
svgToIcon:把 SVG 标记(Iconify body、网页复制的<svg>、shadcn 的<path>)一键转成可消费的图标输入,自动剥离<defs>/<mask>等非渲染容器,对无法诚实变形的图形(无描边的填充图标、带 transform 的元素)在解析期就报清晰错误maskTarget:针对以 CSS mask 渲染图标的场景(UnoCSS presetIcons、Iconify Tailwind 等)——DOM 里没有<path>可写,它在幕后创建一对隐藏的<svg><mask><path>缓冲来回切换,让图标原地变形,保留你的 `size-/text-` 类canvasTarget:图标从此不再只是 DOM 节点,而是你拥有的像素——支持 Canvas/OffscreenCanvas/裸 2D 上下文,可跑在 Worker 主线程外,还能toBlob()导出 PNG 精灵、captureStream()录成视频、甚至toDataURL()做动态 favicon,或直接当 WebGL 纹理
无障碍与可访问性,默认就做对
默认 aria-hidden,传入 label 则自动切换为 role="img" + <title>;SSR 干净——服务端输出与静态图标完全一致的 SVG,零闪烁零布局偏移,hydration 时才注入运行时。
减动效策略:显式声明
图标变形是短小、传达性的微动效,按业界指南属于可接受的动效类型。自 1.4.2 起默认跟随动画(忽略系统减动效设置),需要时显式传 reducedMotion="user" 让 OS 设置生效,"always" 则完全禁用。
性能与体积,精打细算
- plan(两图标配对计算)亚毫秒级:实测 0.01 ms(开放路径)到 0.06 ms(闭合路径带循环搜索),极端 12 环病例 0.42 ms
- 每帧零数值分配:插值写入预分配的 Float 64 Array,每帧只分配一个
d字符串 - 按引用缓存:normalize 与 plan 都用 WeakMap 缓存,GC 友好
- 全局单一 rAF:一百个图标同时变形也只有一个循环
- 核心 6.60 KB gzip,CI 有尺寸门槛防回归
纯核心,不碰 DOM
核心是纯函数:解析 → 归一化(全部转三次贝塞尔)→ 弧长重采样(N=64,带角点锚定,静止时形状精确)→ 对应匹配 → Procrustes 对齐 → 规划 → 极坐标插值 → 序列化。这也正是 React Native 绑定只是「另一个适配器」而非重写的原因——它通过 setNativeProps 垫片原样复用 DOM 驱动。
上手教程
第一步:安装
仅 ESM。各框架绑定是可选 peer:React ≥ 18、Vue ≥ 3.3、Svelte ≥ 5、React Native ≥ 0.71(+ react-native-svg ≥ 14)。morphicons/element(Web Component)和 morphicons/astro 零 peer 依赖。
第二步:最基础的用法
关键点:图标来自数据包(vanilla lucide),不是框架组件包(lucide-react 导出的是组件,MorphIcon 消费不了)。如果你已经在用 lucide-react 渲染静态图标,保留它即可——数据包与组件包设计上共存,且都支持 tree-shaking。
第三步:进阶三连
- 手势驱动:
<MorphIcon from={Menu} to={X} progress={dragProgress} /> - 序列动画:
ref.current?.morphTo(Check)/ref.current?.set(X) - 零框架场景:裸 HTML 里
defineMorphIcon()后直接用<morph-icon icon="M4 6h16M4 12h16M4 18h16" label="Menu">
第四步:其他框架
Vue 绑定是纯 render 函数(无需 SFC 编译器),Nuxt 开箱即用;Svelte 5(runes)以 .svelte 源码分发,SvelteKit 开箱即用;React Native 复用 DOM 驱动;Astro 服务端输出精确静态 SVG,hydration 只是自定义元素升级,不附带任何框架运行时。
第五步:动手玩
官方 playground 内置 38 个真实图标(Lucide、Feather、Tabler),逐对显示检测到的相似度(θ、σ、residual),可对比 polar / linear 插值模式、切换弹簧预设和进度滑块。
我的使用感受
上手的第一感觉是「过分丝滑」。我把汉堡菜单换成 X 的动画做进项目,原本预期要调半天 from/to 和旋转分组,结果那四行代码跑起来,中间态居然自己转了个漂亮的 90°——连点几下的中断表现也完全不像常见库那样跳变,真的像有物理质量。最让我佩服的是体积克制:整个核心 6.5 KB,连动画库都不用带,性能预算焦虑直接消失。要说门槛,它的文档属于「强技术向」——匹配算法、Procrustes、极坐标插值写得极深,普通业务前端只需消费 React 绑定的三行用法,但想用透 maskTarget 这类适配器确实需要读文档;另外它只适合描边图标,Material Symbols 这类填充图标在中间态会「读不懂」,选型时要注意。总体而言,如果你做的是工具类产品、Dashboard 或任何讲究图标交互细节的界面,这是目前最优雅的描边图标变形方案。
本文信息源自:格物致知 · 科技发现周刊 007期:MacBook的触控板(15个项目)
官方网站
https://github.com/guillermolg00/morphicons







