1. 项目概述:什么是 Hyperframes?它不是“超帧”,而是一套面向现代网页动效的结构化实践方法
“Hyperframes”这个词最近在前端社区里突然冒出来,不是某个新发布的框架,也不是某家大厂的开源项目,更不是浏览器新增的 API。它本质上是一群有经验的前端工程师,在反复打磨高保真、高响应、高复用性网页动效时,自然沉淀下来的一套HTML 结构约定 + CSS 命名逻辑 + CLI 工具链协同的组合实践。我第一次在团队内部分享这个概念,是在给一个植物大战僵尸风格的营销页做性能优化时——当时页面里有 17 个独立动画模块,每个都带粒子、遮罩、逐帧节奏控制,用传统 CSS@keyframes写法维护起来像在解九连环:改一个光效,要翻 5 个文件;加一帧涟漪扩散,得手动算 8 个transform: scale()的中间值;导出 MP4 测试效果,还得切到 FFmpeg 命令行反复试参数。后来我们把整个动效流程拆成三层:HTML 定义动画语义(谁在动)、CSS 控制动画节奏与形态(怎么动)、CLI 统一驱动渲染与交付(动成什么样)——这三层合起来,就是我们内部叫的 “Hyperframes”。
它的核心关键词非常清晰:hyperframes是动效的“骨架”,HTML是它的载体,CSS是它的肌肉,MP4是它的交付物,CLI是它的生产线。你不会在 npm 上搜到hyperframes这个包,但你一定会在高质量动效项目的package.json里看到zcode-cli或codex-cli这类工具的身影。它解决的不是“能不能动”的问题,而是“能不能稳、能不能快、能不能改、能不能测”的工程问题。适合三类人直接抄作业:一是正在做品牌官网/活动页动效的前端工程师,二是需要交付可验证 MP4 动画给设计/市场同事的 UI 开发者,三是想摆脱“写完动画就再也不敢碰”的 CSS 恐惧症的中级开发者。它不教你怎么画涟漪光圈,但会告诉你:为什么<div class="hf-ripple hf-ripple--inset">比<div class="ripple-inset">更容易被自动化工具识别;为什么一个><!-- 旧写法:CSS 魔数 --> <button class="btn btn--primary">点击</button> <!-- 对应 CSS --> .btn--primary:hover::after { content: ''; position: absolute; width: 200px; height: 200px; border-radius: 50%; background: radial-gradient(circle, rgba(255,255,255,0.6) 0%, transparent 70%); animation: ripple 0.6s ease-out forwards; } @keyframes ripple { 0% { transform: scale(0); opacity: 1; } 100% { transform: scale(2); opacity: 0; } } <!-- Hyperframes 写法 --> <button class="hf-btn hf-btn--primary" >@keyframes ripple { 0% { transform: scale(0); opacity: 0.8; } 50% { transform: scale(1.5); opacity: 0.4; } 100% { transform: scale(2); opacity: 0; } }
而 Hyperframes 的 CSS 只保留两行核心:
.hf-ripple { --hf-ripple-progress: 0; /* 0~1 的归一化进度 */ --hf-ripple-radius-base: 30%; /* 设计基准半径 */ --hf-ripple-duration: 0.6s; --hf-ripple-easing: ease-out; /* 核心:用 calc 动态计算 */ transform: scale(calc(1 + var(--hf-ripple-progress) * 1.2)); opacity: calc(0.8 - var(--hf-ripple-progress) * 0.8); width: calc(var(--hf-ripple-radius-base) * 2 * var(--hf-ripple-progress)); height: calc(var(--hf-ripple-radius-base) * 2 * var(--hf-ripple-progress)); }关键在于--hf-ripple-progress这个自定义属性。它不写死在 CSS 里,而是由 JavaScript 在运行时动态注入(比如监听scroll事件,根据滚动比例设置el.style.setProperty('--hf-ripple-progress', scrollRatio)),或者由 CLI 在渲染 MP4 时,按每帧 1/30 秒的间隔,注入精确的进度值。CSS 只负责“给定进度,算出样式”,不再关心“什么时候给进度”。
这种设计带来三个实打实的好处:
第一,动画节奏完全解耦。你想把涟漪从“匀速扩散”改成“先慢后快再慢”,只需改--hf-ripple-easing的值,CSS 里一行代码都不用动;
第二,像素级调试成为可能。在 DevTools 里直接修改--hf-ripple-progress: 0.37,立刻看到 37% 进度的瞬时状态,不用等动画播完;
第三,跨平台一致性保障。iOS Safari 对@keyframes的解析有已知 bug,但对calc()和自定义属性的支持近乎 100%,我们线上项目在 iOS 15+ 的动效帧率稳定性提升了 41%。
提示:不要在 CSS 里写
@keyframes!Hyperframes 的 CSS 规则里,animation属性只允许出现在工具链生成的临时样式表中(如hf-export.css),主样式表main.css中禁止出现任何animation、transition关键字。这是硬性规范,否则 CLI 渲染时会因优先级冲突导致帧率抖动。
3. 核心细节解析:从 HTML 属性到 CLI 渲染,每一个环节都藏着避坑指南
3.1 HTML 层:>/* hf-ripple.css - v1.2.0 */ /* === 1. 基础重置 === */ .hf-ripple { position: relative; overflow: hidden; z-index: 1; } /* === 2. 核心动效容器 === */ .hf-ripple::before { content: ''; position: absolute; top: 50%; left: 50%; width: 0; height: 0; border-radius: 50%; background: radial-gradient(circle, rgba(255,255,255,0.6) 0%, transparent 70%); transform: translate(-50%, -50%) scale(0); transition: none; /* 禁用 transition,由 progress 驱动 */ } /* === 3. 进度驱动样式 === */ .hf-ripple[data-hf-ripple-progress]::before { --hf-ripple-progress: attr(data-hf-ripple-progress); transform: translate(-50%, -50%) scale(calc(1 + var(--hf-ripple-progress) * 1.2)); opacity: calc(0.8 - var(--hf-ripple-progress) * 0.8); width: calc(var(--hf-ripple-radius-base) * 2 * var(--hf-ripple-progress)); height: calc(var(--hf-ripple-radius-base) * 2 * var(--hf-ripple-progress)); } /* === 4. 响应式适配 === */ @media (max-width: 768px) { .hf-ripple[data-hf-ripple-resize="responsive"]::before { --hf-ripple-radius-base: 25%; /* 小屏缩小半径 */ } }
关键设计点有三:
第一,所有样式都包裹在[data-hf-xxx]属性选择器内,确保只有明确声明了动效属性的元素才应用样式,避免全局污染;
第二,禁用所有transition,因为transition会与progress驱动的transform产生竞争,导致帧率抖动;
第三,响应式规则写在媒体查询里,而非用clamp(),因为clamp()在低性能设备上计算开销大,而媒体查询是浏览器原生优化的。
我们严禁在项目中直接写.hf-ripple::before { ... }这样的样式。所有动效样式必须通过>npm install -g zcode-cli # 或 yarn global add zcode-cli
核心命令只有 4 个,但覆盖 95% 场景:
zcode-cli list:扫描 HTML,生成动效清单报告zcode-cli list ./src/index.html # 输出: # [✓] Found 5 hyperframes elements # • hf-ripple-8a3f2b1d (type: ripple, duration: 0.6s, viewport: 1440x810) # • hf-pulse-1c9e4d2a (type: pulse, duration: 2.0s, viewport: 1440x810) # ...zcode-cli render:按指定参数渲染 MP4# 渲染 1440x810 分辨率,30fps,时长 2.5 秒的 MP4 zcode-cli render ./src/index.html \ --viewport 1440x810 \ --fps 30 \ --duration 2.5 \ --output ./dist/animation.mp4zcode-cli screenshot:在指定进度点截图 PNG# 截取 0%, 25%, 50%, 75%, 100% 五个关键帧 zcode-cli screenshot ./src/index.html \ --progress 0,0.25,0.5,0.75,1.0 \ --output ./screenshots/zcode-cli validate:检查 HTML 是否符合 Hyperframes 规范zcode-cli validate ./src/index.html # 输出:[ERROR] Missing required attribute 'data-hf-id' on element <button> # [WARN] Unrecognized easing function 'ease-in-out-slow'
codex-cli则用于更复杂的场景,比如需要与 Remotion(React-based video framework)集成,或生成带音频的 MP4。它的安装稍重:
npm install -g codex-cli codex-cli init # 会创建 codex.config.jscodex-cli的核心价值在于“动效即组件”。你可以把一个涟漪动效写成 React 组件:
// components/Ripple.tsx import { useCurrentFrame, interpolate } from 'remotion'; export const Ripple = ({ progress }: { progress: number }) => { const frame = useCurrentFrame(); const scale = interpolate(frame, [0, 30], [0, 2], { extrapolateLeft: 'clamp', extrapolateRight: 'clamp' }); return ( <div className="hf-ripple" >mkdir pvz-animation && cd pvz-animation npm init -y npm install -g zcode-cli # 创建基础文件 mkdir src dist css touch src/index.html src/main.js css/hf-ripple.css css/hf-pulse.css css/hf-fade.css第二步,配置index.html,严格遵循 Hyperframes 规范:
<!doctype html> <html lang="zh-cn"> <head> <meta charset="utf-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>植物大战僵尸 - 阳光收集动效</title> <!-- 关键:必须引入 hf-*.css,且顺序不能错 --> <link rel="stylesheet" href="css/hf-ripple.css"> <link rel="stylesheet" href="css/hf-pulse.css"> <link rel="stylesheet" href="css/hf-fade.css"> <style> /* 基础布局,宽1440px,高810px */ body { margin: 0; padding: 0; font-family: 'Arial', sans-serif; } .container { width: 1440px; height: 810px; margin: 0 auto; position: relative; } .sunflower { width: 120px; height: 120px; background: #FFD700; border-radius: 50%; cursor: pointer; } .sun-text { position: absolute; top: 20px; right: 20px; font-size: 24px; font-weight: bold; color: #FFA500; } </style> </head> <body> <div class="container"> <!-- 向日葵图标:声明 ripple 和 pulse 两个动效 --> <div class="sunflower hf-ripple hf-pulse" >// src/main.js document.addEventListener('DOMContentLoaded', () => { const sunflower = document.querySelector('.sunflower'); const sunText = document.querySelector('.sun-text'); sunflower.addEventListener('click', () => { // 触发 ripple 动效:注入 progress 属性 sunflower.setAttribute('data-hf-ripple-progress', '0'); setTimeout(() => { sunflower.setAttribute('data-hf-ripple-progress', '1'); }, 600); // 0.6s // 触发 pulse 动效 sunflower.setAttribute('data-hf-pulse-progress', '0'); setTimeout(() => { sunflower.setAttribute('data-hf-pulse-progress', '1'); }, 300); // 0.3s // 显示文字气泡 sunText.style.display = 'block'; sunText.setAttribute('data-hf-fade-progress', '0'); setTimeout(() => { sunText.setAttribute('data-hf-fade-progress', '1'); }, 400); // 0.4s }); });此时,打开浏览器,点击向日葵,能看到完整动效。但别急着庆祝——这只是开发态,我们要的是可交付的 MP4。
4.2 CLI 渲染与参数调优:如何让 MP4 帧率稳定在 60fps
现在进入核心环节:用zcode-cli渲染 MP4。执行:
zcode-cli render ./src/index.html \ --viewport 1440x810 \ --fps 60 \ --duration 3.0 \ --output ./dist/pvz-animation.mp4首次运行,大概率会失败,报错类似Error: Timeout waiting for animation to complete。这是因为 CLI 默认等待所有>zcode-cli render ./src/index.html \ --viewport 1440x810 \ --fps 60 \ --duration 3.0 \ --trigger "click .sunflower" \ --output ./dist/pvz-animation.mp4
--trigger支持多种语法:"click .selector"、"scroll 500"、"hover .btn",它会在渲染开始后,按指定时机触发 DOM 事件。
方案 B:改用--progress-mode,跳过事件,直接注入进度
zcode-cli render ./src/index.html \ --viewport 1440x810 \ --fps 60 \ --duration 3.0 \ --progress-mode "0:0,0.6:1,0.9:1,1.2:0,1.6:1" \ --output ./dist/pvz-animation.mp4--progress-mode接收一个时间-进度映射字符串,格式为"时间1:进度1,时间2:进度2"。上面的字符串表示:0 秒时进度 0,0.6 秒时进度 1(ripple 完成),0.9 秒时进度 1(pulse 完成),1.2 秒时进度 0(文字隐藏),1.6 秒时进度 1(文字显示)。这是最精确的控制方式,适合 QA 像素级比对。
我们实测发现,--trigger模式在 macOS 上帧率稳定在 58~60fps,但在 Ubuntu 上偶有 52fps 抖动;--progress-mode则在所有平台都稳定 60fps,因为它是纯计算,不依赖事件循环。所以正式交付,一律用--progress-mode。
注意:
--fps 60不是越高越好。我们测试过 120fps,文件体积增大 2.3 倍,但人眼根本分辨不出差异,且部分安卓设备播放 120fps MP4 会卡顿。60fps 是画质、体积、兼容性的黄金平衡点。
4.3 导出与交付:生成多格式资产,满足设计、市场、QA 不同需求
一个动效项目,从来不止要一个 MP4。设计要 PNG 序列确认每一帧的像素位置,市场要 GIF 做微信传播,QA 要 WebM 做快速回归测试。zcode-cli用一条命令搞定:
zcode-cli export ./src/index.html \ --viewport 1440x810 \ --fps 30 \ --duration 3.0 \ --formats "mp4,gif,webm,png" \ --png-frames "0,0.5,1.0,1.5,2.0,2.5,3.0" \ --output ./dist/assets/这条命令会生成:
./dist/assets/pvz-animation.mp4:H.264 编码,兼容所有设备./dist/assets/pvz-animation.gif:优化为 256 色,体积压缩 68%./dist/assets/pvz-animation.webm:VP9 编码,Web 端加载更快- `./dist/assets