- 前端
- 开发工具
【免费下载链接】vanilla-extract
Zero-runtime Stylesheets-in-TypeScript
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(无返回值) |
三个关键细节:
- 名称必须自行保证唯一。
globalKeyframes不做任何哈希或去重,若两个模块声明了同名动画,后编译的规则会覆盖先编译的规则(取决于产物中@keyframes的书写顺序)。因此建议像文档示例那样,把名称定义为模块内的常量(如const rotate = 'globalRotate'),集中管理命名空间。 - 参数类型
CSSKeyframes:第二个参数是描述关键帧步骤的对象,键可为'from'、'to'或百分比字符串(如'0%'、'50%'),值是与style相同的 CSS 属性对象。 - 底层走同一条管线:两个 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 值,包括单位补全(10→10px)、引号规范化等;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: 0→0px、content: '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修改--角度变量的值,配合createVar的syntax: '<angle>'声明,浏览器可以插值该变量实现平滑渐变旋转动画;而fallbackVar保证了在不支持@property的浏览器中样式仍可降级使用。
实战建议与注意事项
- 命名空间约定:由于
globalKeyframes不做哈希,建议为全局动画名添加统一前缀(如globalRotate、brand-fade-in),并在文档或注释中登记,避免多模块命名冲突。 - 引用方式:全局动画名是普通字符串,可以在
animation简写属性(如animation: \3s infinite ${rotate}`)或animationName属性中直接使用;当动画名与 CSS 关键字(如none`)同名时需注意,必要时可用引号包裹。 - 属性值仍受归一化保护:尽管名称是全局的,关键帧内部的数值单位、引号等依然会被
transformProperties/transformVars标准化,因此可以放心使用 TS 对象语法书写。 - 与
keyframes的选择:如果动画只在单个.css.ts模块内使用,优先用keyframes获取哈希隔离;只有需要跨模块、跨文件共享动画名称时,才使用globalKeyframes。
总结
globalKeyframes是 vanilla-extract 全局 API 家族中面向动画的关键成员:它以「不哈希、不返回值」的简洁契约,把@keyframes的命名权交给开发者,同时依然享受完整的类型安全(CSSKeyframes)、属性归一化与零运行时输出。结合本地作用域的keyframes和createVar变量动画能力,你可以在纯 TypeScript 中构建出作用域清晰、可跨模块复用的完整动画体系。
- 前端
- 开发工具
【免费下载链接】vanilla-extract
Zero-runtime Stylesheets-in-TypeScript
相关推荐
vanilla-extract 动画指南:keyframes 的局部作用域与 CSS 变量动画
vanilla extract 动画指南:keyframes 的局部作用域与 CSS 变量动画 vanilla extract 是零运行时(Zero runti
前端开发工具vanilla-extract 的 fontFace API:在 TypeScript 中零运行时定义局部作用域 @font-face
vanilla extract 的 fontFace API:在 TypeScript 中零运行时定义局部作用域 @font face 导读 fontFace
前端开发工具Backstage v1.28.0-next.0 升级指南:Proxy 鉴权强制开启与后端系统迁移要点
Backstage v1.28.0 next.0 升级指南:Proxy 鉴权强制开启与后端系统迁移要点 本文档基于 docs/releases/v1.28.0
前端开发工具
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考