4000 字
20 分钟
Generative-design Homepage
2026-07-22
Purely By AI

生成式设计在个人主页的落地实践#

在个人主页中引入「生成式设计系统」,让每一次访问能产出一套独特且自洽的视觉语言——配色、字体、排版、装饰层、阴影、圆角、间距,皆由单一伪随机种子确定性推导而来。


缘起:从「固定页面」到「生成规则」#

传统静态主页的视觉语言在部署那一刻即被固化:设计师在 Figma 中定稿配色、字重、间距、圆角,工程师将其翻译为 CSS 变量与类名,部署后便不再变化。这种「设计即交付物」的模式虽稳妥,却也意味着访客每次看到的都是同一张面孔。

生成式设计(Generative Design)提供了另一条路径:不再交付固定的视觉结果,而是交付一套「生成规则」与「参数空间」。访问者每次打开页面、每次点击「重掷」、每次在种子栏输入任意字符,系统都依据同一套确定性算法、从一颗种子出发,即时演算出一套完整、自洽、可用的设计系统——内容固定,样式即时生长

本文记录该生成式设计系统在个人主页中的落地要点、核心算法与工程落地时的关键决策,供同类实践参考。


核心架构:种子 → 伪随机流 → 设计令牌 → CSS 变量#

整个生成链路可拆解为四个阶段,数据单向流动,无循环依赖:

Seed (string) ──► Hash (FNV-1a) ──► PRNG (Mulberry32) ──► Design Tokens ──► CSS Custom Properties
│ │ │ │ │
用户输入/随机 统一成 32-bit 确定性伪随机流 调色板/字体/间距/ 注入 :root,
种子字符串 无符号整数 (可复现、可遍历) 阴影/圆角/装饰层 即时生效

1. 种子规范化与哈希:FNV-1a 32-bit#

种子来源不受控:可能是用户手输的中文、Emoji、空字符串、极长字符串,也可能是 Math.random() 产出的数字字符串。首要任务是将任意字符串稳定映射为 32-bit 无符号整数,作为 PRNG 的初始状态。

// FNV-1a 32-bit:极简、分布均匀、无依赖
function hashStr(str) {
var s = String(str == null ? "" : str);
var h = 2166136261 >>> 0; // FNV offset basis
for (var i = 0; i < s.length; i++) {
h ^= s.charCodeAt(i);
h = Math.imul(h, 16777619); // FNV prime
}
// 末轮混淆:让空串、单字符、数字字符串等边界情况碰撞概率进一步降低
h ^= s.length + 1;
h = Math.imul(h ^ (h >>> 16), 2246822507) >>> 0;
h = Math.imul(h ^ (h >>> 13), 3266489909) >>> 0;
return (h ^ (h >>> 16)) >>> 0;
}

工程要点

  • 统一按字符串处理"012" ≠ "12" ≠ 12,避免隐式类型转换导致的语义歧义;
  • 空字符串合法:作为「无种子/随机种子」的显式表示,哈希后仍得到确定值;
  • Math.imul 保证 32-bit 乘法溢出语义与 C/C++ 一致,跨引擎行为一致;
  • 末轮混淆(两轮 xorshift + multiply)大幅降低短串、近似串的碰撞概率。

2. 确定性伪随机数生成器:Mulberry32#

哈希值仅作为初始状态,后续需源源不断的均匀分布随机数。选用 Mulberry32——单状态、周期 2³²、零分配、极简实现:

function mulberry32(seed) {
return function () {
var t = (seed += 0x6D2B79F5) >>> 0;
t = Math.imul(t ^ (t >>> 15), t | 1);
t ^= t + Math.imul(t ^ (t >>> 7), t | 61);
return ((t ^ (t >>> 14)) >>> 0) / 4294967296;
};
}

为何不选 Math.random()

维度Math.random()Mulberry32
可复现性❌ 无法指定种子✅ 同种子必产出同序列
状态隔离❌ 全局共享✅ 实例级隔离,互不干扰
规范一致性❌ 引擎实现不一✅ 算法固定,跨环境一致
体积内置~10 行 ES5 代码

工程要点

  • 仅在 applySeed() 时实例化一次 rand = mulberry32(seedToU32(seed)),随后所有随机决策均调用 rand()
  • 提供 pick(arr)range(min, max) 等语义化封装,业务代码零直接依赖数值分布细节。

3. 调色板生成:HSL 空间中的约束求解#

配色是视觉系统的骨架。生成器在 HSL 空间构建调色板,再通过 对比度约束求解 保证可读性:

function genPalette() {
var baseHue = range(0, 360); // 基准色相
var offsets = pick([ // 配色策略:单色/类似/互补/三角色/分裂互补
[0, 0, 0],
[0, 30, -30],
[0, 180, 180],
[0, 120, 240],
[0, 150, 210]
]);
var hues = offsets.map(function (o) { return (baseHue + o + 360) % 360; });
var sat = range(42, 88); // 饱和度带
var dark = rand() < 0.52; // 暗/亮模式 52% 概率暗色
// 背景/表层/前景三层基础色
if (dark) {
bg = { h: hues[0], s: sat * 0.32, l: range(6, 12) };
surface = { h: hues[0], s: sat * 0.3, l: bg.l + range(5, 9) };
fg = { h: hues[0], s: sat * 0.12, l: range(90, 96) };
} else {
bg = { h: hues[0], s: sat * 0.4, l: range(93, 98) };
surface = rand() < 0.55
? { h: hues[0], s: sat * 0.25, l: 99 }
: { h: bg.h, s: bg.s, l: bg.l };
fg = { h: hues[0], s: sat * 0.45, l: range(10, 18) };
}
// 强调色与第二强调色
accent = { h: hues[1], s: sat, l: range(44, 58) };
accent2 = { h: hues[2], s: sat * 0.85, l: range(40, 60) };
// 对比度守卫:逐步调整亮度直到满足 WCAG AA
ensureContrast(fg, bg, 4.5);
ensureContrast(accent, surface, 3.5);
ensureContrast(accent, bg, 2.8);
// 衍生色:muted / dim
muted = { h: fg.h, s: fg.s * 0.45, l: dark ? range(58, 68) : range(38, 48) };
dim = { h: fg.h, s: fg.s * 0.3, l: dark ? range(40, 50) : range(55, 65) };
return { bg, surface, fg, accent, accent2, muted, dim, dark };
}

关键决策

  1. HSL 而非 sRGB/Lab:色相旋转直观、饱和度/亮度线性插值符合设计直觉、CSS hsl() 原生支持、无需色彩管理库;
  2. 策略化配色而非纯随机:五种经典配色策略(单色、类似、互补、三角色、分裂互补)离散采样,避免「全随机」产生的不协调感;
  3. 对比度迭代修正ensureContrast() 在保持色相、饱和度不变的前提下,仅调整亮度,以最小视觉偏移换取可读性达标(WCAG AA 4.5:1 / 3:1 分级);
  4. 暗色模式概率偏向rand() < 0.52 让暗色略占优势,符合开发者/设计师主页的审美惯性。

4. 设计令牌全谱生成#

调色板仅是色彩维度。完整设计系统还需字体、排版、布局、装饰、阴影、圆角、边框等正交维度的令牌,全部在 genDesign() 中一次性生成并写入 :root

function genDesign() {
var p = genPalette();
var root = document.documentElement.style;
var set = function (k, v) { root.setProperty(k, v); };
// 色彩令牌
set("--bg", css(p.bg));
set("--surface", css(p.surface));
set("--fg", css(p.fg));
set("--muted", css(p.muted));
set("--dim", css(p.dim));
set("--accent", css(p.accent));
set("--accent2", css(p.accent2));
set("--accent-a", "0.14");
// 字体族:Serif/Display、Sans/Body、Mono 三轴各备选 7~5 组
set("--font-display", pick(DISPLAY_FONTS));
set("--font-body", pick(BODY_FONTS));
// font-mono 固定为系统等宽栈,保证代码片段可读性
// 排版比例与微调
set("--scale", pick([1.2, 1.25, 1.333, 1.414, 1.5])); // 模块化比例
set("--name-factor", range(0.88, 1.45).toFixed(2)); // 英雄区标题缩放
set("--tracking", pick(["-0.04em", "-0.02em", "0em", "0.03em", "0.08em"]));
set("--tag-weight", pick([500, 600, 700, 800]));
// 布局对齐与容器宽度
var align = pick(["left", "left", "center"]); // 左对齐权重 2/3
set("--align", align);
set("--links-justify", align === "center" ? "center" : "flex-start");
document.body.setAttribute("data-align", align); // CSS 选择器联动
set("--container", pick([
"min(88vw, 560px)",
"min(90vw, 680px)",
"min(92vw, 820px)",
"min(94vw, 920px)"
]));
// 间距、圆角、边框、阴影
set("--space", range(1.05, 1.85).toFixed(2) + "rem");
set("--radius", pick([0, 0, 6, 10, 14, 20, 999]) + "px"); // 0 与 999 各 1/7 概率
set("--border-w", pick([0, 1, 1, 2, 2, 3]) + "px");
var shadowType = pick(["none", "soft", "soft", "hard"]);
if (shadowType === "soft") {
set("--shadow", "0 " + (range(10, 26) | 0) + "px " + (range(28, 56) | 0) +
"px hsl(0 0% 0% / " + (p.dark ? 0.35 : 0.14) + ")");
} else if (shadowType === "hard") {
set("--shadow", (range(4, 9) | 0) + "px " + (range(4, 9) | 0) + "px 0 " + css(p.fg));
} else {
set("--shadow", "none");
}
// 装饰层:纯色/渐变/点阵/网格/斜纹/几何体/大字/光晕
genDecoration(p);
}

正交性保证:每个令牌独立采样,互不制约。例如 --radius--shadow 无耦合,--font-display--scale 无耦合。这种「参数空间正交采样」虽可能产出极端组合(如 radius=999px + shadow=hard),但正是生成式设计的张力所在——极端即个性,常态即舒适

5. 装饰层程序化生成#

body::before 仅提供基础噪点纹理。genDecoration(palette) 在运行时动态挂载 .deco 元素,构建八种风格迥异的背景层:

类型实现要点视觉特征
plain纯色背景极简、聚焦内容
gradient双色线性渐变,随机角度方向性、层次感
glow双径向渐变叠加,位置/半径/不透明度随机深度、氛围感
dotsradial-gradient 点阵,background-size 控制间距复古印刷/半调感
grid正交线性渐变十字叠加瑞士风/技术感
diagonalrepeating-linear-gradient(45deg...)动态、节奏
shapes4~8 个随机几何体(圆/环/方/三角),随机位置/旋转/透明度抽象、生成艺术感
bigword超大号显示字体,极低不透明度,旋转偏移编辑性、品牌感

实现细节

  • 所有 .deco 统一 position: fixed; pointer-events: none; z-index: 0; will-change: transform, opacity,不干扰内容层交互与滚动;
  • 切换种子前 body.querySelectorAll(".deco").forEach(el => el.remove()) 清理旧层,避免 DOM 堆积;
  • bigword 文案从预设词表抽样("HI", "HELLO", "你好", "WELCOME", "★", "CODE", "YUER"),中英混排体现双语语境。

交互层:种子栏与骰子动画#

生成式系统若无交互入口,便退化为「随机主题切换器」。种子栏 让访客成为共创者:

<div class="seedbar" id="seedbar" data-stage="seed">
<span class="seedbar-prefix" id="seed-prefix">seed</span>
<input class="seedbar-input" id="seed-input" type="text"
spellcheck="false" autocomplete="off" placeholder="任意字符…" />
<button type="button" class="seedbar-copy" id="seed-copy">copy</button>
<button type="button" class="seedbar-btn" id="reroll" aria-label="reroll">
<span class="die" id="die-face">🎲</span>
</button>
</div>

交互逻辑#

触发行为
页面首次加载applySeed(randomSeed()),随机种子落地
用户输入种子并回车/失焦applySeed(input.value, { animate: true }),平滑过渡
点击 🎲 重掷playDiceRoll(nextSeed, () => applySeed(nextSeed, { animate: true }))
点击 Copynavigator.clipboard.writeText(currentSeed),按钮文案临时变为 COPIED

骰子面映射:种子最后一位数字#

var DIE_FACES = ["⚀", "⚁", "⚂", "⚃", "⚄", "⚅"];
function faceForSeed(seed) {
var s = String(seed == null ? "" : seed);
var d;
if (!s.length) {
d = seedToU32(s) % 10;
} else {
var ch = s.charAt(s.length - 1);
d = (ch >= "0" && ch <= "9") ? +ch : (seedToU32(s) % 10);
}
var n = d % 6;
if (n === 0) n = 6;
return DIE_FACES[n - 1];
}

设计意图:种子最后一位数字直观映射为骰子点数(非数字字符回退哈希),让「种子即骰子」的隐喻在视觉上闭环。

种子切换过渡动画(GSAP)#

if (opts.animate && typeof gsap !== "undefined" && gsap.to &&
!window.matchMedia("(prefers-reduced-motion: reduce)").matches) {
var targets = document.querySelectorAll(".card, .block");
gsap.fromTo(targets,
{ autoAlpha: 0.55, y: 10, scale: 0.99 },
{ autoAlpha: 1, y: 0, scale: 1, duration: 0.45, ease: "power3.out", stagger: 0.04 }
);
var decos = document.querySelectorAll(".deco");
if (decos.length) {
gsap.fromTo(decos,
{ autoAlpha: 0, scale: 0.85 },
{ autoAlpha: 1, scale: 1, duration: 0.7, ease: "power2.out", stagger: 0.05 }
);
}
}
  • 不重排、不重绘布局:仅 opacitytransform,GPU 合成层完成;
  • 交错延迟 传递「重生」而非「闪烁」的质感;
  • 尊重 prefers-reduced-motion:动画降级为瞬时切换。

工程落地的关键决策与避坑指南#

1. 单文件零构建约束下的模块化#

项目坚持 单 HTML 文件、无构建、无打包。生成引擎、色彩工具、交互逻辑、GSAP 动画、RSS 拉取、时钟、代码流式启动动画——全部内联在一个 <script> IIFE 中。

组织策略

(function () {
'use strict';
/* ===== 1. PRNG & Hash ===== */
var mulberry32 = ...;
var hashStr = ...;
var seedToU32 = ...;
/* ===== 2. Color Science ===== */
var hslToRgb = ...;
var luminance = ...;
var contrastRatio = ...;
var ensureContrast = ...;
var css = ...; // HSL 对象 → "hsl(h s% l% / a)"
var genPalette = ...;
/* ===== 3. Design Token Generation ===== */
var DISPLAY_FONTS = [...];
var BODY_FONTS = [...];
var genDecoration = ...;
var genDesign = ...;
/* ===== 4. Seed Application & UI ===== */
var applySeed = ...;
var randomSeed = ...;
var faceForSeed = ...;
var setDieFace = ...;
var playDiceRoll = ...;
/* ===== 5. Boot / Streaming / GSAP Intro ===== */
var runCodeStream = ...;
var startIntro = ...;
/* ===== 6. Runtime: Clock, RSS, Magnetic ===== */
var padTime = ...;
var fetchPosts = ...;
var bindMagnetic = ...;
/* ===== Entry ===== */
applySeed(randomSeed());
runCodeStream(function (meta) { startIntro(meta); });
})();
  • 命名空间隔离:IIFE 形成闭包,全局仅暴露 currentSeedrand 等极少必要引用;
  • 阶段注释分块/* ===== N. 模块名 ===== */ 替代文件分割,便于在单文件中定位;
  • 零依赖核心:PRNG、哈希、色彩数学、令牌生成均为原生 JS,仅 GSAP 与 fetch 为外部运行时依赖

2. CSS 变量注入时机与 FOUC 抑制#

genDesign() 直接操作 document.documentElement.style.setProperty(),浏览器同步重算样式,无闪烁(FOUC)风险。但需注意:

  • 首屏渲染前完成:在 applySeed(randomSeed()) 调用时,HTML 已解析完毕、CSSOM 就绪,变量注入即时生效;
  • transition 仅用于交互态:root 变量变更本身不触发过渡,.card, .block 等组件通过 transition: background-color .35s, box-shadow .35s... 响应色彩/阴影变化,种子切换动画由 GSAP 显式驱动,而非依赖 CSS Transition;
  • prefers-reduced-motion 全链路贯通:CSS @media、GSAP matchMedia、骰子动画分支、种子切换过渡分支,四处同步判断,确保动效减弱模式下零动画、零延迟

3. 字体回退与中英文混排#

DISPLAY_FONTSBODY_FONTS 均采用 字体栈 而非单一字体:

var DISPLAY_FONTS = [
'Georgia, "Noto Serif SC", "Songti SC", serif',
'"Palatino Linotype", "Book Antiqua", "Noto Serif SC", serif',
'Impact, "Arial Black", "PingFang SC", sans-serif',
...
];

原则

  • 西文字体在前(Georgia, Palatino, Impact…),中文回退在后(Noto Serif SC, PingFang SC, Songti SC);
  • 系统字体优先,避免 @font-face 加载延迟与 CLS;
  • 等宽字体栈固定为 ui-monospace, "Cascadia Code", "SF Mono", Menlo, monospace,保证代码片段、种子栏、时钟的字符宽度稳定。

4. 色彩可访问性:对比度守卫的实现细节#

function ensureContrast(color, bg, min) {
if (min == null) min = 4.5;
var bgRgb = hslToRgb(bg.h, bg.s, bg.l);
var dir = luminance(bgRgb) > 0.4 ? -1 : 1; // 背景亮则降低前景亮度,反之升高
var guard = 0;
while (contrastRatio(hslToRgb(color.h, color.s, color.l), bgRgb) < min && guard++ < 60) {
color.l = Math.min(100, Math.max(0, color.l + dir * 1.5));
}
}

要点

  • 单向亮度调整:锁定色相、饱和度,仅移动明度,保持色彩身份;
  • 方向自适应:基于背景相对亮度判断前景应变亮还是变暗;
  • 迭代上限 60 步:极端情况下防止死循环,步长 1.5% 足以在 0~100 区间收敛;
  • 分级阈值:正文 4.5:1(WCAG AA),强调色对表层 3.5:1,强调色对背景 2.8:1(大号文本/装饰性元素放宽)。

5. 种子持久化策略:不持久化#

/* every load/refresh gets a fresh seed — no URL / storage restore */
applySeed(randomSeed());

决策依据

  • 生成式设计的核心体验是「偶遇」:每次打开皆不同,刷新即重生,书签/分享链接固化种子反而削弱惊喜感;
  • 种子栏即分享入口:用户若钟情某版面,可手工 Copy Seed,后续在种子栏粘贴复现,分享权归属用户
  • 避免 URL 污染#seed=...?seed=... 会干扰语义化 URL、SEO 与分享预览;
  • 隐私最小化:不写 localStoragesessionStorageCookie,无指纹追踪风险。

6. 响应式与容器查询的配合#

生成式令牌中包含 --container(容器最大宽度)、--space(基础间距)、--radius(圆角)、--border-w(边框宽度)等布局级变量。这些变量在 @media (max-width: 720px)@media (max-width: 420px) 中被媒体查询覆盖而非 JS 重算:

@media (max-width: 720px) {
.card, .block { padding: calc(var(--space) * 1.25) calc(var(--space) * 1.1); }
.item { grid-template-columns: 1fr; gap: 0.4rem; }
.item-go { display: none; }
.seedbar { bottom: 0.85rem; gap: 0.35rem; padding: 0.35rem 0.4rem 0.35rem 0.6rem; }
.seedbar-input { width: min(34vw, 8.5rem); min-width: 4.5rem; }
}

分工原则

  • JS 负责「设计语言」维度:配色、字体、比例、装饰层、阴影风格;
  • CSS 媒体查询负责「布局适配」维度:断点下的内边距、网格结构、交互元素显隐;
  • 两者正交,避免「JS 监听 resize 重算布局令牌」的性能陷阱与闪烁。

7. 装饰层 DOM 生命周期管理#

function genDecoration(p) {
var body = document.body;
body.querySelectorAll(".deco").forEach(function (el) { el.remove(); });
body.style.background = "";
body.style.backgroundImage = "";
body.style.backgroundSize = "";
// ... 依据 type 生成新背景或挂载新 .deco 元素
}
  • 每次 genDesign() 先清理旧 .deco,防止重掷种子时 DOM 节点累积;
  • 背景样式显式重置,避免 gradientdots/grid 叠加残留;
  • will-change: transform, opacity 仅在 .deco 上声明,内容层不受影响。

参考与延伸阅读#

  • Mulberry32 原作者:Tommy Ettinger, 「Mulberry32: A fast, high-quality PRNG」
  • FNV-1a Hash:Fowler–Noll–Vo, 「FNV Hash Functions」
  • WCAG 2.1 Contrast Requirements:W3C, 「Understanding Success Criterion 1.4.3」
  • Modular Scale:Tim Brown, 「More Meaningful Typography」
  • Generative Design Patterns:Hartmut Bohnacker 等, 「Generative Design: Visualize, Program, and Create with JavaScript in p5.js」
  • CSS Custom Properties for Design Systems:Lea Verou, 「CSS Variables: The Good, the Bad, and the Ugly」
  • prefers-reduced-motion:Eric Bailey, 「Revisiting prefers-reduced-motion」

这篇文章对你有帮助吗?
🐈
Hi,我是 YuBlogAI