占位图 / 头像

生成 SVG 占位图与首字母头像,配色稳定可复现。

这一页有两种模式:占位图与首字母头像,输出的主体都是一段现拼出来的 SVG 字符串,生成这一路不碰 Canvas(预览是 <img> 直接吃 data URL,只有 PNG 导出才建画布、才用 objectURL,全程不发请求、没有上传)。它的立意在确定性:代码里没有 Date、没有 Math.random,同样参数两次调用得到字节完全相同的串,图案那份「随机感」全部来自名字哈希——哈希是 FNV-1a 32 位,伪随机是 xorshift32,只用位运算。配色公式是写死的:色相 = FNV-1a(名字) % 360,底色固定 HSL(色相, 58%, 42%),字色在 #ffffff 与 #111111 之间按 WCAG 相对亮度取对比度更高的一边。

字号与字宽都是近似:度量按 Helvetica/Arial 与 Georgia 的 AFM 值加分档兜底,不是排版引擎的真量,中文与 emoji 在别人机器上的真实宽度由对方命中的字体决定。3:1 那条线稳过,正文 AA 的 4.5:1 并不总达标,具体名字得实测;关掉「配色由名字哈希派生」手填颜色时完全不经过哈希,也不做对比度体检。放不下时页面不玩静默截断:照下限出字并把「会被 SVG 视口裁掉」写在提示条里。底色与字色都会被强制压成不透明(alpha 被丢掉),生成串里那张背景 rect 永远在,所以没有透明底这一档;SVG 只写 font-family 的族名、不嵌任何字体。PNG 那一步的前提是 Canvas 允许把这张 SVG 画进去,个别浏览器会把内嵌 SVG 的栅格化当跨源拒绝,真被拒时按钮旁写的是原因而不是静默失败。这一页没有复制按钮,也不登录、不收藏。

占位图 / 头像怎么用

  • 选模式填参数:参数不在首屏横铺三大行,而是收成右侧一条 250px 细轨,按「尺寸/文字/颜色/图层与描边」四组竖排(头像那轨是「尺寸/字母/图案/颜色」)。占位图模式给宽、高、文字(留空或纯空白就退回尺寸串)与文字来源开关、字体三家、前景色与底色、四个图层开关、不改字开关以及字号上下限;宽高下面有六颗预设按钮 1920×1080、1280×720、1080×1080、1200×628、640×480、375×667,按一下填进宽高——它们只是本站的排版习惯起点,不是任何规范规定的尺寸,当前宽高正好等于某一档时那颗按钮会呈选中态。头像模式给名字、边长、四种模式、三种外形与圆角半径、字母数 1 或 2、是否大写、单词是否取前两字符、是否在图案上叠字母、配色是否由名字派生和两个颜色框。图层四张各自独立:网格步长 20px(默认关,600×400 上是 29 条竖线加 19 条横线)、对角交叉盒(默认开)、标尺步长 50px 且每 5 格一条长刻度并标数字(默认关,0 不标,用来一眼看出图被拉伸了多少)、边框 rect 内缩 0.5px(默认开)。输入框里的宽或高填 0、负数、NaN 会整条回落到默认的 600×400(那是输入层的兜底,不是夹小),只有小于 1 的正数才真被夹到 1×1,比如 0.4,文本随之变成 1x1。头像边长下限 8px,填 0、负数、NaN 会先回落到 128(所以 -50 不是夹到 8 而是变 128),填 4 才走夹下限这一路得 8,128.6 取整成 129。模式四选一:纯首字母、几何图案(族由 FNV-1a(名字) % 9 决定)、同心环、竖条;九族是 rings/bars/diagonal/triangles/dots/chevron/quadrants/wave/blocks,思路承接 identicon 与 boring-avatars 那一系,名字定生死——KeepCoder 用户 落 chevron、张三 落 rings、ada 落 bars,扫两千个名字九族都会命中,换个名字整套图形就换一套,因为图案里的伪随机取自 FNV-1a(名字:族名)。pattern 模式默认不写字母,勾了「图案上叠字母」才叠上去。
  • 看读数再决定:画布像素数、1:1 还是几倍显示、以及这一串的字节数写在预览卡标题行;「实际字号与用的文字」(头像那侧给字母、底色、字色与图案族)写在「SVG 源码」那条折叠标题的右侧,折叠收起也读得到;SVG 源与 data URL 两个字节数及比值写在「现成片段」的折叠标题上,比值通常大于 1,因为 < > 双引号 这类字符每个要花三个字符。配色行把「色相 ← FNV-1a % 360、底色 HSL(色相,58%,42%)、白字与深灰字两个对比度取高的」整条推演写出来,放不下字时提示条点名说会被视口裁掉。预览只有一格:1:1 按画布像素原样摆(最多 420px 高,摆不下在这层滚动、绝不缩图),左上角标写「1:1 实际尺寸」;想确认被放大几倍是什么观感,点标题行那组「1:1|按倍率放大」分段按钮切同一张图的显示,放大视图按 PNG 导出的倍率摆、只用来观察(显示夹在 4000 CSS 像素内,标题行同时写出 PNG 下载的真实像素尺寸),真正的栅格化仍然发生在下载那一刻。预览底下垫的棋盘格是纯 CSS 渐变,只表示「这块有底」,不代表结果是透明底。
  • 取值与下载:输出有三块只读文本:SVG 源码、<img src="data:image/svg+xml,…" alt="…" width height loading="lazy"> 形式的 HTML、以及 background-image 加 background-repeat: no-repeat 加 background-size: cover 三行 CSS;这里的 data URL 走百分号编码而不是 base64,宽高是从生成好的 SVG 串里回读的,所以勾了不改字时片段里给的是加宽后的数。「SVG 源码」与「现成片段:data URL 的 <img> 与 CSS background」是两条默认收起的折叠,展开后都是只读文本框,自行选中复制,本页没有复制按钮,也不登录、不收藏。下载有两个:下载 SVG 用 Blob([svg], {type:"image/svg+xml"}) 存成不带尺寸的固定名 placeholder.svg 或 avatar.svg;下载 PNG 按 1 到 4 倍率(默认 2×,即 @2x)把宽高各乘倍率建画布、drawImage 那张 data URL 图片、toBlob 成 PNG,文件名形如 placeholder-1200x800.png、avatar-256x256.png,objectURL 四秒后回收——被拒时改用 SVG 下载即可,占位图本来就该用矢量。两个按钮跟着倍率选择放在预览卡下面那一行,不用先展开代码;生成全程在浏览器内存里做字符串拼接,PNG 走本地 Canvas,不发请求、没有上传。
  • 字号与基线是怎么算出来的:占位图这一路的数学全是显式算的。默认 600×400:内边距取 max(8, round(min(宽,高) × 6%)) = 24,文本用尺寸串 600x400,七个字符按 Helvetica/Arial 的 AFM 值 0.556 em 估宽,反解出字号 141.83,基线不取 em 方框而取帽高——sans 0.716(Arial 的 CapHeight 716)、serif 0.70、mono 0.585,基线 = 中心 + 帽高 × 字号 ÷ 2,即 250.78,这样大写字母才真看着居中。字号自适应是「按宽度反解 → 按帽高夹住高度 → 夹进上下限」三步,默认 12 到 160,输入 0 或非数字回落到默认。放不下时页面不玩静默截断:它照下限出字并把「字号已到下限 12px,字符串仍比可用宽度宽:会被 SVG 视口裁掉」写在提示条里,同时给一条出路,即勾「不改字:宁可加宽画布」——那时字号回到按高度算的档位、画布加宽到放得下为止(60×40 配 ABCDEFGH 会加宽成 204×40,输出 SVG 的 width、viewBox 与片段里的 width/height 一起跟着改),也就是说尺寸和你填的不一样了,这一条同样写在提示里。
  • 字母抽取与外形:首字母抽取的顺序值得看:先剥 emoji(Extended_Pictographic、变体选择符 FE00–FE0F、零宽连接符 200D、肤色修饰 1F3FB–1F3FF 以及 1F000–1FAFF 整段),邮箱只取 @ 之前的本地部分(dev@keepcoder.top 给 D 而不是 DT),再按一整列分隔符(空格、下划线、连字符、点、斜杠、反斜杠、竖线、逗号分号冒号、各类引号与括号、加等号星号&^%$#@!?<>~`)切词:两个及以上词取首尾两词的首字符(张三 李四 → 张李,a.b-c → AC,a_b/c[d]e → AE),单个词默认只取一个字符、勾了「单个词取前两字符」才取两个(Madonna → M 或 MA),全空、纯空白、纯 emoji 都落到 ?;字母数可设 1 或 2,大写走 toLocaleUpperCase。外形三档用 clipPath 实现:圆形是 <circle>,方形 rx=0,圆角方的半径夹在 0 到边长一半之间(1000 → 64,-8 → 0);clipPath 的 id 把边长、外形与半径都编进去(kcap128c、kcap128s、kcap128r24),这样同页并排放两个同尺寸不同外形的头像时不会互相抢裁剪路径(否则方块会被前一个的圆裁圆)。图案层只用 rect、circle、path 三种元素,不写 text、g、polygon、filter、url( 与 href,颜色全来自同一族 tint 公式(色相 + i×37,饱和度 0.5 + (i%3)×0.12,亮度 0.3 + (i%4)×0.13),所以串短、任何渲染器都认。

常见问题

同一个名字为什么每次都是同一个颜色?换台机器还一样吗?

因为颜色不是随机数而是哈希:FNV-1a 32 位吃的是名字的 UTF-8 字节,色相取模 360,底色的饱和度与亮度写死成 58% 与 42%,图案层的伪随机是 xorshift32 且种子同样来自名字哈希——整条链路没有读时间、也没有读 Math.random,所以两次生成字节相同、跨机器跨时刻也相同(测试把随机器与计时器换成抛异常的实现来锁这一条)。哈希是 FNV-1a 32 位(offset basis 0x811c9dc5、prime 0x01000193,逐字节异或后乘),对名字的 UTF-8 字节做散列,官方公开的向量空串得 0x811c9dc5、a 得 0xe40c292c、foobar 得 0xbf9cf968,测试另写了一版 BigInt 实现逐字符串比对,因此同一个名字在任何机器、任何时刻都是同一个颜色与同一套图案。伪随机是 xorshift32,只用位运算,种子为 0 时换成常数 0x9e3779b9 而不是停在 0。会变的是字而不是色:SVG 里只写 font-family 族名、不嵌字体,对方机器缺同款字形时字母宽度与落位会跟着本机的替代字体走。

我写的中文文案在别人机器上溢出了,是谁算错了?

都不是,是两套近似叠在一起。本页估算宽度用的是 Helvetica/Arial 与 Georgia 的 AFM 值加档位兜底:CJK 与假名按全角 1 em、emoji 方块按 1.2 em、等宽恒 0.6 em、其余按大小写分档,这套数不是真排版结果,而真实渲染宽度由对方机器命中的那个字体决定。三条出路按代价排序:勾「不改字」让画布自己加宽(尺寸会变,提示条会写清从多少加宽到多少)、把字号下限调低(放得下的判据是按宽度反解出的那个数不低于下限,下限越低允许排出的字就越小)、或者别写字——图案族与纯色底那一档完全不受字体影响,要严格一致就改用文字外描路径的方案。

头像上的字够对比度吗?能不能给我一张透明底的?

自动配色那一档可以说得准:底色锁在 HSL(色相, 58%, 42%),字色在白与 #111111 里取对比度更高的那个。把 360 个色相全扫一遍:取高的那一侧最差落在色相 30°(底色 #a96b2d)的 4.3493:1,此时白字 4.3416:1、深灰字 4.3493:1 只差 0.008,必须比过才决定用哪个;而「一律用白字」是错的——色相 60°(#a9a92d)白字只有 2.4937:1,连 3:1 的图形与组件门槛都不到。所以 3:1 那条线稳过,而正文 AA 的 4.5:1 在最坏那几个色相上差一点点、不能一概保证,具体名字得实测;手填颜色时不经过哈希也不做对比度体检,得自己看。透明底做不到:底色与字色都会被压成不透明(alpha 丢弃,写法认不出时回落默认色),生成的 SVG 里那张背景矩形总是存在,导出的 PNG 因此也是铺了底的。

字宽估算是按什么数取的?

宽度估算的可信度要摊开讲:表内字符按字体分开取值(Helvetica/Arial:空格与句点 0.278、连字符 0.333、数字 0.556、i 与 l 0.222、m 0.833、w 0.722、% 0.889;Georgia:空格 0.248、句点 0.28、连字符 0.32、i 与 l 0.264、数字 0.536、m 0.906、w 0.848),表外字符按档位给(大写 sans 0.7、serif 0.72,小写 0.53、0.5,CJK 与假名从 0x2E80 起按全角 1 em,emoji 方块从 0x1F000 起给 1.2 em 且这条判定排在 CJK 之前,等宽一律 0.6 em),兜底档是 sans 0.55、serif 0.52、mono 0.6。它是估算,不是排版引擎的真量。

文本和颜色里写了尖括号、引号或 emoji 会怎样?

文本与颜色的转义走同一套 XML 规则:& < > 双引号 单引号 五对实体,控制字符除制表、换行、回车之外剔除,孤立的 UTF-16 代理整个丢弃(留着会让 encodeURIComponent 抛 URIError),成对代理即平面外 emoji 原样保留,而 % 不在这一层动——那是 data URL 那一层的事。

对应功能编号 #139无需安装,手机端同样可用按 Ctrl + K 换工具