[{"content":"动机 一直觉得静态博客的纯色背景有点单调，想给它加一点\u0026quot;地图感\u0026quot;：经典地形图上那种一层层的等高线，作为背景若隐若现。同时有几个硬性要求：\n矢量图（SVG），任意分辨率都清晰 每次访问都随机，每张地图都不一样 低对比度，只做氛围，不能影响正文阅读 尽量零依赖，单文件搞定 最终效果可以在本站直接看到——你现在的这个页面背后，就是一张刚刚为你随机生成的等高线图。\n方案选择 \u0026ldquo;每次访问都随机\u0026quot;是关键约束。静态站点没有服务端，可选的路只有两条：\n方案 做法 随机性 客户端实时生成 浏览器里用 JS 生成噪声地形，实时提取等高线 无限变化，真·每访问一次换一张 预生成 SVG 池 构建时生成 N 张 SVG，访问时随机挑一张 只在固定几张里轮换 我选了客户端实时生成。听起来重，其实核心算法只有两个：噪声场 + marching squares，加起来一百多行。\n核心原理 整个流程分四步：\n随机种子 → 噪声高度场 → 等高线提取 → SVG 渲染 1. 种子随机数：mulberry32 先造一个可复现的随机数生成器。等高线背景在窗口缩放时需要重绘，但不应该变成另一张图，所以随机性必须来自一个种子，而不是散装调用 Math.random()：\n1 2 3 4 5 6 7 8 9 function mulberry32(seed) { var s = seed | 0; return function () { s = (s + 0x6d2b79f5) | 0; var t = Math.imul(s ^ (s \u0026gt;\u0026gt;\u0026gt; 15), 1 | s); t = (t + Math.imul(t ^ (t \u0026gt;\u0026gt;\u0026gt; 7), 61 | t)) ^ t; return ((t ^ (t \u0026gt;\u0026gt;\u0026gt; 14)) \u0026gt;\u0026gt;\u0026gt; 0) / 4294967296; }; } 页面加载时取一个新种子（Date.now() ^ Math.random()），刷新即换图；resize 重绘时沿用同一种子，地图保持不变。\n2. 地形高度场：simplex 噪声 + fBm 地形起伏不能是真随机噪点，要有\u0026quot;连绵的丘陵\u0026quot;感。simplex 噪声正好输出这种平滑的连续值。再把 4 个倍频叠加（fBm），大尺度起伏叠上小尺度细节，就很像真实地貌：\n1 2 3 4 5 6 7 8 9 10 function fbm(noise, x, y) { var v = 0, amp = 0.5, freq = 1, sum = 0; for (var o = 0; o \u0026lt; 4; o++) { v += amp * noise(x * freq, y * freq); sum += amp; amp *= 0.5; freq *= 2; } return v / sum; } 在按 9px 间距划分的网格上逐点采样，得到一个高度场数组。最后做两轮 3×3 均值模糊，等高线会更圆润。\n3. 等高线提取：marching squares 这是最有趣的一步。对某个海拔值 iso（比如 0.12），逐格检查网格四个角的高度：高于 iso 记 1，低于记 0，四个角组成 16 种情况。每种情况对应等高线穿过这个格子的方式——比如只有左下角高，线就从左边穿入、底边穿出：\n1 2 3 4 5 6 7 // 边编号: 0=上 1=右 2=下 3=左 var CASES = { 1: [[3, 2]], 2: [[2, 1]], 3: [[3, 1]], 4: [[0, 1]], 6: [[0, 2]], 7: [[0, 3]], 8: [[0, 3]], 9: [[0, 2]], 11: [[0, 1]], 12: [[3, 1]], 13: [[2, 1]], 14: [[3, 2]] }; 交点的具体位置用线性插值求出，比如上边缘：\n1 2 3 if ((tl \u0026gt; iso) !== (tr \u0026gt; iso)) { pt[0] = [x + (iso - tl) / (tr - tl), y]; } 5 和 10 两个\u0026quot;对角\u0026quot;情况存在歧义（两个高点在斜对角），用格子中心值决定连线方式即可。把所有格子里的线段拼成一个 path，一级等高线就出来了。取 11 个海拔值重复这个过程，得到 11 层嵌套的等高线。\n4. SVG 渲染 每级等高线一个 \u0026lt;path\u0026gt;，全部塞进一个固定定位的 \u0026lt;svg\u0026gt;：\n1 2 3 4 var svg = document.createElementNS(SVG_NS, \u0026#39;svg\u0026#39;); svg.setAttribute(\u0026#39;viewBox\u0026#39;, \u0026#39;0 0 \u0026#39; + data.width + \u0026#39; \u0026#39; + data.height); svg.setAttribute(\u0026#39;preserveAspectRatio\u0026#39;, \u0026#39;xMidYMid slice\u0026#39;); svg.setAttribute(\u0026#39;aria-hidden\u0026#39;, \u0026#39;true\u0026#39;); slice 模式保证任何窗口比例下地图都铺满屏幕。\n样式：低对比度的关键 背景层定位与配色都很简单，但有个 CSS 陷阱：如果 body 有不透明背景，z-index: -1 的子元素会被盖住。好在 body 的背景在 HTML 规范里会传播到画布（canvas）上，所以负 z-index 实际是可见的——这也是很多\u0026quot;背景滤镜\u0026quot;技巧能成立的原因。\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 #contour-bg { position: fixed; inset: 0; width: 100%; height: 100%; z-index: -1; pointer-events: none; } #contour-bg path { fill: none; stroke: var(--secondary); stroke-width: 0.7; opacity: 0.35; } #contour-bg path.contour-index { stroke-width: 1; stroke-dasharray: 5 4; opacity: 0.45; } 两个细节：\n颜色直接用主题的 var(--secondary)，PaperMod 在亮/暗模式下会重定义这个变量，深浅主题无需额外适配。35% 的透明度让线与背景的对比度远低于正文文字，阅读不受干扰 模仿真实地形图的习惯，每隔 5 级画一条加粗虚线的\u0026quot;计曲线\u0026rdquo;，其余是细实线 成本 代码量：单文件约 230 行，压缩后 3.5KB，零依赖 耗时：1440×800 视口生成 12 条等高线约 15ms，手机端无压力 无网络请求、无运行时库，构建产物只多一个 JS 文件 小结 噪声场 + marching squares 是一对被游戏和可视化领域用了几十年的老搭档，几十行代码就能做出各种\u0026quot;生成艺术\u0026quot;效果。等高线只是最直接的用法——同样的高度场换个渲染方式，还能做流场图、岛屿生成、地形光照。\n本文背景的完整实现在仓库的 assets/js/contour-bg.js，欢迎自取魔改。\n","permalink":"https://wilfoo.com/posts/contour-map-background/","summary":"用 simplex 噪声生成地形高度场，marching squares 提取等高线，纯前端渲染成 SVG——不依赖任何库，单文件 3.5KB，每次访问都是一张新地图。","title":"给博客加一个每次访问都随机的等高线地形图背景"},{"content":"这里是关于页，介绍一下你自己吧。\n你是谁 博客写什么 联系方式 ","permalink":"https://wilfoo.com/about/","summary":"\u003cp\u003e这里是关于页，介绍一下你自己吧。\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003e你是谁\u003c/li\u003e\n\u003cli\u003e博客写什么\u003c/li\u003e\n\u003cli\u003e联系方式\u003c/li\u003e\n\u003c/ul\u003e","title":"关于"}]