vanilla-extract globalKeyframes 完全指南:零运行时地定义全局作用域 @keyframes 动画
2026/9/24 17:20:32 网站建设 项目流程
  • 前端
  • 开发工具

【免费下载链接】vanilla-extract

Zero-runtime Stylesheets-in-TypeScript

项目地址:https://gitcode.com/gh_mirrors/va/vanilla-extract
点击查看免费下载

globalKeyframes是 vanilla-extract(Zero-runtime Stylesheets-in-TypeScript)提供的全局作用域动画 API,用于在不经过任何运行时的情况下,向编译产物输出一个名称完全由你控制的@keyframes规则。本指南围绕 site/docs/global-api/global-keyframes.md 展开,讲解它与本地作用域keyframes的本质区别、API 签名与返回值、底层编译原理(名称生成与属性归一化),并给出可直接复制运行的动画示例,读完即可在.css.ts文件中为跨组件共享动画编写全局关键帧。

为什么需要 globalKeyframes:本地作用域与全局作用域的分工

vanilla-extract 的核心设计之一是「作用域隔离」:通过 generateIdentifier 基于文件 scope 哈希和引用计数生成形如文件哈希_序号的确定性名称,保证每个.css.ts模块内声明的类名、变量名互不冲突。动画名称同样适用这一机制:

  • keyframes(本地作用域):调用后由 vanilla-extract 生成哈希化的动画名称并返回该名称,你只能通过返回值引用它。适用于「动画只属于当前模块」的场景,名称冲突完全不可能发生。
  • globalKeyframes(全局作用域):直接使用你传入的字符串作为@keyframes的动画名称,不返回任何值。适用于动画需要在多个组件、多个.css.ts模块甚至全局样式中被引用的场景,例如品牌级旋转、渐入等通用动效。

也就是说,globalKeyframes与文档中另一侧的 keyframes 构成互补:前者把「命名权」交还给你,后者把「命名权」交给编译器。

基本用法:定义一个全局动画并在样式中引用

以文档中的示例为基础(原样可运行),创建一个animation.css.ts文件:

// animation.css.ts import { globalKeyframes, style } from '@vanilla-extract/css'; const rotate = 'globalRotate'; globalKeyframes(rotate, { '0%': { transform: 'rotate(0deg)' }, '100%': { transform: 'rotate(360deg)' } }); export const spin = style({ animation: `3s infinite ${rotate}` });

编译后产生的 CSS 大致为:

@keyframes globalRotate { 0% { transform: rotate(0deg); } 100% { transform: rotate(360deg); } } .spin_哈希 { animation: 3s infinite globalRotate; }

要点在于:spin类名仍然会被哈希化(保持样式隔离),而globalRotate动画名则原样保留在产物中。这样,其它任意.css.ts或普通 CSS 文件都可以用字符串'globalRotate'直接引用该动画。

API 签名与返回值:与 keyframes 的源码级差异

从 packages/css/src/style.ts 的实现可以看到两个 API 的本质差异:

export function keyframes(rule: CSSKeyframes, debugId?: string) { const name = cssesc(generateIdentifier(debugId), { isIdentifier: true, }); appendCss({ type: 'keyframes', name, rule }, getFileScope()); return name; } export function globalKeyframes(name: string, rule: CSSKeyframes) { appendCss({ type: 'keyframes', name, rule }, getFileScope()); }
API第一个参数名称来源返回值
keyframes(rule, debugId?)关键帧对象generateIdentifier(debugId)哈希生成返回生成后的动画名称字符串
globalKeyframes(name, rule)你指定的动画名称字符串原样使用,不哈希返回void(无返回值)

三个关键细节:

  1. 名称必须自行保证唯一globalKeyframes不做任何哈希或去重,若两个模块声明了同名动画,后编译的规则会覆盖先编译的规则(取决于产物中@keyframes的书写顺序)。因此建议像文档示例那样,把名称定义为模块内的常量(如const rotate = 'globalRotate'),集中管理命名空间。
  2. 参数类型CSSKeyframes:第二个参数是描述关键帧步骤的对象,键可为'from''to'或百分比字符串(如'0%''50%'),值是与style相同的 CSS 属性对象。
  3. 底层走同一条管线:两个 API 最终都通过appendCss({ type: 'keyframes', name, rule }, getFileScope())把关键帧注册到当前文件作用域(File Scope)中,因此它们产出的规则会被同一套编译流程处理。

编译原理:关键帧规则如何被转换与输出

globalKeyframes注册的规则最终由transformCss处理。packages/css/src/transformCss.ts 中对keyframes类型规则的处理如下:

if (root.type === 'keyframes') { root.rule = Object.fromEntries( Object.entries(root.rule).map(([keyframe, rule]) => { return [keyframe, this.transformVars(this.transformProperties(rule))]; }), ); this.keyframesRules.push(root); return; }

这意味着每一个关键帧步骤内的声明都会经历两次归一化

  • transformProperties:把 TS 对象中的 CSS 属性值转换为标准 CSS 值,包括单位补全(1010px)、引号规范化等;
  • transformVars:处理vars属性中的 CSS 变量(见下文进阶用法)。

处理完成后,所有关键帧规则被收集到keyframesRules数组,最终在渲染阶段统一输出(transformCss.ts#L600-L602):

for (const keyframe of this.keyframesRules) { css.push(renderCss({ [`@keyframes ${keyframe.name}`]: keyframe.rule })); }

name字段此时就是globalKeyframes传入的原始字符串,因此在产物中你看到的必然是@keyframes globalRotate而非哈希名。以上行为有测试用例佐证:packages/css/src/transformCss.test.ts 中的should handle animations用例验证了from/to步骤中padding: 00pxcontent: 'foo'"foo"等属性转换结果。

进阶:在全局关键帧中动画 CSS 变量

vaniila-extract 支持在关键帧步骤内通过vars属性改变 CSS 变量的值,实现「动画 CSS 变量」的效果。虽然该示例在 keyframes 文档 中演示,但同样适用于globalKeyframes——因为二者走的是同一条transformVars转换管线。配合 createVar 声明的类型化变量,可以做出渐变角度旋转等高级动画:

// animation.css.ts import { createVar, fallbackVar, globalKeyframes, style } from '@vanilla-extract/css'; const angle = createVar({ syntax: '<angle>', inherits: false, initialValue: '0deg' }); const angleKeyframes = 'globalAngleKeyframes'; globalKeyframes(angleKeyframes, { '0%': { vars: { [angle]: '0deg' } }, '100%': { vars: { [angle]: '360deg' } } }); export const root = style({ backgroundImage: `linear-gradient(${angle}, rgba(153, 70, 198, 0.35) 0%, rgba(28, 56, 240, 0.46) 100%)`, animation: `${angleKeyframes} 7s infinite ease-in-out both`, vars: { // 浏览器不支持 @property 时回退到 180deg [angle]: fallbackVar(angle, '180deg') } });

这里globalKeyframes的每个关键帧步骤通过vars修改--角度变量的值,配合createVarsyntax: '<angle>'声明,浏览器可以插值该变量实现平滑渐变旋转动画;而fallbackVar保证了在不支持@property的浏览器中样式仍可降级使用。

实战建议与注意事项

  1. 命名空间约定:由于globalKeyframes不做哈希,建议为全局动画名添加统一前缀(如globalRotatebrand-fade-in),并在文档或注释中登记,避免多模块命名冲突。
  2. 引用方式:全局动画名是普通字符串,可以在animation简写属性(如animation: \3s infinite ${rotate}`)或animationName属性中直接使用;当动画名与 CSS 关键字(如none`)同名时需注意,必要时可用引号包裹。
  3. 属性值仍受归一化保护:尽管名称是全局的,关键帧内部的数值单位、引号等依然会被transformProperties/transformVars标准化,因此可以放心使用 TS 对象语法书写。
  4. keyframes的选择:如果动画只在单个.css.ts模块内使用,优先用keyframes获取哈希隔离;只有需要跨模块、跨文件共享动画名称时,才使用globalKeyframes

总结

globalKeyframes是 vanilla-extract 全局 API 家族中面向动画的关键成员:它以「不哈希、不返回值」的简洁契约,把@keyframes的命名权交给开发者,同时依然享受完整的类型安全(CSSKeyframes)、属性归一化与零运行时输出。结合本地作用域的keyframescreateVar变量动画能力,你可以在纯 TypeScript 中构建出作用域清晰、可跨模块复用的完整动画体系。

  • 前端
  • 开发工具

【免费下载链接】vanilla-extract

Zero-runtime Stylesheets-in-TypeScript

项目地址:https://gitcode.com/gh_mirrors/va/vanilla-extract
点击查看免费下载
上一篇:Beads 错误处理规范:三种模式的工程实践与源码解析
下一篇:Mastra 教程:为 Financial Assistant Agent 添加持久化记忆(Memory + LibSQL 实战)

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询