2141 字
11 分钟
– 次浏览
– 位访客
换掉那只紫鸟:侧边栏头像与手写签名的折腾记录
AI 摘要
正在生成摘要…

侧边栏顶上那张头像,挂的是一张 AI 生成的紫色鸟。用了几个月,越看越别扭——不是说它丑,是它跟这个站的气质不搭。

同一时间我还惦记着另一件事:侧边栏那个名字,能不能换成手写签名。

两件事凑一起,就有了这一轮返工。过程比预想曲折:三换字体、两换方向,还挖出两个藏了很久的 CSS 问题。

第一回合:让 AI 再生成一张,还是去图库找#

原先那张紫鸟就是生成的。问题在于生成物总带着一股说不清的”平均感”——构图挑不出错,但也没有任何地方让人记住。再来一张大概率还是这样,所以换个思路:去公共图库找真人拍的图。

关键词锁定极简 / 抽象。下了三十张,第一轮把太花的、有明显主体的、一眼看着像壁纸的都筛掉,剩九张:

九张头像候选,第一轮筛完后按圆形裁切的形式展示

怎么从九张里定一张?我用了一个很实际的判据——站点主题色。

本站主色是 oklch(0.70 0.14 250),约等于 #53A3F2,一个偏亮的天蓝。手写签名要压在头像上,所以头像最好是低饱和的中性底,能把天蓝衬出来。按这个标准排,3 号(蓝紫幻境)和 6 号(青蓝流线)先出局:它们本身就是蓝紫调,签名压上去等于蓝叠蓝,糊成一团。

最后选中的是 9 号,黑白灰的抽象几何。理由是它几乎是纯灰阶,签名不管什么颜色都压得住。

第二回合:我按圆形裁切,但头像框是方的#

选完图,我做了件想当然的事:把九张候选全按圆形裁切,拼成对比图。上面那张图就是这么来的。

但这是错的——回头翻 Profile.astro 才看清,头像外面套的是 rounded-xl + overflow-hidden,配 object-cover 填一个 256×256 的方框。是圆角方形,根本不是圆形。

后果很实际:那九张圆形预览完全不能代表真实观感。一张靠斜向线条构图的图(比如选中的 9 号),裁成圆形后只剩中间一坨,看不出原来的走向;反过来,方框下会露出来的四个角,在圆形预览里也全被切掉了。

重写预览脚本,按真方框、真圆角、真尺寸渲染:

侧边栏头像在亮色与暗色主题下的真实方框效果,签名压在头像下缘

裁切落地就一条命令,源图 → 中心裁 1:1 → 640×640 PNG,体积从 378KB 降到 192KB:

Terminal window
node .workbuddy/tools/set-avatar.mjs <源图> public/avatar.png

这里还有个容易忽略的细节:object-cover 是从中心裁的,所以选图时画面重点必须落在正中,否则会被裁掉。九张候选里就有两张主体的位置偏下,方框一裁只剩背景。

第三回合:签名换了三次字#

先说我想要的机制。签名不是文字,是一张透明底的单色图,视觉上压在头像下缘。显示时用 CSS mask-image 指向这张图,再给元素填 background: var(--primary):

<span class="signature-mask" style={`mask-image: url('${signature}')`}></span>
.signature-mask {
position: absolute;
inset: 0;
background-color: var(--primary); /* 图只负责形状,颜色由主题色决定 */
-webkit-mask-size: contain;
mask-size: contain;
-webkit-mask-repeat: no-repeat;
mask-repeat: no-repeat;
-webkit-mask-position: center;
mask-position: center;
}

好处是图只管形状、颜色由主题色决定。哪天改主题色,签名自动跟着变,不用重新出图。

接下来是选字,一共换了三轮:

  1. 华文行楷「留心」——系统自带。落在图上太”印刷”,笔画粗细几乎一样,没有飞白,看着像从字帖上截下来的。
  2. 志莽行书——比行楷活,但两个方块字撑不起一条签名,字形也偏”设计字体”。
  3. 英文 Watch Your Back——最后定的。理由很实际:侧边栏文字栏是横长的一条(240×47),两个中文字放进去,左右全是留白;换成西文连笔,正好横向铺满,内容也和站点 name 对得上。

字体选 Great Vibes。它的笔画是 hairline 级的细,直接压在照片上几乎看不见,所以生成时给它描了一道同色的 0.8% 细边当”加墨”:

九款英文手写体在真实尺寸下的对比,各带宽度与加墨参数

生成脚本走的不是文字渲染,而是取字形轮廓:opentype.js 取 path → 拼成一条 SVG path → sharp 按 300dpi 渲染 → 紧贴笔画外框裁切 → 输出 webp。这样字距、裁切边距、笔画粗细全部可控,也不受机器上装没装这个字体影响。

NOTE

这里踩了个 ESM 的坑:opentype.js 的 dist/opentype.js 是 UMD 包,在 ESM 下 import 会报 self is not defined;它的 loadSync() 内部走 require('fs'),ESM 下同样用不了。必须换成 dist/opentype.module.js,再自己 fs.readFileSync + opentype.parse()。

落款:短横 + 一句话#

到这一步其实就能上线了。真正让我决定再改一轮的,是一张手写题款的图:主体字下面跟一条短横,再下面一行小字。三层一凑,签名才像”落款”,而不是”贴了张字上去”。

于是补上:签名 → 短横(主题色)→ 一句话(取 profileConfig.bio):

签名、短横、小字三层结构的最终效果

落点(原生 px,侧栏宽 280、文字栏宽 240):

元素位置(相对头像下缘)
签名底边+23.7
短横(32×4,主题色)+33.7
小字+45.7

代价是卡片高度从 328.8 涨到 396.7(+68px),多一横一句的必然结果。

期间挖出来的两个真问题#

一、短横和简介”本来就在”,只是被头像盖住了#

动手前我以为要给卡片添加短横和简介。翻 DOM 才发现这两行一直都在,只是完全看不见。两个原因叠在一起:

  1. 层级——头像外面套的是 <a class="relative">,是定位元素;短横和简介是普通 static 元素。CSS 绘制顺序里定位元素盖在普通元素之上,所以头像把它们挡住了。
  2. 位置也不对——签名图当时挂着 margin-bottom: -40%,把下方元素往上拽了 96px,短横实际落在头像内部(下缘往上 33px)。等于双重看不见。

修法是给这两行加 position: relative; z-index: 60 抬到头像之上,再把 margin-bottom 从 -40% 调成 -12.125%,让它们落在签名正下方:

.signature-rule,
.signature-caption {
position: relative;
z-index: 60;
}

二、margin 的百分比,基准是”宽度”不是”高度”#

这是我自己记错过的一个点。签名浮层用了两个位移:margin-bottom: -40% 和 transform: translateY(-75%)。我一度以为加起来就是”上移自身高度的 115%“——错了。

  • margin 的百分比,基准是包含块的宽度。哪怕设的是 margin-top / margin-bottom 这类纵向属性,也照样按宽度算。
  • translateY 的百分比,基准才是元素自身的高度。

两者基准不同,不能相加。算签名落点得分开算:文字栏宽 240 → 签名图高 = 240 × 273 / 1400 = 46.8px,translateY(-75%) 上移 35.1px。基准搞错,预览图会偏上十几像素,而且很难发现——因为拉不出明显错误,只是”感觉有点高”。

几个能复用的小工具#

这一轮攒下几个不用开 dev server 就能验效果的脚本:

脚本干什么
gen-signature.mjs取字形轮廓生成签名图,参数:文字 / 字体 / 字距 / 加墨
set-avatar.mjs任意图 → 中心裁 1:1 → 640×640 PNG
preview-avatar.mjs按真实方框 + 真实位移渲染侧栏头像效果(亮/暗两套)
preview-signature-block.mjs按 CSS 盒模型算落点,渲染落款三层结构

好处是不依赖浏览器:改完样式直接出 PNG,比开 dev server 快得多,也不会因为”本地看着对”就放过一个只在真实宽度下才暴露的偏差。

一点体会:这一轮真正花时间的不是写代码,是把”看起来对”变成”量出来对”。圆形预览、记错的 margin 基准、被盖住却以为不存在的元素——三个坑同一个来源,拿感觉代替了测量。

相关阅读#

AI 参与程度
润色
完全
不使用
换掉那只紫鸟:侧边栏头像与手写签名的折腾记录
https://blog.142588.xyz/posts/sidebar-avatar-signature/
作者
Watch Your Back
发布于
2026-09-13
许可协议
CC BY-NC-SA 4.0

评论

加载中…