一个组件搞定6大框架:morphicons在React、Vue、Svelte、React Native、Astro与Web Component中的统一玩法
【免费下载链接】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 是一个零运行时依赖、gzip 后仅约 7 KB 的图标变形(icon morphing)动画库:任意图标都能带弹簧物理地平滑变形为任意其他图标。本文带你用同一套心智模型,一次性掌握它在 React、Vue、Svelte、React Native、Astro 与 Web Component 六大框架中的统一玩法。
为什么图标变形动画很难做对?🤔
传统的图标 morph 动画要么直接插值坐标——形状在飞行途中会缩小、剪切;要么要求你为每一对图标手写"旋转分组"。
morphicons 的做法是让数学自己说话:
- 用2D Procrustes闭式解算出两个形状之间的最优相似变换(旋转 θ、缩放 σ、平移)
- 在极坐标空间做插值:如果两个图标只是旋转关系(比如 arrow-right → arrow-down),它会自动执行纯旋转 90°,全程不变形
- 配合阻尼弹簧(smooth / snappy / bouncy 三档预设),飞行中的图标随时可被打断,连点也不会跳变
旋转分组从来不需要你手写——它是数学的涌现结果。核心公式与流程细节可以阅读 src/core/ 下的源码,架构决策记录见 docs/adr/0001-frozen-core-contracts-adapters-entry.md。
安装步骤:一条命令,按需加载
npm install morphicons lucide💡 关键认知:图标数据来自
lucide数据包(data, not components)。import { Menu, X } from "lucide"拿到的是IconNode数据,而不是lucide-react里的组件。如果你项目里已经在用 lucide-react 渲染静态图标,可以保留它——数据包与组件包按设计共存,且都能 tree-shake,只需保持版本一致。
morphicons 通过子路径导出支持六大框架,只导入你用的那一份即可(sideEffects: false),入口定义见 package.json:
| 入口 | 适用场景 | gzip 体积 |
|---|---|---|
morphicons/react | React 18+ | 8.01 KB |
morphicons/vue | Vue 3.3+ / Nuxt | 8.04 KB |
morphicons/svelte | Svelte 5 | 7.66 KB + 1.26 KB 组件源码 |
morphicons/react-native | RN 0.71+ + react-native-svg | 8.37 KB |
morphicons/astro | Astro(SSR 外壳,零框架运行时) | 1.58 KB 源码 |
morphicons/element | <morph-icon>自定义元素,任何 HTML | 8.86 KB |
统一心智模型:3 种控制模式,6 个框架通用 🧠
这是全文最值钱的表格——学完任意一个框架,其余五个都会写:
| 模式 | 用法 | 适用场景 |
|---|---|---|
| 非受控(90% 场景) | 传icon,prop 一变就自动播放弹簧动画 | 菜单 ↔ 关闭、加载态切换 |
| 受控 | 传from+to+progress(0..1),无弹簧 | 拖拽手势、滚动驱动 |
| 命令式 | 通过 ref / 句柄调用morphTo()(动画)或set()(跳变) | 序列动画、编程式控制 |
六大绑定共享同一套属性面:size、strokeWidth、color、spring、label(无障碍)、reducedMotion(动效偏好),全部收敛在一个共享控制器里,见 src/dom/controller.ts。
React 图标变形组件:三行代码搞定菜单按钮
import { MorphIcon } from "morphicons/react"; import { Menu, X } from "lucide"; // 数据,不是组件 <button onClick={() => setOpen(o => !o)}> <MorphIcon icon={open ? X : Menu} spring="snappy" /> </button>就是全部。没有 wrapper、没有 AnimatePresence、没有 key 对、没有 from/to 配置——状态在外层,动画只是 prop 变化时组件自动拾取到的实现细节。
亮点:
- SSR 干净:服务端输出精确的静态 SVG(零闪烁、零布局偏移),运行时在水合后才诞生
- 无障碍默认正确:不传
label时自动aria-hidden,传了则role="img"+<title> - lucide-react 平替:
size、strokeWidth、color、className及所有<svg>属性直接透传
实现见 src/react/index.tsx,挂载行为由镜像测试套件锁定,见 test/react/mount.test.tsx。
Vue 图标变形动画:Nuxt 开箱即用
<script setup lang="ts"> import { ref } from "vue"; import { MorphIcon } from "morphicons/vue"; import { Menu, X } from "lucide"; const open = ref(false); </script> <template> <MorphIcon :icon="open ? X : Menu" spring="snappy" /> </template>与 React 绑定完全相同的三种模式、相同的无障碍默认值、同样的 SSR 干净特性——Nuxt 直接可用。绑定是纯 render function,不涉及 SFC 编译器或 JSX,构建零额外依赖。源码见 src/vue/index.ts。
Svelte 5 runes 时代的极简集成
<script lang="ts"> import { MorphIcon } from "morphicons/svelte"; import { Menu, X } from "lucide"; let open = $state(false); </script> <MorphIcon icon={open ? X : Menu} spring="snappy" />组件以.svelte源码形式发布,由你的打包器通过svelte导出条件编译(1.26 KB gzip),类型经svelte/elements完整覆盖——SVG 属性、事件、ARIA 全部自动补全,写错编译即报错,体验对齐 lucide-svelte。SvelteKit 开箱即用。源码:src/svelte/MorphIcon.svelte。
React Native 图标变形:跨平台零重写
import { MorphIcon } from "morphicons/react-native"; import { Menu, X } from "lucide"; <MorphIcon icon={open ? X : Menu} spring="snappy" />浏览器之外的 DOM-free 核心在这里体现得最充分:dom 驱动被原样复用为引擎,整个平台差异只是一个把每帧d写入转发给react-native-svg的Path.setNativeProps的 shim——在 React render 之外执行,与 web 突变模式一致。New Architecture 下还处理了 Fabric 提交重建原生路径的时序问题。源码:src/react-native/index.tsx。
⚠️ 需要 Metro 的 package
exports解析——React Native 0.79 起默认开启,旧版本请启用unstable_enablePackageExports。
Astro 图标组件:零框架运行时的 SSR 形态
--- import MorphIcon from "morphicons/astro"; import { Menu } from "lucide"; --- <MorphIcon icon={Menu} label="Menu" id="menu-icon" />这是最有意思的绑定:hydration 就是一次自定义元素升级。服务端用纯核心输出精确静态 SVG(静态输出和任意 SSR 适配器都工作),页面加载后<morph-icon>元素原样接管服务端标记——升级期间零d写入,由插桩测试钉死。客户端字节只有morphicons/element一份,不发布任何框架运行时。适合那些根本不需要 Island 的页面;岛内的图标仍可用 React/Vue/Svelte 绑定。源码:src/astro/MorphIcon.astro。
Web Component:<morph-icon>出现在任何 HTML
<script type="module"> import { defineMorphIcon } from "morphicons/element"; defineMorphIcon(); // 幂等;自定义标签:defineMorphIcon("my-icon") </script> <morph-icon icon="M4 6h16M4 12h16M4 18h16" label="Menu"></morph-icon>morphicons/element就是第五个绑定,而不是第二套实现——它把属性与属性方法接到同一个共享控制器,暴露同样的三种模式,元素本身就是句柄(el.morphTo(X)/el.icon = X/el.progress = 0.5)。纯 HTML、HTMX、Rails 或任何服务端渲染栈都能用。源码:src/element/index.ts。
无障碍动效:reducedMotion 三档策略
六个绑定统一支持reducedMotion属性,且实时生效(改变它管的是下一次 morph):
"never"(默认):始终动画——图标 morph 属于简短、有沟通性的微过渡,自动降级反而让库看起来像坏了"user":尊重系统"减弱动态效果"设置,开启时降级为即时切换(web 读prefers-reduced-motion,RN 读AccessibilityInfo)"always":全部跳变,适合测试与截图
必须严格跟随用户系统偏好的应用,全局传reducedMotion="user"即可。
本地体验:跑起来 playground
仓库自带可视化 playground:38 个真实图标(Lucide / Feather / Tabler)、每对图标的相似度读数(θ、σ、残差)、弹簧预设与 t 滑杆,见 playground/:
git clone https://gitcode.com/gh_mirrors/mo/morphicons cd morphicons bun install bun run play # → http://localhost:3000架构速览:一个包为什么能喂饱 6 个框架?
┌─────────────────────────────────────────────┐ │ bindings react · vue · svelte · rn │ 薄层:ref + createMorph │ · element (+ astro SSR shell) │ ├─────────────────────────────────────────────┤ │ drivers dom (setAttribute + rAF) │ 单例调度器 ├─────────────────────────────────────────────┤ │ core parse → resample → align → plan │ 纯函数,不碰 DOM │ → interpolate → serialize + spring│ └─────────────────────────────────────────────┘铁律只有一条:core 永不触碰 DOM。纯函数消费图标数据、产出d字符串——正因如此,React Native 绑定只是"又一个适配器"而非重写,六个框架绑定都只是共享驱动上的一层薄壳。这就是"一个组件搞定 6 大框架"的底层原因。
常见问题 FAQ ❓
只支持 Lucide 吗?不。任何满足三条件的线性(stroke)图标集都能用:几何以描边中线形式存在、几何可取为d字符串或节点列表、一对图标共享同一坐标网格。Tabler、Heroicons(outline)、Iconoir 都在 24×24 网格上开箱即用;其他网格(Carbon 32、Teenyicons 15)用fitIcon重排一次即可。
会破坏我现有的 lucide-react / lucide-vue-next 吗?不会。静态图标继续用组件包,morph 用数据包,两者按设计共存。
体积有多大?core 6.60 KB gzip,单个框架绑定约 8 KB,<morph-icon>元素 8.86 KB——对比动辄几十 KB 的动画库,这是"只买你需要"的按需导出。
写在最后
morphicons 的设计哲学值得学习:把复杂留在 core(纯函数 + 数学),把简单留给框架(薄绑定 + 统一契约)。无论你今天用 React 还是明天迁移到 Svelte,<MorphIcon icon={...} />这一行代码都不用改——图标变形动画终于做到了"一次理解,处处复用"。
【免费下载链接】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),仅供参考