swarm-build 是做什么的?
swarm-build 是一个无依赖的 ES 模块(src/swarm-build.js,约 600 行)及一个可选的 CSS 文件(src/swarm-build.css)。
它有两种模式:
- 图像模式
swarmBuild(img):一个空白画布覆盖<img>(或带有 CSSbackground-image的元素),每个区域在光标拖出其方框并带有实时宽 × 高徽章后扫入。 - 页面模式
swarmBuildPage("[data-swarm-block]"):实时页面中标记的元素消失,然后光标逐个重建它们。纯文本块(标题、短段落)会以打字效果呈现。嵌套在其他块内的块会等待其父块完成。折叠线以下的块会等到它们滚动到视口后再构建,因此群体跟随读者向下滚动。
两种模式中,光标最后都会离开,并且库所做的所有更改都会被撤销。
1. 安装
将 src/swarm-build.js、src/swarm-build.d.ts 和 src/swarm-build.css 从本仓库复制到项目中(例如 lib/swarm-build/),并使用相对路径导入它们。
没有依赖项,也不需要构建步骤。
仅当 npm view swarm-build 显示该包已发布时:npm install swarm-build,然后 import { swarmBuild } from "swarm-build" 和 import "swarm-build/style.css"。
2a. 页面模式:标记块
- 阅读页面标记,挑选设计师会绘制的块:导航栏、英雄标题、英雄段落、每个按钮、每个卡片、图表、表格、页脚。一个典型的落地页有 25 到 60 个。
- 为每个块添加
data-swarm-block。- 标记容器(如一个卡片)及其内部的文本(其标题和段落),以实现“先框架,再填充”。
- 标记内容为纯文本的标题和短段落:这些会以打字效果出现。包含内联标记(链接、
<b>)的元素则会扫入。 - 跳过仅用于布局的包装器(如网格、没有自己背景的区域)。隐藏它们会同时隐藏内部所有内容。
- 不要标记访客需要立即使用的固定或粘性 UI,例如 Cookie 横幅或聊天小部件。
- 在内容进入 DOM 后调用:
import { swarmBuildPage } from "./lib/swarm-build/swarm-build.js";
swarmBuildPage("[data-swarm-block]", { speed: 1.5 });
为了在脚本运行前隐藏块(避免完成页面的闪现),还需为每个块添加 data-swarm-pending 并包含 CSS 文件。
2b. 图像模式:选择区域
区域决定构建什么以及按什么顺序构建。 好的区域能让动画看起来有意为之,所以不要跳过此步骤。
- 打开图像并观察它(读取图像文件,或截取页面截图)。
- 列出设计师会绘制的 6 到 15 个有意义的块:导航栏、页眉、搜索栏、卡片、图表、列表、页脚。
- 将每个块写为图像宽度和高度的百分比形式
x y w h(0 到 100)。根据像素尺寸测量:x = left / imageWidth * 100。 - 按人们构建屏幕的顺序排列:先 chrome(导航栏、页眉),然后从上到下构建内容。
- 留出小间隙。任何区域未覆盖的内容会在最后画布淡出时出现,因此区域不需要覆盖每个像素。
将它们作为 data-swarm-regions="0 0 18 100, 21 3.5 47 5.5, ..." 放在图像上,或在 JavaScript 中传递 regions: [{ x, y, w, h }]。
没有区域时,库会使用通用的侧边栏、页眉和卡片布局,这通常与图像不匹配。
3. 接入
纯 HTML:
<link rel="stylesheet" href="/lib/swarm-build/swarm-build.css" />
<img src="/shot.png" alt="..." width="1200" height="800"
data-swarm-pending
data-swarm-regions="0 0 18 100, 21 3.5 47 5.5, 21 13 24 16" />
<script type="module">
import { swarmBuild } from "/lib/swarm-build/swarm-build.js";
swarmBuild(document.querySelector("img[data-swarm-regions]"));
</script>
React 或 Next.js(客户端组件):
"use client";
import { useEffect, useRef } from "react";
import { swarmBuild } from "@/lib/swarm-build/swarm-build.js";
export function BuiltImage(props: React.ComponentProps<"img"> & { regions: string }) {
const ref = useRef<HTMLImageElement>(null);
useEffect(() => {
const swarm = swarmBuild(ref.current!);
return () => swarm.destroy();
}, []);
const { regions, ...img } = props;
return <img ref={ref} {...img} data-swarm-pending="" data-swarm-regions={regions} />;
}
对于 Vue、Svelte 或 Astro,做法相同:挂载后调用 swarmBuild(el),卸载时调用 destroy()。
多个图像(例如 iPad 和手机)可以共享一个群体:swarmBuild([ipadImg, phoneImg])。
4. 选项
| 选项 | 默认值 | 用途 |
|---|---|---|
regions (每个目标) | data-swarm-regions,然后通用 | 要构建的内容及顺序 |
agents | 6 个内置名称和颜色 | [{ name, color, text }],匹配网站配色和受众 |
speed | 1 | 2 将时长减半(对于 10 到 15 个区域,1 时约为 6 到 8 秒) |
start | "visible" | "now" 立即开始,而不是等到图像滚动到视口 |
canvas | "#fff" | 未构建画布的颜色;匹配截图的背景色 |
zIndex | 2147483000 | 固定光标层的层叠值;如果模态框或页眉必须保持在上方,请降低此值 |
onDone | 无 | 构建完成或调用 finish() 时调用一次 |
typeSpeed (页面模式) | 1 | 打字文本的速度;2 打字速度加倍 |
返回值包含 finished、finish() 和 destroy():
finished是一个始终解决的 Promise,无论群体如何结束。finish()跳转到完成的图像,并运行onDone。destroy()立即移除所有内容,且onDone不会运行。在卸载时调用。setSpeed(n)在运行时更改速度。
在页面模式下,start 默认值为 "now";在图像模式下为 "visible"。
5. 需遵守的规则
data-swarm-pending加上 CSS 文件可以在脚本启动前保持图像隐藏,因此不会先闪现完成状态。如果脚本从未运行,CSS 会在 4 秒后显示图像。当图像是页面的 LCP 元素且加载时间比效果更重要时,请省略该属性。- 减少运动(
prefers-reduced-motion: reduce)会显示完成的图像并且从不动画。请勿覆盖此设置。 - 光标位于页面之上的固定、全视口层中(
pointer-events: none)。 - 运行时,库会在图像之后、图像父元素内部插入一个覆盖层。如果父元素是静态定位的,它还会在其上设置
position: relative。两者在结束时都会被撤销。- 为图像提供一个独立的块级父元素,例如
<div>或<figure>。内联父元素(如<a>或<picture>)可能导致覆盖层位置错乱。 - 同级 CSS(如
:last-child或 Tailwind 的space-y-*)在覆盖层存在时可能会短暂改变。
- 为图像提供一个独立的块级父元素,例如
- 图像会作为 CSS 背景图复制,拉伸到图像的内容盒。图像的边框和内边距没问题,但
object-fit: cover或contain的裁剪不会被重现。 - 在严格的 CSP 或 Trusted Types 下无需更改:库通过 DOM 调用构建每个元素,通过 CSSOM 设置样式,不使用
innerHTML、<style>或内联脚本。图像 URL 必须被img-src允许。
6. 验证
- 加载页面并观察一次完整的构建。每个方框应落在图像的真正 UI 块上,而不是空白区域或跨越两个块。
- 检查手机宽度(375px)。徽章和名称标签是固定大小,因此非常小的图像可能会显得拥挤;在这种情况下,请使用更少、更大的区域。
- 在操作系统或浏览器开发者工具中开启减少运动:图像必须立即显示,且没有光标。
- 检查控制台是否有错误,并确保完成后 DOM 中没有残留任何
aria-hidden的群体元素。
