零框架运行时的图标动画:morphicons Web Component与Astro SSR壳设计,custom element升级即水合
零框架运行时的图标动画morphicons Web Component与Astro SSR壳设计custom element升级即水合【免费下载链接】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/morphiconsmorphicons是一个零依赖、约 7 KB gzip 的图标变形morphing动画库任何描边图标都能变形为另一个图标旋转角度由数学自动推导无需手写。它的morph-iconWeb Component 配合一个无逻辑的 Astro SSR 壳让图标动画在不引入任何框架运行时的前提下实现服务端精确首屏 custom element 升级即水合hydration。这篇文章带你读懂这套设计的核心思想以及为什么它对 Astro / 纯 HTML / HTMX 等不想为图标背上框架运行时的场景特别友好。痛点图标动画为什么不想背框架运行时在 React/Vue 项目里给图标加动画直接 import 一个组件就好。但 Astro 的哲学恰恰相反服务端渲染一次客户端尽量不 hydrate。如果你给每个独立图标都套一个 React 岛屿等于把整个框架运行时下发到浏览器——这正是 Astro 用户选择 Astro 想避免的成本。morphicons 的方案很直接Astro 本身就自带一套客户端响应式表面——custom elements。于是它没有写第五种框架绑定而是把 Web Component 做成独立绑定Astro 只是它的一个薄壳。决策过程记录在 docs/adr/0003-astro-rides-a-custom-element.md被否掉的方案包括纯.astro组件SSR 容易但渲染后没有响应式生命周期三种控制模式全都做不了只用岛屿可行但每个图标都被迫背上框架运行时custom element SSR 壳最终选择一个入口同时服务 Astro、纯 HTML、HTMX、Rails 等任何服务端渲染栈。核心机制升级即水合upgrade hydration这套设计最妙的一句话是hydration IS custom-element upgrade。整个流程分三步服务端Astro 壳用纯核心无 DOM 的纯函数算出初始d字符串输出逐字节精确的静态svg——零闪烁、零布局偏移静态输出和任意 SSR 适配器都工作页面加载后Astro 自动处理的script每页只执行一次defineMorphIcon()把morph-icon注册为 custom element升级瞬间浏览器把页面上已存在的morph-icon标记升级成新元素。元素原样收养adopt verbatim服务端渲染的svgpath不重写任何 SSR 字节。更精细的细节首次挂载会走一个写门控的PathEl把驱动器的初始化写入全部吞掉——因为那些写入只会重新推导出服务端已经画好的同一串字节。结果是升级过程执行 0 次d写入并有插桩测试instrumentedsetAttributetests钉死这个不变量。相关实现见 src/element/index.ts 中connectedCallback的 gate 逻辑。 一个例外如果你在升级前用脚本改过属性或调用过morphToSSR 字节就不再描述当前状态门控自动失效、元素正常重绘——宁可多画一帧也不冻结过期像素。同一份生命周期契约三种控制模式morph-icon是第五个绑定不是第二套实现它把属性和方法接到与 Svelte 绑定共享的同一控制器src/dom/controller.ts并通过了完全镜像的挂载测试套件。三个模式在其他四个框架绑定里长什么样这里就长什么样模式用法说明1️⃣ 非受控给icon属性/属性赋新值90% 场景属性变更自动播放弹簧动画2️⃣ 受控fromtoprogress手势/滚动 scrubbing服务端可冻结成静态帧3️⃣ 命令式el.morphTo(X)/el.set(X)元素本身就是 handle像 ref 一样调用属性只携带字符串d路径、弹簧预设名完整的IconNode、自定义弹簧对象走 JS 属性。这是 HTML 的天然限制也是属性给 HTML 作者、属性给脚本的双面设计。Astro 壳长什么样一行逻辑都没有打开 src/astro/MorphIcon.astro 你会发现它出奇地薄MorphIcon icon{Menu} labelMenu idmenu-icon /壳文件只做两件事用computeInitialD算出初始d拼出morph-icon 内嵌svg的静态标记在页面底部输出一个script调用defineMorphIcon()。它甚至自带morph-icon { display: contents }让包裹元素从第一帧开始就不影响布局。受控的from/to只有在两端都是d字符串时才会转发为属性——这样升级后元素仍留在受控模式progress能继续 scrubbing 服务端冻结的那对图标IconNode对则保持服务端专用升级后用脚本给元素属性赋值来控制。这种无逻辑壳shell是项目术语表里的正式概念它只把响应式表面接到已类型检查的逻辑上本身不可能单独出错——这就是它从不做类型检查也安心的原因见 CONTEXT.md 的 Shell 条目。不止 Astromorph-icon在任何 HTML 都成立Astro 壳只是morphicons/element上的一层薄 SSR。去掉 Astro它同样独立工作于纯 HTML、HTMX、Railsscript typemodule import { defineMorphIcon } from morphicons/element; defineMorphIcon(); // 幂等 /script !-- 属性接受 d 字符串label 让图标对读屏友好 -- morph-icon iconM4 6h16M4 12h16M4 18h16 labelMenu/morph-icon该模块在 SSR 环境下导入也安全类继承自一个惰性基类无HTMLElement时求值不抛错defineMorphIcon()在没有 DOM 时自动 no-op。体积账单这才是零框架运行时的真实含义morphicons 在 CI 里为每个入口设了 gzip 体积闸门防回归绊线Astro 场景下的账单是入口实测 gzipMorphIcon.astro源码交付消费者编译1.58 KBmorphicons/element全部逻辑8.86 KB也就是说客户端下载的全部字节就是morphicons/element这一个模块——没有 React、没有 Vue、没有任何框架运行时。对照 README.md 里的完整体积表可以看到这个元素和框架绑定们是同一量级甚至更轻省掉了框架胶水。快速上手清单 安装npm install morphicons或bun add morphicons/pnpm add图标数据来自 vanillalucide包数据不是组件import { Menu } from lucideAstro 页面里import MorphIcon from morphicons/astro写MorphIcon icon{Menu} labelMenu idmenu-icon /需要交互时在任意script里document.querySelectorMorphIconElement(#menu-icon)然后el.morphTo(X)动画切换或el.icon X赋值即变形。注意图标数据必须来自lucide而非lucide-react等组件包详见 README.md。延伸阅读想深挖设计决策与源码建议按这个顺序读决策依据docs/adr/0003-astro-rides-a-custom-element.md——三个候选方案、逐字收养、写门控、半对转发等所有细节的来龙去脉元素实现src/element/index.ts——升级、收养、写门控的完整逻辑SSR 壳src/astro/MorphIcon.astro——不到 110 行的薄壳范本共享控制器src/dom/controller.ts——computeInitialD与生命周期契约惰性驱动器 / 受控优先 / 干净重入的单一实现契约冻结原则docs/adr/0001-frozen-core-contracts-adapters-entry.md——为什么适配器按格式而非框架/厂商划分。一句话总结morphicons 把 Astro 的少水合哲学和 custom element 的原生生命周期对齐——服务端给精确像素浏览器负责升级而升级本身就是水合。零框架运行时图标照样动起来。【免费下载链接】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),仅供参考