零依赖、单文件:一套自创的分析框架,和它的三个应用站点

先说结论

我做了一套自创的分析框架《三变·六反》,并把它落成了三个应用站点。

三个站都是单文件、零外部依赖------没有 CDN、没有 npm 包、没有第三方字体、没有统计脚本。

应用 说明 地址
总览 框架与三个应用 体系入口,四节分镜 0603wangxiao.github.io/36wx/
01 应用于地缘 武装冲突伤亡记录 + 八模块风险推演沙盘 0603wangxiao.github.io/geosandbox/
02 框架本体 可缩放的知识图谱,节点可点开看释义 0603wangxiao.github.io/36wx/kg/
03 应用于投资 同一套框架在投资研究中的展开 0603wangxiao.github.io/36wx/invest...

「本体」和「投资」两张图谱是桌面端设计,建议用电脑打开。

为什么强调「零依赖」:这类内容往往在信号不稳定的环境里被打开,不该为渲染框架等半秒;而且记录战争伤亡的页面,不该顺手记录读者。

下面讲的是实现这三个站时真实踩过的坑------不是教程,是复盘。


一、三个站点分别是什么

01 应用于地缘 · 生命之祭 + 推演沙盘

index.html 是《生命之祭》------只记录已发生的事实 ,不含任何推演。每个伤亡数字都标注信源与统计口径,而且一律是「经核实的最低确证数」:实际数字更高,这句话写在页面上。

两个编辑判断决定了它的呈现:

  • 儿童单列。在所有统计口径里,儿童最容易被并入「平民」而失去可见度。
  • 军人与平民分列。前者多来自实名核实或情报估算,后者依赖各方登记,有的口径甚至不区分军民。合并会制造虚假的确定性。

sandbox.html 是八个模块的推演沙盘。它和上面严格分栏------已核实事实与推演情景是两条互不混读的轨道,后者每一条都强制加【推演情景·非已证实事实】标签。

这个分栏不是免责声明。而是因为:一旦两类内容在视觉上无法区分,读者就再也无法判断自己读到的到底是什么。

02 框架本体 · 知识图谱

把框架本身画成一张可缩放的知识图谱:三章、六反、卦象与偈文依序展开,每个节点可点开看释义与出处。

  • 单 HTML 文件,386 KB
  • Canvas 手写渲染,支持缩放 / 拖拽 / 节点点击
  • 手写的分层布局(没有用力导向)

03 应用于投资 · 投资策略图谱

同一套框架在投资研究中的展开:图谱化的概念关系与操作次序。

这张图里有一个视频背景 (astra-bg.mp4,3.3 MB),和另外两站的视觉处理方式不同------它需要更低的对比度来衬托前景的节点。

属个人研究笔记与方法整理,不构成任何投资建议,不含具体证券标的或买卖指令。

总览页

把三个站串成一个体系。四节分镜(00 起 / 01 变 / 02 反 / 03 用),右侧有固定的章节轨,页面本身也是零依赖。


二、为什么不用现成的图库

两张图谱都考虑过 Cytoscape、G6、D3-force,最后都自己写了。三个原因。

1. 依赖体积会超过内容本身

这些库动辄数百 KB,加上依赖,体积会超过页面内容。

而页面的全部意义就是让人打开就能看,不该为渲染框架等半秒。

2. 布局要可控

框架有明确的层级语义(三章 → 六反 → 卦象 → 偈文)。力导向布局会把它揉成一团,反而看不清结构。手写的分层布局更贴合内容。

3. 字体

用 Google Fonts 加载等宽字体,在国内会被墙,且阻塞渲染。

最后用系统字体栈:

css 复制代码
--font-sans: -apple-system, BlinkMacSystemFont, "Segoe UI", "PingFang SC",
             "Hiragino Sans GB", "Microsoft YaHei", sans-serif;
--font-mono: ui-monospace, "SF Mono", Menlo, Consolas,
             "Liberation Mono", "Courier New", monospace;

视觉几乎一致,但零请求。


三、踩过的四个坑

坑 1:中文走等宽字体栈会静默回退

这个坑最隐蔽,三个站都中招了。

我给正文里的强调文字设了等宽字体,想做技术感:

css 复制代码
.sec-body em { font-family: var(--font-mono); font-size: .92em; }

结果------ui-monospace / SF Mono / Menlo 都没有中文字形 。浏览器不报错,而是静默回退到某个中文字体。

问题在于:回退字体的度量完全不同 。同一个 font-size 下,中文强调文字看起来明显比周围大一号,字体也不一样。

查 CSS 查不出来------你写的确实是 0.92em,比周围还小。

判断方法 :DevTools → Elements → Computed → 看 Rendered Fonts,它会告诉你这段文字实际用了哪个字体。

修法 :中文一律继承正文字体,只靠颜色做区分。

css 复制代码
.sec-body em { font-style: normal; color: var(--accent); }  /* 不改字体、不改字号 */

如果确实要等宽中文,得显式指定带中文字形的等宽字体(如 Sarasa Mono SC、Noto Sans Mono CJK SC),并做好回退链。


坑 2:min-height: 100svh 会骗过你的移动端测试

总览页有四节满屏分镜,每节 min-height: 100svh。

我用 2600px 高的 iframe 测手机端。结果 100svh 就撑到 2600px,内容垂直居中,顶部出现一大片空白。

看起来像布局崩了。查了很久才发现------是测试方法错了,不是页面错了。

正确做法:用真机视口尺寸。测 390×844 就用 390×844 的 iframe:

html 复制代码
<iframe src="/index.html" style="width:390px;height:844px;border:0"></iframe>

顺便说,这个尺寸下量 scrollWidth 才有意义。我用 --window-size=390 跑 headless Chrome,结果窗口被钳到最小 500px------我拍的其实是「500 宽布局的 390 宽截图」,右边 110px 被切掉 ✗ 又看了一次假象。

--window-size 小于 500 时会被忽略,用 iframe 才可靠。


坑 3:全角书名号造成 25px 光学错位

这个坑只有量墨迹才能发现。总览页首屏是编号 + 大标题,两者都左对齐。

用 getBoundingClientRect() 量:

ini 复制代码
「00」   left = 46.0
「《」   left = 46.0
          差值 = 0.0     ← 完全对齐

但看起来就是没对齐。

用 Range 逐个量字形:

js 复制代码
const r = document.createRange();
r.setStart(textNode, 0);
r.setEnd(textNode, 1);
const glyph = r.getBoundingClientRect();   // 第一个字形的实际位置

结果:

ini 复制代码
「00」   边界框 left = 46.0
「《」   边界框 left = 46.0
        墨迹起点 x = 72      ← 实际墨迹
              差 26px

原因 :《》「」() 这类全角标点自带左侧边距(这里是约 0.48em)。边界框是对齐的,视觉上却是凹进去的。

修法:用 em 做负向缩进,随字号缩放。

css 复制代码
#c0 h1 { text-indent: -.48em; }   /* 仅宽屏生效 */

只在宽屏生效是因为:手机端左右内边距只有 18px,负缩进会把字顶到屏幕边缘。

教训:边界框对齐 ≠ 视觉对齐。 全角标点必须量墨迹。


坑 4:末端孤字(一行只剩一两个字)

中文排版里,一行末尾只剩一两个字折下去,非常难看。

三个成因,得逐个解决:

① 硬换行会锁死断行。

我原本在正文里用 <br> 控制断句。宽屏没问题,但窄屏下硬 <br> 把段落切成固定行,浏览器失去优化断行的余地。

css 复制代码
@media (max-width: 1079px) {
  .sec-body br { display: none; }   /* 放开,交给浏览器按实际列宽断行 */
}

② 没有告诉浏览器避免孤字。

css 复制代码
.sec-body { text-wrap: pretty; line-break: strict; }

text-wrap: pretty 让浏览器主动避免末行孤字;line-break: strict 让 CJK 标点不落在行首。

③ pretty 对多行长段落能力有限。

实测一段 5 行的文字,末行仍然只剩 3 个字(42px / 320px = 13%)。

窄屏再叠加 balance:

css 复制代码
@media (max-width: 1079px) {
  .sec-body, .app p { text-wrap: balance; }   /* 把各行宽度拉平 */
}

实测那段文字从 5 行压到 3 行,末行占容器宽度从 13% 升到 70%。

分工 :桌面用 pretty(列宽大、行数少,断行更自然),手机用 balance(列宽小、行数多,需要拉平)。标题和短块一律 balance。


四、三个站的排版统一:把字号收敛成一套变量

三个站做到一半,我发现总览页有 11 种字号,而且同一角色在不同位置还不一样。

最离谱的是「正文」:.sec-body 是 14.5px,而同样属于正文的 .app p 只有 13px。

改成在 :root 定义 7 级阶梯,全站只引用变量:

css 复制代码
:root {
  --fs-title: clamp(28px, 3.2vw, 48px);    /* 分节标题 */
  --fs-sub:   clamp(17px, 1.9vw, 26px);    /* 子标题 */
  --fs-verse: clamp(16px, 1.26vw, 18px);   /* 偈文 */
  --fs-body:  clamp(14px, .95vw, 16px);    /* 正文(多处共用)*/
  --fs-val:   clamp(13px, .86vw, 14px);    /* 规格值 */
  --fs-idx:   clamp(11px, .9vw, 13px);     /* 序号 */
  --fs-meta:  clamp(10px, .82vw, 11.5px);  /* 标签与元信息 */
}

关键不只是「定义变量」,而是让同一个角色只有一个值 :正文和卡片说明共用 --fs-body,序号和页码共用 --fs-idx。

改完实测,手机端每个角色只剩一个值 ✓


五、粒子场:调上限没有用

总览页背景是 Canvas 2D 的加色混合粒子场。最初参数:

js 复制代码
const n = Math.round(Math.min(340, (W * H) / 7600));

有人反馈「粒子再多一点」。我第一反应是调上限------但没用。

因为上限根本没到。在 1664×900 下:

ini 复制代码
1664 × 900 / 7600 = 197   ← 实际只有 197 颗,340 是空的

要加密得改密度除数:

js 复制代码
const n = Math.round(Math.min(560, (W * H) / 4200));   // 197 → 356,+80%

性能上做三件事:

js 复制代码
// DPR 上限:软圆点感知不到高分辨率,但开销差一倍
const dpr = Math.min(devicePixelRatio, coarse ? 1 : 1.3);

// 离屏或标签页隐藏时暂停
new IntersectionObserver(es => visible = es[0].isIntersecting)
  .observe(document.body);

// 尊重用户偏好
const reduced = matchMedia('(prefers-reduced-motion: reduce)').matches;
const k = reduced ? 0 : 1;   // 位移乘 0,粒子静止

每帧约 356 次 arc() 填充,普通设备无压力。


六、一个必须说的设计决定

总览页的入场动画,我改成了 无 JS 默认可见。

原本写法是 opacity: 0 打底、靠 JS 加 .in 类才显示。问题是------一旦 JS 失败或 IntersectionObserver 不触发,整页就是空白的。

对一个「让人阅读」的页面,这不可接受。

改成用 <html class="js"> 作用域:

html 复制代码
<script>document.documentElement.classList.add('js')</script>
css 复制代码
/* 无 JS 时内容默认可见 */
.js .sec-body { opacity: 0; transform: translateY(14px); }
.js section.in .sec-body { opacity: 1; transform: none; }

再加一个 1.6 秒的兜底,强制点亮已进入视口的元素。

渐进增强应该真的"渐进" ------ 动画是加分项,不是显示的前提。


七、源码

三个站的源码在 GitHub:

再放一次三个地址,方便你直接点:


作者:王潇,独立完成。自由使用,请注明来处;禁止商用。

如果这些坑对你有用,或者你对「零依赖静态站点」这个方向有自己的取舍,欢迎评论区聊。

相关推荐
阿俊在Coding1 小时前
从 0 实现最小 Coding Agent:读懂 Pi 生产级 Agent 的骨架
github
开开心心就好1 小时前
二维码批量生成导出工具,离线可用完全免费
java·前端·人工智能·智能手机·github·excel·visual studio
wflynn1 小时前
GitHub 日榜趋势速报 | 2026-10-03
开源·github
本地化文档2 小时前
pygmt-docs-l10n
python·github·gitcode·sphinx·pygmt·gmt·crowdin
wflynn2 小时前
GitHub 今日推荐|REDox:64 位 token 表示结构化数据,内存占用降 70% 支持多格式互转
开源·c#·github
miofly3 小时前
GitHub 周榜趋势速报 | 2026-10-04
开源·github
miofly3 小时前
GitHub 日榜趋势速报 | 2026-10-04
开源·github
怕浪猫13 小时前
Prompt Engineering 面试怎么考?这 5 个范式你必须会
面试·程序员·github
可乐ea17 小时前
GitHub Security Lab 开源 Fuzzing Taskflow:让 LLM 智能体自己跑完 C/C++ 模糊测试
jvm·c++·github·模糊测试·mcp·代码agent·智能体自动化