{/* This page is auto-generated from the skill's SKILL.md by website/scripts/generate-skill-docs.py. Edit the source SKILL.md, not this page. */}
Pretext
用无 DOM 文本布局构建创意浏览器 demo。
Skill 元数据
| 来源 | 可选 — 通过 hermes skills install official/creative/pretext 安装 |
| 路径 | optional-skills/creative/pretext |
| 版本 | 1.0.0 |
| 作者 | Hermes Agent |
| 许可证 | MIT |
| 平台 | linux, macos, windows |
| 标签 | creative-coding, typography, pretext, ascii-art, canvas, generative, text-layout, kinetic-typography |
| 相关 skill | p5js, claude-design, excalidraw, architecture-diagram |
参考:完整 SKILL.md
以下是 Hermes 在触发本 skill 时加载的完整 skill 定义。这是 agent 在 skill 激活时所看到的指令内容。
Pretext 创意 Demo
概述
@chenglou/pretext 是 Cheng Lou(React 核心、ReasonML、Midjourney)写的一个 15KB、零依赖的 TypeScript 库,用于无 DOM 的多行文本测量与排版。它只做一件事:给定 (text, font, width),返回换行、每行宽度、每个字素的位置和总高度——全部经 canvas 测量,无重排。
听着像管线工程。其实不是。因为它快且几何化,它是一个创意原语:你可以让段落以 60fps 绕着一个移动精灵重排,做出关卡几何由真实单词构成的游戏,让 ASCII logo 穿行散文,把文字按精确的逐字素起始位置炸成粒子,或打包紧裹的多行 UI 而无需任何 getBoundingClientRect 抖动。
本 skill 的存在就是让 Hermes 用它做酷 demo——那种人们会发到 X 上的。社区 demo 库见 pretext.cool 和 chenglou.me/pretext。
何时使用
用户要求以下时使用:
- 一个"pretext demo" / "酷的 pretext 东西" / "文本即 X"
- 文字绕着移动形状流动(首屏、编辑版式、动画长文页)
- 用真实单词或散文做 ASCII 艺术效果,而非等宽光栅
- 游戏,其场地/障碍/砖块由文字构成(字母版俄罗斯方块、散文版打砖块)
- 逐字形物理的动态排版(碎裂、散射、集群、流动)
- 字体排印生成艺术,尤其带非拉丁文字或混合文字
- 多行"紧裹"UI(仍能放下文本的最小容器宽度)
- 任何需要在渲染之前知道换行的东西
不要用于:
- CSS 已解决布局的静态 SVG/HTML 页面——直接用 CSS
- 富文本编辑器、通用内联格式化引擎(pretext 刻意做窄)
- 图片 → 文字(用
ascii-art/ascii-videoskill) - 无文字角色的纯 canvas 生成艺术——用
p5js
创意标准
这是在浏览器里渲染的视觉艺术。Pretext 返回数字;你来画东西。
- 不要交付一个 "hello world" demo。
hello-orb-flow.html模板是起点。每个交付的 demo 必须加有意的颜色、动效、构图,以及一个用户没要求但会欣赏的视觉细节。 - 暗背景、暖核心、考究的调色板。 经典黑底琥珀(CRT/终端)可行,但炭底冷白(编辑感)和去饱和粉彩(risograph)也行。选一个并坚持。
- 比例字体才是重点。 Pretext 的整个调性就是"不等宽"——拥抱它。用 Iowan Old Style、Inter、JetBrains Mono、Helvetica Neue 或可变字体。绝不默认无衬线。
- 真实源文本,不要 lorem ipsum。 语料要有意义。短宣言、诗、真实源码、一段捡到的文字、库自己的 README——绝不
lorem ipsum。 - 首帧即卓越。 无加载态、无空白帧。demo 打开瞬间就必须看起来可交付。
技术栈
每个 demo 一个自包含 HTML 文件。无构建步骤。
| 层 | 工具 | 用途 |
|---|---|---|
| 核心 | @chenglou/pretext,经 esm.sh CDN | 文本测量 + 行布局 |
| 渲染 | HTML5 Canvas 2D | 字形渲染、逐帧构图 |
| 分词 | Intl.Segmenter(内置) | emoji / CJK / 组合符的字素切分 |
| 交互 | 原生 DOM 事件 | 鼠标 / 触摸 / 滚轮——无框架 |
<script type="module">
import {
prepare, layout, // use-case 1: simple height
prepareWithSegments, layoutWithLines, // use-case 2a: fixed-width lines
layoutNextLineRange, materializeLineRange, // use-case 2b: streaming / variable width
measureLineStats, walkLineRanges, // stats without string allocation
} from "https://esm.sh/@chenglou/pretext@0.0.6";
</script>
固定版本号。写作时是 @0.0.6——若 demo 行为不对,查 npm 最新版。
两个用例
几乎一切都归约成这两种形状之一。两个都学。
用例 1——测量,然后用 CSS/DOM 渲染
const prepared = prepare(text, "16px Inter");
const { height, lineCount } = layout(prepared, 320, 20);
你仍让浏览器画文字。Pretext 只告诉你给定宽度下盒子多高,无需读 DOM。用于:
- 行内含换行文本的虚拟化列表
- 卡片高度精确的瀑布流
- "这个标签放得下吗?"开发期检查
- 远程文本加载时防止布局偏移
让 font 和 letterSpacing 与你的 CSS 严格同步。 canvas ctx.font 格式(例如 "16px Inter"、"500 17px 'JetBrains Mono'")必须匹配渲染的 CSS,否则测量漂移。
用例 2——你既测量又渲染
const prepared = prepareWithSegments(text, FONT);
const { lines } = layoutWithLines(prepared, 320, 26);
for (let i = 0; i < lines.length; i++) {
ctx.fillText(lines[i].text, 0, i * 26);
}
创意工作就在这里。你掌控绘制,所以可以:
- 渲染到 canvas、SVG、WebGL 或任何坐标系
- 做逐字形变换(旋转、抖动、缩放、透明度)
- 用行元数据(宽度、字素位置)当几何
对逐行变宽流动(文字绕形状、环形带里的文字、非矩形栏里的文字):
let cursor = { segmentIndex: 0, graphemeIndex: 0 };
let y = 0;
while (true) {
const lineWidth = widthAtY(y); // your function: how wide is the corridor at this y?
const range = layoutNextLineRange(prepared, cursor, lineWidth);
if (!range) break;
const line = materializeLineRange(prepared, range);
ctx.fillText(line.text, leftEdgeAtY(y), y);
cursor = range.end;
y += lineHeight;
}
这是整个库里最重要的模式。它解锁了"文字绕拖拽精灵流动"——那个在 X 上病毒式传播的 demo。
值得知道的辅助函数
measureLineStats(prepared, maxWidth)→{ lineCount, maxLineWidth }——最宽行,即多行紧裹宽度。walkLineRanges(prepared, maxWidth, callback)——不分配字符串地迭代行。当你不需要字符、只要对字素做统计/物理时用。@chenglou/pretext/rich-inline——同一系统,但用于混合字体/chip/@提及的段落。从子路径导入。
Demo 配方模式
社区库(见 references/patterns.md)聚成几个强模式。挑一个变奏——除非要求否则别发明新类别。
| 模式 | 关键 API | 示例点子 |
|---|---|---|
| 绕障碍重排 | layoutNextLineRange + 逐行宽度函数 | 编辑段落从拖拽光标精灵两侧分开 |
| 文字即几何游戏 | layoutWithLines + 逐行碰撞矩形 | 每块砖是一个被测单词的打砖块 |
| 碎裂/粒子 | walkLineRanges → 逐字素 (x,y) → 物理 | 点击时炸成字母的句子 |
| ASCII 障碍字体 | layoutNextLineRange + 测得的逐行障碍跨度 | 位图 ASCII logo、形状变形、可拖拽线框物体,让文字按其真实几何打开 |
| 编辑多栏 | 逐栏 layoutNextLineRange + 共享光标 | 带 pull quote 的动画杂志跨页 |
| 动态字体 | layoutWithLines + 随时间逐行变换 | 星球大战字幕滚动、波浪、弹跳、glitch |
| 多行紧裹 | measureLineStats | 自动缩到最紧容器的引用卡 |
可运行的单文件起点见 templates/donut-orbit.html 和 templates/hello-orb-flow.html。
工作流
- 根据用户简报从上面的表挑一个模式。
- 从模板起步:
templates/hello-orb-flow.html——文字绕移动球体重排(绕障碍重排模式)templates/donut-orbit.html——高级示例:测得的 ASCII logo 障碍、可拖拽线框球/立方体、变形形状场、可选 DOM 文本、仅开发控件write_file到~/.hermes/cache/scratch/或用户工作区里的新.html。
- 把语料换成贴合简报的有意内容。真实散文,10-100 句,不要 lorem。
- 调校美学——字体、调色板、构图、交互。这是正事;别跳过。
- 本地验证:
cd <dir-with-html> && python -m http.server 8765 # then open http://localhost:8765/<file>.html - 查控制台——若
prepareWithSegments用了坏字体串,pretext 会抛错;每个现代浏览器都有Intl.Segmenter。 - 给用户文件路径,不只是代码——他们要打开它。
性能笔记
prepare()/prepareWithSegments()是昂贵调用。每个 文本+字体 对只做一次。缓存 handle。- 缩放时只重跑
layout()/layoutWithLines()——绝不重新 prepare。 - 对文字不变但几何变的逐帧动画,紧循环里的
layoutNextLineRange对正常长度段落每帧 60fps 都够便宜。 - 逐帧渲染 ASCII 遮罩时,保留一个单元格缓冲(
Uint8Array/类型数组),从单元格或投影几何推导测得的逐行障碍跨度,合并跨度,再在画文字前喂给layoutNextLineRange。 - 让视觉动画和布局动画耦合。若球变成立方体,用同一个值补间渲染的单元格缓冲和障碍跨度;否则 demo 看起来像贴上去的,而非物理重排。
- 淡入淡出时,优先层透明度而非改字形强度或障碍尺度。把临时 ASCII 精灵放在自己的 canvas 上,用 CSS/GSAP 透明度淡该 canvas,几何就不会显得缩小。
- canvas
ctx.font设置慢得惊人;字体不变时每帧设一次,而不是每次fillText。
常见坑
-
CSS/canvas 字体串漂移。
ctx.font = "16px Inter"测量,但 CSS 是font-family: Inter, sans-serif; font-size: 16px。只要 Inter 加载就没问题。若 Inter 404,CSS 回退到 sans-serif,测量漂移 5-20%。始终preload字体或用 web 安全字体族。 -
在动画循环里重新 prepare。 只有
layout*便宜。每帧重调prepare会拖垮性能。把 prepared handle 留在模块作用域。 -
字素切分忘了
Intl.Segmenter。 Emoji、组合符、CJK——"é".split("")给你两个字符。采样单个可见字形时用new Intl.Segmenter(undefined, { granularity: "grapheme" })。 -
rich-inline里break: 'never'的 chip 没给extraWidth。 若你对原子 chip/提及用break: 'never',还必须为胶囊内边距给extraWidth——否则 chip 金属边溢出容器。 -
从
unpkg用@chenglou/pretext且入口是纯 TypeScript。 用esm.sh——它自动把 TS 导出编成浏览器可用的 ESM。unpkg会 404 或发原始 TS。 -
等宽回退悄悄抹掉整个意义。 看到等宽外观输出的用户,CSS
font-family常回退到了monospace。用 DevTools 验证实际渲染字体。 -
绕形状流动时,跳行 vs 调宽度。 若这行走廊太窄放不下一行,跳过该行(
y += lineHeight; continue;),而不是给layoutNextLineRange传一个极小 maxWidth——pretext 会返回一字素行,看着像坏了。 -
交付一个冷冰冰的 demo。 默认首帧看着像教程级。加:暗角、微妙扫描线、闲置自动动效、一个精心选的交互响应(拖、悬停、滚、点)。没有这些,"酷 pretext demo" 就落成"实习生复刻 README"。
验证清单
- demo 是单个自包含
.html文件——双击或python -m http.server可开 -
@chenglou/pretext经esm.sh导入且版本固定 - 语料是真实散文,不是 lorem ipsum,且匹配 demo 概念
- 传给
prepare的字体串与 CSS 字体严格一致 -
prepare()/prepareWithSegments()调一次,不是每帧 - 暗背景 + 考究调色板——不是默认白 canvas
- 至少一个交互响应(拖/悬停/滚/点)或闲置自动动效
- 本地用
python -m http.server测过,确认无控制台错误 - 中端笔记本上 60fps(或记录了优雅降级)
- 一个用户没要求的"多走一步"细节
参考:社区 Demo
clone 这些找灵感/模式(均为 MIT 类,链接自 pretext.cool):
- Pretext Breaker——单词砖块打砖块——
github.com/rinesh/pretext-breaker - 俄罗斯方块 × Pretext——
github.com/shinichimochizuki/tetris-pretext - 龙动画——
github.com/qtakmalay/PreTextExperiments - Somnai 编辑引擎——
github.com/somnai-dreams/pretext-demos - Bad Apple!! ASCII——
github.com/frmlinn/bad-apple-pretext - 拖精灵重排——
github.com/dokobot/pretext-demo - Alarmy 编辑时钟——
github.com/SmisLee/alarmy-pretext-demo
官方 playground:chenglou.me/pretext——手风琴、气泡、动态布局、编辑引擎、对齐对比、瀑布流、markdown 聊天、富笔记。