Diorama:一个会动的小世界
如需详细的叙事微缩场景或像素级完成的场景,请使用单独的 diorama-worlds 技能。此技能保留为无依赖的 SVG 插图工作流。
你正在制作一个 diorama:一个由圆角底座上的积木构成的场景,从等距相机角度观看,包含一些自动移动并能响应指针的元素,以某种主题绘制。默认主题 toy 包含柔和积木、一轮温暖太阳、柔和阴影,每个物体由少量方块和圆柱组成,无纹理,令人愉悦且一目了然。同一场景文件在 soft(奶油色,更柔和)、mono(单一强调色,其余为墨色)、line(细线绘制:白色填充,墨色边缘,无阴影)、print(每个面平涂色调加墨线,类似印刷插图)和 night(黄昏,亮灯)主题下均可绘制,且每个主题字段都可覆盖:wobble 以手绘方式绘制墨线,halo 是底座投射到页面上的柔和阴影,accent 是每个主题中主体的颜色。examples/ 中的十八个示例是标准,其格式即为你需遵循的格式。
你只需编写一个东西:figure,即一个 .js 文件。内核 (kit/diorama.js) 和页面 (bench.html) 是固定的。永远不要编辑它们,不要粘贴更改后的副本,也不要重复编写内核已提供的功能(相机、灯光、阴影、一个在屏幕外休眠的循环、指针、弹簧、路径、道具)。
下面提到的每个文件都位于此技能的文件夹中。figure 和页面编写在用户的工作目录中;从此文件夹的路径运行 scripts/build.mjs、scripts/validate.mjs 和 scripts/look.mjs。
1. 尺寸与概念
首先确定尺寸,因为它设定了预算:
| 尺寸 | 使用场景 | 纵横比 | 图元数量 | 代码行数 | 运动 |
|---|---|---|---|---|---|
hero | 登录页顶部、README、产品导览 | 3:2,全宽 | 150–400 | ≤ 420 | 三到四个独立循环,总有物体在动 |
card | 文档页、博客文章、特性区域 | 4:3,约500px | 60–150 | ≤ 240 | 一到两个循环及指针响应 |
mini | 任何小空间:空状态、列表中的特性、404、设置区域 | 1:1,160–240px | 15–40 | ≤ 150 | 一种安静的环境运动 |
然后阅读 references/concepts.md,并用一行写出概念,主题放在末尾:
名称. 地点及其中的事物。自动移动的内容。指针的作用。(对于mini:缺少什么。)主题。
从产品本身的世界中选取地点,而不是从示例中:港口、厨房、屋顶、书桌、温室、站台、发射台、街角都是 diorama。然后根据 figure 的所在位置选取主题,而非个人喜好:toy 用于需要温暖和色彩的产品页面;soft 用于编辑页面或奶油色页面;mono 用于拥有自己强调色(可将其作为 accent 传递)的产品 UI、文档或紧邻密集文本的任何内容;line 用于极简或技术性页面、深色文字配白色背景的品牌,或应保持低调的空状态;night 用于深色页面。如果用户有品牌色,则将其作为 accent 传入,该颜色会在每个主题中重新着色主体;第二个颜色通过 palette 传入。references/craft.md 包含每个主题添加的规则。
然后在任何代码之前编写部件列表:五行,每组一行,命名每个物体及其两到三个特征。地面(底座、区域、道路、水面),结构(竖立的物体:建筑、架子、树木),移动体(行驶、行走、转动或升降的物体),家具(填充边缘的物体:灯、锥桶、板条箱、长凳、栅栏),灯光(发光体:窗户、灯、信标)。每个部件都放置在其他部件上;没有悬浮物体。一个 hero 列表包含多达三十个物体,card 为十个,mini 为三个。该列表即是场景:如果读起来单薄,figure 也会如此。
如果有人可问,提供两到三个选项并等待。如果无人可问,选择静止帧最强的那个,并说明你选取了哪个。一个 diorama,一个地点,一个故事。需要标题才能被理解的场景尚不是一个概念。
2. 构建
- 阅读
references/craft.md。十条规则是让 diorama 看起来像一个地方而非一堆立方体的关键;违反任何一条的 figure 都未完成。 - 阅读
kit/diorama.js顶部的索引(var Diorama上方的注释)。它列出了你可以调用的所有内容。请勿阅读其下的代码。 - 阅读最接近你尺寸和主题的示例:
examples/town.js(hero, toy:一个由小型辅助函数构建的完整城镇,多个循环,循环灯光)、examples/island.js(hero, toy:一个有水的地方,带有停靠点的循环,海鸥和云朵)、examples/farm.js(hero, toy:田野、动物,带有停靠点的循环) 或examples/warehouse.js(hero, toy:一个带有班车和码头的院子) 或examples/harbour.js(hero, toy:一个码头,有起重机以18秒周期吊运集装箱,带有停靠点和可升降栏杆的拖拉机循环,拖船,灯塔,点击启动升降,读条);examples/cafe.js(card, soft:人物和小物件)、examples/kitchen.js(card, toy:一个带有后墙和窗户的房间,冰箱作为壳体构建,门在悬停时打开,烤面包机在点击时弹出,时钟)、examples/launchpad.js(card, night:灯光、罕见事件、在指针下拆解的火箭、点击发射的火箭、倒计时读条)、examples/server.js(card, mono with accent:节奏性灯光,悬停时整组亮起) 或examples/rack.js(card, mono with one accent:一个带有指针响应的空状态) 或examples/assembly.js(card, mono with one accent:一条通过闸门的任务传送带,点击可暂停,冲压臂,计数读条);examples/mailbox.js(mini, toy)、examples/seedling.js(mini, line:用墨绘制的空状态)、examples/observatory.js(mini, line with wobble:旋转的穹顶和观察的望远镜)、examples/desk.js(mini, line with an accent) 或examples/vending.js(mini, print:一个框架式机器,内置凹槽,裸露线圈,风扇)。这些示例并非都是工厂,这是有意为之:diorama 可以是任何地方。 - 在工作目录中编写 figure 为
<name>.js,格式与示例一致:一个Diorama.figure({ … })调用,包含:name: 小写,单个单词或连字符;size:hero|card|mini。theme: 一个名称,或{ base: 'mono', accent: '#f2c230', shadows: false, … }以覆盖任何字段(palette, accent, lights, shadows, finish, tone, tint, edges, outline, stroke, strokeWidth, round, glow, halo, wobble)。仅构建一次;主题由内核应用于每个图元,因此 figure 永远不会基于主题进行分支。使用pal.accent绘制重要的事物:它是每个主题中主体的颜色,是mono和line保留的颜色,是品牌十六进制颜色替换的颜色,也是工作台色板实时重新着色的颜色。对于手绘的linefigure,可添加wobble: 1.4(震颤的 CSS 像素:1.2 为钢笔,2.5 为毡尖笔)。means: 一句话,≤ 140 字符,显示在舞台下方。说明场景展示的内容和移动的对象。hint: 一行,≤ 90 字符,说明指针的操作和点击的操作(“悬停在火箭上查看其级数;点击控制室立即发射”)。页面会显示此提示,让访客知道可以互动,而look.mjs会按下它命名的内容。title: figure 的名称,页面会显示在舞台上方。用户自己的 UI 中围绕 figure 的词语(标题、行、按钮)由他们自己编写;场景本身不携带任何文字。camera:{ az, el, pad },以度为单位。az45 是等距默认值;30–40 在 3:2 hero 中显示宽阔底座。除非场景较高,否则将el保持为 35.264。still: 循环中用于减少动效和缩略图的秒数。选择一个所有物体都处于有意义位置的时刻(卡车在码头,叉车抬起)。build({ D, pal }): 使用工具包构建场景一次,并返回parts:tick将移动的每个组和网格,以及parts.hot,指针可能选择的组。为每个热区组赋予一个.name(boat.name = 'boat'):页面会在指针悬停时将其显示为读条。tick(ctx): 每一帧,使用D.phase、D.pulse、D.wave、D.inOut、D.path和D.spring根据ctx.t定位部件。使用ctx.hover响应指针,使用ctx.pressed或ctx.active响应点击(点击是手机端的响应方式,因此 hero 或 card 应有一个可点击的对象:开关、呼叫,或悬停的孪生操作)。D.apart在指针下拆解物体并将其还原。将ctx.read设置为一个简短的行,说明场景当前正在做什么('truck · at the dock'):页面会将其显示为读条,并且这是访客了解场景是一个工作模型而非循环的方式。references/motion.md包含相关模式。
- 组装:
node <skill>/scripts/build.mjs <name>.js写入diorama-<name>.html:包含工作台、内核和 figure。内核以插画师模拟深度的方式绘制 SVG(等距投影,每个面一种平涂色调,从场景后到前绘制部件,将阴影作为形状投射在地面上),因此页面无需渲染器、无需 WebGL 且不发送网络请求;绘图是矢量图,在任何尺寸下都清晰,并且页面可以为其设置样式。
3. 检查
node <skill>/scripts/look.mjs <name>.js。它构建页面、验证页面、在无头 Chromium 中打开页面,并写入diorama-<name>-look.png:静止帧、循环的三个时刻、指针响应、相机倾斜、点击、缩略图和整个页面。它输出控制台、填充、运动、响应和点击的数字。修复每个FAIL,并将SMALL、STILL、FAINT和MUTE行视为问题,直到它以 0 退出。点击会落在提示中“tap”、“click”或“press”之后命名的热区组(或使用--press name,或--press x,y);MUTE表示提示承诺了点击但没有任何变化。添加--theme line(或任何名称)以查看同一 figure 在其他主题中的效果;在toy和line中都能良好呈现的 figure 才是正确构建的。它还会输出clash:穿过其他部件的运动部件;车辆穿过树木或汽车穿过公交车是需要修复的错误,而叉车在货架上或旗帜在其铰链上则是正常的。- 阅读
references/look.md,然后阅读表单,并回答其问题。修复失败项,然后返回步骤 1。
look.mjs 需要浏览器;它会查找 Playwright 的浏览器,或者在此文件夹外部安装一次 playwright-core。如果没有浏览器,则在每次构建后运行 node <skill>/scripts/validate.mjs diorama-<name>.html,并按照 look.md 中“Without a browser”的描述进行查看。不要移交验证器拒绝的页面。如果你没有查看过,就不要说查看已完成。
4. 移交
按照用户的要求移交 diorama-<name>.html:当用户需要文件时,给出文件并说明其位置;否则,如果可能,将其发布为制品。然后,每行一项说明:
- 概念:地点、移动内容、指针和点击的作用,以及主题(以及如何切换:在 figure 中使用
theme:,在页面中使用?theme=和?accent=,在Diorama.mount中使用{ theme };工作台的色板可以实时重新着色主体); - 静止帧:哪个秒数,以及为什么选择那个时刻;
- 适用位置(hero、card 或 mini)以及如何嵌入(
references/react.md用于 React 或 Next.js;其他地方使用一个包含内核和 figure 的普通<script>,加上Diorama.mount(fig, { el })); - 任何你无法验证的内容(没有 Node、没有浏览器),如实说明。
5. 调整
当用户请求更改时,仅编辑 <name>.js,然后再次运行 look.mjs 并阅读新的表单。第十个版本与第一个版本遵循相同的标准。
常见问题
| 如果你发现自己这么做 | 请改用以下方式 |
|---|---|
| 使用 three.js、灯光、相机、画布或 WebGL | 不需要这些:内核绘制 SVG;使用 D.box、D.cyl、D.props.*、o.M 处理矢量,以及 ctx |
编写 requestAnimationFrame、setInterval 或 CSS 动画 | 通过 tick 中的 ctx.t 对所有部件进行定位(规则 07) |
使用 position.x += speed * dt 移动物体 | 将其放在 D.path 上,并通过 D.phase(t, period) 读取其距离,以确保循环无缝且静止帧可重现(规则 08) |
| 在场景中编写文字、数字或箭头 | 通过标题或 means 来表达;场景中不携带任何文字,连标志都没有(规则 10) |
| 使用纹理、渐变、发光或第 15 种颜色 | 使用调色板颜色再添加一个方块;pal.* 已提供所需内容(规则 03 和 04) |
在 figure 中编写 if (theme === 'line') | 内核根据主题对每个图元进行色调、描边和光照处理;figure 在所有五种主题中都是相同的 |
| 将品牌颜色粘贴到二十个部件上,或使用调色板名称绘制主体 | 使用 pal.accent 绘制主体,并在声明中一次性将品牌十六进制颜色放入 accent;第二个颜色使用 palette: { cobalt: … } |
| 只有悬停响应而没有手指操作 | 为一个对象设置点击响应(ctx.pressed、ctx.active):悬停预览,点击确认(motion.md 中的“The press”) |
一个 line figure 读起来像 CAD 导出文件 | 在主题中添加 wobble: 1.4:墨线会像手绘一样弯曲和越过 |
因为底座“看起来脏”而关闭 halo | halo 是将 diorama 放置在页面上的关键;在繁忙的背景下降低它(.1),但保留它 |
| 读条显示的内容与标题相同 | ctx.read 是用文字表示的时钟:相位、停止、计数,来自与定位部件相同的 T |
| 在场景之后构建底座,或根本不构建 | 首先使用 D.slab:阴影需要落脚点(规则 01) |
| 场景读起来像一堆相同的立方体网格 | 为每个对象赋予其独有的两到三个特征(规则 05) |
| 一个包含八个物体的 mini | 一个对象,一种环境运动(concepts.md 中的“A mini”) |
| 一个 hero 中所有物体都以相同节奏移动 | 三个具有不同周期和相位偏移的循环;motion.md 中的“Loops” |
| 一个拥有15种颜色的场景 | palette 保持10种颜色;通过色调和明度扩展它们(规则 03) |
| 在代码中使用 CSS 选择器或 DOM API | D 工具包是唯一允许的 API;不要触摸 DOM |
编写 console.log 或 alert | 将状态写入 ctx.read 或通过 D.log 写入控制台 |
| 在 figure 中导入其他文件 | figure 是独立的;所有辅助函数都在 D.props 中 |
| 将像素值硬编码到主题中 | 对需要它的图元使用 pal.* 属性,并在主题定义中覆盖它们 |