news 2026/9/11 13:34:44

如何用 @hyperframes/parsers 解析 HTML 合成并安全改写 GSAP 时间线脚本

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
如何用 @hyperframes/parsers 解析 HTML 合成并安全改写 GSAP 时间线脚本

如何用 @hyperframes/parsers 解析 HTML 合成并安全改写 GSAP 时间线脚本

【免费下载链接】hyperframesWrite HTML. Render video. Built for agents.项目地址: https://gitcode.com/GitHub_Trending/hy/hyperframes

当你在 hyperframes 组合(composition)之上构建工具——例如让 agent 自动调整动画节奏、批量移动时间线上的元素,或做一个“改完写回原文件”的自动化脚本——就需要先把 HTML 组合解析成结构化模型,再修改其中的 GSAP 时间线脚本,同时保证脚本里与目标动画无关的代码一行都不变。hyperframes 仓库里的@hyperframes/parsers包就是为这一层设计的:它没有@hyperframes/*运行时依赖,可以脱离框架其余部分单独使用。GSAP 改写的核心是“解析 → 变更 → 重新序列化”的往返(round-trip):写路径按字节区间做 splice,编辑区间之外的内容逐字节保留。

这条路径的完整操作顺序是:安装包 → 解析 HTML 组合取出 GSAP 脚本 → 把脚本解析成结构化动画模型 → 用 writer 助手函数改写 → 通过再解析与校验确认结果。

安装与导入入口

npm install @hyperframes/parsers

官方文档明确提示:大多数用户不需要直接安装@hyperframes/parsers——@hyperframes/core已经重新导出了它需要的解析器 API,CLI 和 studio 也是传递依赖它。只有当你想要纯解析层、不引入 core 的 runtime / compiler / generators 时,才直接使用这个包。

包提供一组聚焦的 subpath 入口,本场景涉及的是:

导入路径用途
@hyperframes/parsersHTML 解析器、GSAP 序列化/校验助手、hf-ids、共享类型
@hyperframes/parsers/gsap-parser-acornAcorn 实现的 GSAP 解析器(读路径,browser-safe)
@hyperframes/parsers/gsap-writer-acornAcorn 实现的 GSAP 写路径(mutation 助手函数)
@hyperframes/parsers/gsap-constantsSUPPORTED_PROPSSUPPORTED_EASES、属性分组
@hyperframes/parsers/hf-ids确定性的元素 id 打戳

两个实现要点:读路径基于 acorn + acorn-walk,无 Node 全局、无fs/require,可以打进浏览器 bundle;写路径基于 magic-string 做字节区间覆写,编辑区间外的每一个字节都原样保留,没有任何 pretty-printer 重排@hyperframes/parsers/gsap-parser-recast是遗留的 recast 实现,新代码走 acorn 路径。

解析 HTML 组合,取出时间线脚本

把组合 HTML 文件读入字符串后交给parseHtml,它在 HTML 与结构化数据之间往返:

import { parseHtml, extractCompositionMetadata, validateCompositionHtml, } from '@hyperframes/parsers'; // 把 HTML 解析成结构化数据 const parsed = parseHtml(htmlString); // parsed.elements, parsed.gsapScript, parsed.styles, parsed.resolution, parsed.keyframes // 提取组合元数据(id、duration、尺寸、variables) const meta = extractCompositionMetadata(htmlString); // 校验 HTML 结构 const result = validateCompositionHtml(htmlString); // result.valid, result.errors

如果validateCompositionHtml返回valid: false,先根据errors处理文档结构问题,再进入脚本改写环节。后续步骤的操作对象是parsed.gsapScript这个字符串。

如果工具需要稳定的元素标识来做 diff 与编辑,先用ensureHfIds给文档打戳:

import { ensureHfIds } from '@hyperframes/parsers/hf-ids'; // 给文档中每个可编辑元素盖上 hf-id 属性 const withIds = ensureHfIds(htmlString);

hf-id是确定性的:同一份文档每次打戳结果相同,因此可以作为跨轮次 diff 和定位改写的锚点。只改时间线、不涉及元素身份识别的工具可以跳过这一步。

把时间线脚本解析成结构化模型

parsed.gsapScript调用parseGsapScriptAcorn,得到ParsedGsap对象:

import { parseGsapScriptAcorn, extractGsapLabels } from '@hyperframes/parsers/gsap-parser-acorn'; const gsap = parseGsapScriptAcorn(scriptContent); // gsap.animations, gsap.timelineVar, gsap.preamble, gsap.postamble

几个关键点:

  • gsap.timelineVar是时间线变量名(tl = gsap.timeline()中的tl),writer 生成和定位 tween 语句时依赖它;
  • gsap.animations每一项是GsapAnimationidtargetSelectormethodset/to/from/fromTo)、positionpropertiesdurationease,以及可选的keyframesextras(stagger、yoyo、repeat 这类不可编辑配置,往返时会被保留);
  • id是基于内容生成的,不是基于位置——同一条 tween 无论排在脚本哪个位置,id 都稳定。所以“先按targetSelector或 id 找到动画,再拿 id 去改写”是安全的定位方式;
  • extractGsapLabels(script)可以取出脚本中的 label 信息,供需要按 label 跳转的工具使用;
  • ParsedGsap上还有multipleTimelinesunsupportedTimelinePattern两个可选标志,使用 writer 之前先确认脚本落在受支持的单一时间线形态内。

改动前,用常量表核对要写入的属性与缓动值是否在支持集合里:

import { SUPPORTED_PROPS, SUPPORTED_EASES } from '@hyperframes/parsers/gsap-constants';

用 writer 助手函数改写脚本

所有 mutation 助手都直接操作脚本文本并返回新字符串,不相关的代码原样保留。主路径 API 如下(签名与仓库源码一致):

import { updateAnimationInScript, addAnimationToScript, removeAnimationFromScript, shiftPositionsInScript, scalePositionsInScript, } from '@hyperframes/parsers/gsap-writer-acorn'; // 更新单条动画(文档中的标准示例) const next = updateAnimationInScript(scriptContent, animationId, { duration: 2 }); // 新增动画,返回值包含新条目的内容式 id const { script: withNew, id: newId } = addAnimationToScript(scriptContent, newAnimation); // 按 id 移除一条动画 const cleaned = removeAnimationFromScript(scriptContent, animationId); // 把某个 selector 的全部 tween 平移 delta 秒(结果位置被钳制在 ≥ 0) const moved = shiftPositionsInScript(scriptContent, '#card', 0.5); // 把旧片段 [oldStart, oldDuration] 线性重映射到 [newStart, newDuration](position 与 duration 按比例换算) const resized = scalePositionsInScript(scriptContent, '#card', 0, 2, 0.5, 3);

实现中内置了几条安全性质,对照 gsapWriterAcorn.ts 源码可以逐一确认:

  • 失败即 no-op:writer 助手内部先执行parseGsapScriptAcornForWrite,解析失败或animationId找不到时,原样返回输入脚本。也就是说“返回的字符串和输入完全相同”本身就是一次信号——改写没有生效,先回到解析结果排查,而不是以为改掉了;
  • 更新语义要分清updateAnimationInScriptproperties替换可编辑属性集合(不在更新里的可编辑 key 会被移除);只传duration/ease/extras是就地 upsert;传position会覆写 tween 调用的位置参数(调用原本没有显式位置参数时则补一个);
  • 每个元素只保留一个位置写入dedupePositionWritesInScript(script, selector, keepId?)会移除同一 selector 上其他纯位置写入(包括duration: 0的退化 tween),避免两处位置写入互相静默覆盖。提交位置类改动(拖拽、加 keyframe)之后应调用它维持该不变量;
  • keyframe 级编辑:对使用keyframes: { ... }的脚本,另有updateKeyframeInScript/addKeyframeToScript/removeKeyframeFromScript等助手。

如果脚本里的值是运行时动态算出来的,注意可编辑性边界。解析器会给每条 tween 标注来源(provenance),并据此划分三种可编辑性:

  • direct—— 字面量 tween,可直接就地编辑;
  • unroll—— helper/循环展开生成,需先展开成字面量 tween 再编辑;
  • source—— 运行时动态值,静态编辑不适用,只能直接改源码

所以改写前检查目标动画的hasUnresolvedSelector/hasUnresolvedKeyframes;值来自source的动画,AST 改写路径不适用,改代码本身。

端到端示例与结果验证

下面是一个可运行的最小端到端示例(脚本内容为示例值,API 调用可直接执行):

import { parseGsapScriptAcorn } from '@hyperframes/parsers/gsap-parser-acorn'; import { updateAnimationInScript, shiftPositionsInScript } from '@hyperframes/parsers/gsap-writer-acorn'; import { validateCompositionGsap } from '@hyperframes/parsers'; const script = [ 'const tl = gsap.timeline();', "tl.from('#title', { opacity: 0, y: 40, duration: 0.8, ease: 'power2.out' }, 0);", "tl.to('#card', { x: 120, duration: 1.2, ease: 'power1.inOut' }, 0.4);", ].join('\n'); // 1) 解析并定位动画 const before = parseGsapScriptAcorn(script); const target = before.animations.find((a) => a.targetSelector === '#card'); // 2) 改写:把 duration 改为 2s,再把该 selector 整体后移 0.5s let next = updateAnimationInScript(script, target.id, { duration: 2 }); next = shiftPositionsInScript(next, '#card', 0.5); // 3) 验证:重新解析,确认改动落地、条目数量未变 const after = parseGsapScriptAcorn(next); const edited = after.animations.find((a) => a.id === target.id); console.assert(edited.duration === 2, 'duration should be 2'); console.assert(edited.position === 0.9, 'position should be 0.4 + 0.5'); console.assert(after.animations.length === before.animations.length, 'animation count unchanged'); // 4) 脚本级校验 const check = validateCompositionGsap(next); console.assert(check.valid, check.errors.join('; '));

验证逻辑逐条说明:

  • 再解析 diffid基于内容稳定,shiftPositionsInScript只重写目标 selector 的位置参数,所以“同 id + 字段已更新 + 总数不变”三者同时成立,才说明改到了正确的那条动画且没伤及其他条目;
  • no-op 检查:如果next === script,说明改写完全没有发生——脚本没解析成功或 id 不匹配,回到解析结果里核对;
  • 脚本级校验validateCompositionGsap返回validerrors;也可以用getAnimationsForElementId拉取某元素的全部动画做交叉核对;
  • 仓库自身用 golden 文件锁定这套往返契约:goldens目录 提供 minimal、moderate、complex、fromto 四组.parsed.json/.serialized.js样本对,gsapParser.golden.test.ts 用它们断言解析与序列化结果,gsapWriter.parity.test.ts 交叉核对 acorn 与 recast 两种写实现的输出。如果你的批量改写工具需要回归保障,可以照这个思路为自己的一组输入/输出样本建 golden 断言。

边界与不支持项

  • 不要使用 recast 路径@hyperframes/parsers/gsap-parser-recast是遗留实现,源码注释明确读路径已切换到parseGsapScriptAcorn、写路径切换到 acorn writer;新代码一律用gsap-parser-acorn/gsap-writer-acorn
  • asset-pathssubpath 仅限 Node@hyperframes/parsers/asset-paths提供的资产路径改写助手是 Node-only 的;主入口与 acorn 解析/写路径是 browser-safe。工具若要在浏览器运行,不要引入该 subpath。
  • 单一时间线形态:编辑前确认ParsedGsapmultipleTimelines/unsupportedTimelinePattern均未置位,否则不要对该脚本走 writer 路径。
  • 与 core 的 API 不重复引入:如果你的工具已经依赖@hyperframes/core,直接使用它重新导出的解析器 API 即可;@hyperframes/lint@hyperframes/studio-server同样构建在 parsers 之上,写校验或预览类工具时可以先看这两个包的消费方式。

下一步

  • 本包的完整入口清单与使用提示见 parsers 包文档;
  • 改写后的脚本要过 lint 的话,见 lint 包文档;
  • 元素身份打戳的规则见 hfIds.ts;HTML 解析与校验的实现见 htmlParser.ts。

【免费下载链接】hyperframesWrite HTML. Render video. Built for agents.项目地址: https://gitcode.com/GitHub_Trending/hy/hyperframes

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

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/11 13:33:39

数字化时代工作家庭平衡:工具精简与时间管理策略

1. 项目背景与现象解析这个看似荒诞的标题实际上反映了一个普遍存在的社会现象——现代人在工作与家庭之间的平衡困境。标题中"据说用好的可敌国"暗示某种被宣传为高效生产力工具或方法,而"被媳妇赶下床"则直指过度投入工作导致的家庭矛盾。这种…

作者头像 李华
网站建设 2026/9/11 13:33:10

嵌入式系统解耦哲学:从数据流架构到消息队列的实践指南

做嵌入式开发的时间越久,我越发现一个规律:真正让人头疼的往往不是算法有多难、芯片有多复杂,而是代码本身慢慢变成一团乱麻。刚接手一个项目时看着还挺清爽——三个模块、两个中断、一个超级循环。半年之后再去看,全局变量满世界…

作者头像 李华
网站建设 2026/9/11 13:29:46

2026年重庆路沿线实测正宗十堰重庆火锅

一、十堰重庆路沿线的重庆火锅选择多吗?十堰重庆路沿线目前聚集了5个不同定位的重庆火锅品牌,选择覆盖不同消费场景和口味偏好。2025年10月新开的遇南三十堰卢浮宫店就位于重庆路88号,是该区域首个主打手工炒料的直营重庆火锅品牌&#xff0c…

作者头像 李华
网站建设 2026/9/11 13:29:24

3行代码跑通Vosk离线语音识别:零基础从安装到出字幕完整攻略

3行代码跑通Vosk离线语音识别:零基础从安装到出字幕完整攻略 【免费下载链接】vosk-api Offline speech recognition API for Android, iOS, Raspberry Pi and servers with Python, Java, C# and Node 项目地址: https://gitcode.com/GitHub_Trending/vo/vosk-ap…

作者头像 李华
网站建设 2026/9/11 13:27:46

RustFS AWS IAM 策略变量(Policy Variables)端到端测试全解析

RustFS AWS IAM 策略变量(Policy Variables)端到端测试全解析 【免费下载链接】rustfs 🚀2.3x faster than MinIO for 4KB object payloads. RustFS is an open-source, S3-compatible high-performance object storage system supporting mi…

作者头像 李华