iso:动画等距解释器
一个场景用一段 6 到 14 秒的循环讲述一个产品故事:物体到达,光线在它们之间传播,每次只有一个部分亮起,循环回到第一帧。一个流程只有一种形状。从节拍表中选择故事所需的形状:一个打开以显示其层的盒子,两个对象之间的交接,一个逐渐填充的网格,一个旋转选择的堆栈,或者用户通过悬停来抬起的瓦片。一切都是真实的 SVG 和 HTML 文本,随其容器缩放,在屏幕外暂停,并为减少动效设置静止。
该工具包与此文件位于同一目录:
lib/iso.ts:Scene构建器(投影、板块、面、连线、脉冲、标签、运动轨迹),scene.exportSvg(),responsive()和lazyBox()。lib/runtime.ts:使用 WAAPI 播放渲染的场景,当 40%(或对于高场景,半个屏幕高度)进入视口时开始其介绍动画,在屏幕外暂停,当它离开超过半个屏幕时丢弃其动画,并在返回前将其恢复到同一帧,为减少动效设置静止,暴露window.isoSeek(t)。scripts/render.ts:将一个场景模块(默认导出Page)渲染为一个独立的 HTML 文件,并内联运行时。scripts/shots.mjs:在选定时间和宽度下截取任何页面的屏幕截图,并写入sheet.png。scripts/check.ts:对磁盘上任何位置的场景文件针对工具包进行严格的类型检查(bun scripts/check.ts scene.ts)。scripts/page-idle.mjs:一次性滚动包含多个场景的页面,然后在 0%、20%、35%、50%、60% 和 80% 处停止,并在 CPU 为 1x 和 4x 时打印主线程时间(node scripts/page-idle.mjs page=<url> width=393 dpr=3 scroll=4)。在任何包含多于几个场景的页面上运行它。scripts/perf.mjs:在 Chrome 中以手机宽度并控制 CPU 限制播放一个页面(node scripts/perf.mjs page=<out.html> width=393 throttle=4 seconds=20),并打印帧率、帧时间百分位数、延迟帧、长任务和主线程时间。在任何包含 30 个或更多运动对象的场景上运行它。scripts/contrast.mjs,scripts/render-all.ts:在静止帧以及另外四个时刻,测量渲染页面中每个文本与其字形后方像素的对比度,低于 4.5:1 则失败(node scripts/contrast.mjs <out.html>;bun scripts/render-all.ts <dir>首先写入所有目录示例)。在每个场景上运行它,然后才能称其为完成。scripts/study-rive.mjs,scripts/compare-rive.mjs:可选工具,用于逐帧研究您自己的.riv动画,并将其与工具包场景进行比较。两者都需要runtime=<folder with canvas_advanced.mjs and rive.wasm>。examples/:64 个虚构产品的场景,每个文件导出example(标题、故事、提示、级别、类别、布局、交互和art:来自art(compact, wide, breakpoint)的紧凑和宽屏场景)以及默认的Page,因此scripts/render.ts可以渲染其中任何一个。一个spot场景是一个单独的小场景,art(compact)。hero.ts导出hero(一个Art),material-gallery.ts导出galleries(每个饰面系列一个Art),每个都有默认的Page。examples/shared.ts包含表面、颜色渐变、paints、字体和fitX(),它保持标签在其将放置的最窄卡片内。
在开始绘制之前,选择最接近您故事的示例并阅读它:
| 示例 | 级别 | 展示内容 |
|---|---|---|
notifications.ts | 简单 | 最小文件:一个核心扇出到三个瓦片,脉冲,到达闪光和跳跃。请先阅读它 |
ci-deploy.ts | 简单 | 一个包裹沿链条跳跃,落地时压缩,仅在末尾显示状态颜色 |
file-upload.ts | 简单 | 块填充进度条,然后副本扩展到边缘点;空槽上显示行进虚线 |
db-branching.ts | 简单 | 虚线幽灵变为实线,一个变化瓦片滑回并合并 |
service-map.ts | 简单 | 悬停:九个包含 { hover } 的 slab() 瓦片,悬停或点击时显示标签,同时循环仍讲述故事 |
auth-flow.ts | 中等 | 一个直立的 panel() 电话,文本输入,携带令牌的交接幽灵 |
etl-pipeline.ts | 中等 | 源瓦片沿着连线馈送核心,连线随之绘制,然后是一个填充和清空网格;覆盖层与其所在的板块分组,以便一起移动 |
queue-workers.ts | 中等 | 由事件表驱动的槽队列,构建验证该表(无溢出,不从空队列取出) |
search-index.ts | 中等 | 浅色页面上的线条艺术瓦片,键入的查询,匹配项高亮,排名结果上升 |
feature-flags.ts | 中等 | 播放:标签中的真实 <button> 和内联脚本,通过 seek 重放一个循环步骤 |
agent-tools.ts | 复杂 | 具有卫星的核心,呼叫和回复脉冲,一个输入答案的卡片 |
checkout-payment.ts | 复杂 | 带虚线返回路径的步骤环,以及一个交叉淡入淡出的状态行 |
edge-cache.ts | 复杂 | 相同请求两次:原始服务器上的冷未命中,然后边缘上的热命中 |
observability.ts | 复杂 | 三种信号样式(虚线、点、跨度),带有 heightTo 的 prism 柱状图,警报和恢复 |
keycard-slot.ts, battery-charge.ts, tile-loader.ts, empty-box.ts | 点状 | 用于功能列表、加载器或空状态的一个小对象;在插槽边缘裁剪的直立 panel() 卡片,heightTo 单元格,对角线下降波,行进虚线 |
sync-loop.ts, inbox-fill.ts, alert-stack.ts, table-scan.ts | 点状 | 压缩到平面上的箭头,玻璃面板托盘,滑到堆栈上的卡片,带有计数并骑在顶部卡片上的徽章,逐行滑动的镜头 |
audit-ledger.ts, dns-resolve.ts, cron-jobs.ts | 简单 | 带封条的堆叠页面,一次跳跃并回复,触发作业的拨盘 |
vault-rotate.ts, firewall-filter.ts, zero-trust.ts | 中等 | 使用槽模式交换密钥,被拒绝的数据包弹回,每个请求都加盖戳记 |
workload-scheduler.ts, autoscale.ts, health-check.ts | 中等 | 从小规则表进行放置,为新节点腾出空间,拉出并重新加入的节点 |
blue-green.ts, snapshot-restore.ts | 中等 | 重新定向的连线,沿时间线向后滑动的光标 |
embedding-clusters.ts, training-loss.ts, recs-rerank.ts, ab-test.ts | 中等 | 点聚集成簇,下降的 prism 柱状图,重新排序的槽架,带有赢家的拆分 |
data-erasure.ts, rag-answer.ts | 复杂 | 一个请求扇出到六个系统并带有计数器,检索到的卡片飞入模型,答案输入 |
hero.ts | 复杂 | 一个提示按 1、5、15 和 40 的层级增长系统:转盘旋转和加权下降,输出从下方滑出,弹出,带有网格填充的池子,无尽请求,静止时朝鼠标倾斜;通过 fill 和使用 <use> 的重复瓦片保持成本低廉;仅使用 SVG text(),因此 exportSvg() 可在其上工作 |
funnel-live.ts, billing-cycle.ts, parcel-delivery.ts, test-shards.ts | 中等,复杂 | layout: 'wide':从左到右阅读的 3:1 条带(逐渐变窄的阶段,无跳跃变化的月份条带,运送盒子的车辆,在车道中竞赛并合并的分片) |
kanban-move.ts, stock-sync.ts, diff-merge.ts, refund-flow.ts | 简单到复杂 | layout: 'diagonal':沿一条等距轴移动的卡片或令牌,变为绿色的门,以第二种颜色沿着相同路径返回的退款 |
live-cursors.ts, two-approvals.ts, design-comments.ts, translate-six.ts | 简单,中等 | layout: 'panels':彼此面对的直立 panel();输入的光标,解锁锁的两个勾选标记,图钉和线程,六种语言的一条消息,带有泰语和日文字体堆栈 |
cms-publish.ts, sensor-gateway.ts, smart-home.ts, currency-convert.ts | 简单,中等 | layout: 'radial':一个副本到五个通道,读数输入然后地板波输出,设备依次改变状态,一个脉冲沿环线绕行一次 |
ota-rollout.ts, build-cache.ts, calendar-booking.ts, load-slider.ts | 简单到复杂 | layout: 'field':跨设备场的波浪,带有停止和重试,从依赖关系表计算缓存命中,跳过冲突的会议;load-slider 是一个拖动控件(真实的 range 输入,支持鼠标、触摸和按键),在环境流量保持移动的同时寻找循环 |
video-transcode.ts, image-resize.ts, fraud-gauge.ts, data-lineage.ts, drill-down.ts | 简单到复杂 | layout: 'exploded':向上分离并进行编码的演绎版,剥离的大小,超越警戒线的累积风险层,通过层向下照亮的上下游路径;drill-down 通过真实按钮,通过寻找该部分的循环段,将一部分打开为其层 |
materials-*.ts, material-gallery.ts | 六个系列(金属、玻璃、天然、科技图案、复古、生成式)的 49 种侧面饰面,作为 (scene) => Material 工厂,每个系列一个无标签的画廊。生成式饰面接受种子。复制一个饰面而不是发明一个 |
一次性设置:cd <此目录>/scripts && bun install && bunx playwright install webkit(截图使用已安装的 Google Chrome for Chromium)。类型检查:scripts/node_modules/.bin/tsc -p <此目录>。
1. 在绘制前进行故事板
首先打开 recipes.md。它包含所有测量的运动和构图模式(到达、连接、流、比较、选择、空闲、交互、缓动、表面),每个都带有其数字和工具包调用。从这些配方中构建故事,并保持它们的时序。
首先将故事编写为一个时间线表格:观众应该理解什么,展示它的 3 到 6 个节拍,以及对于每个节拍,哪个元素移动、如何移动、何时移动以及移动多长时间。每个节拍一个想法。需要一个段落来解释的节拍是两个节拍或错误的图片。
经过验证的节拍形状,带有测量的时序:
| 节拍 | 发生了什么 | 时序 |
|---|---|---|
| 到达 | 碎片一个接一个地掉入,稍微过冲,然后稳定 | dropIn:每个 0.55 秒,83 毫秒交错,46 像素下落 |
| 交接 | 瓦片的幽灵轮廓从一个对象滑到另一个对象 | 4 个幽灵,间隔 0.22 秒,每个 1.2 秒,ease.io |
| 旅行 | 一个短光脉冲沿连线运行;目标在到达时闪烁 | 270 像素/秒,脉冲约为连线的 0.12 到 0.16,上升 0.1 秒,淡出 0.6 秒 |
| 揭示层 | 一个实心盒子变为透明;内层一次亮一个并带有标签 | 开启 0.23 秒,保持约 1 秒,关闭 0.2 秒,下一个在前一个结束时开始 |
| 旋转选择 | 一个堆栈向上移动一个槽位;活动卡片变为强调色 | 每 3 秒移动 0.9 秒,慢速启动,长时间缓出 |
| 填充和清空 | 一个网格逐个瓦片填充,保持,然后清空回空 | 7 帧(0.12 秒)间隔进入,2 帧间隔退出 |
| 牵引 | 当连线缩短或伸长时,它所连接的盒子会沿连线方向被拉动约 6 像素,保持,然后缓回,使连线看起来有张力 | 拉动 0.23 秒,保持 0.25 秒,返回 0.65 秒,带有长时间缓出 |
| 悬停提升 | 瓦片上升 16 像素并变为其品牌颜色,其名称出现 | slab({ hover }):270 毫秒开启,325 毫秒关闭,ease.out |
连接规则:只有当连线的两端都在屏幕上时,连线才显示。连接到在循环中间到达的对象的连线会随其脉冲一起绘制自身(wire({ dash: '1 1' }),然后 at(id, 'strokeDashoffset', t, d, 1, 0, ease.linear)),并随其加入的对象一起离开。永远不要将这样的连线放在介绍动画中。连线永远不会穿过盒子的顶面:在它连接的对象之前添加它(以便它们绘制在它之上),并从盒子的边缘而不是中心开始。
循环规则:最后一帧等于第一帧。任何在循环期间发生变化的事物都必须在循环结束前变回原样。仅介绍动画的运动放在 intro() 中;循环在介绍动画结束时开始,并且介绍动画必须结束在与循环开始的相同画面中。没有介绍动画的元素会在介绍动画期间显示循环的起始值,即其循环段的最后一个 to。
绘制顺序是固定的:稍后添加的内容总是覆盖较早添加的内容。会改变谁覆盖谁的运动(例如,一张卡片从堆栈顶部移动到底部)将绘制为槽位。每个元素是一个移动一步的槽,并像其邻居一样淡出或重新着色,并且每个段都从槽的静止值重新开始,因此所有槽一起跳回,同时画面看起来相同。为每个槽位提供一个选项的标签,并交叉淡入淡出哪一标签显示在活动槽旁边。
2. 选择布局,然后首先为手机进行构图
每个示例在其 layout 中命名其构图:wide(一个从左到右阅读的长条;其宽屏场景约为 3:1,例如 1200x400,断点为 720,展示区将其显示为全宽卡片),diagonal(沿等距轴的长行),panels(彼此面对的直立 panel()),radial(一个核心,周围环绕着环上的物体),field(从高处看到的许多瓦片;稍陡的 ratio 有帮助),exploded(垂直向上分离的层)或 object(用于功能列表或空状态的一两个对象)。在绘制之前选择故事所需的布局;当场景变化时,页面阅读效果更好。
一个观众可以播放的场景(interaction: 'play' | 'drag' | 'explore')在 art() 的 head 中提供一个小型内联模块脚本:label({ html }) 中的真实 <button> 或 `<input type=