1454 字
7 分钟
页面浏览--访问次数--
拼图纹理、角色喵声与默认皮肤:这轮站点打磨的零额外负担做法

缘起#

首页复刻完之后,剩下一堆「看着别扭但说不上哪里错」的小问题:首屏底纹只活在一个页面里、近期文章和动态两列高度不齐、页脚备案号挤成一团、卡通人物点下去毫无反馈、关于页少写了一个项目。

这篇记录这几处打磨,以及最后把 mmz 设成默认皮肤的做法。

一、背景纹理:把首页的拼图搬进「显示设置」#

首页的拼图底纹原本写死在 mmz 皮肤的 body 上,只有首页能看到。这次把它提升成通用能力:

  • src/styles/textures.css 定义纹理预设;
  • TEXTURE_PRESETS / TexturePreset 给出类型与白名单;
  • Layout.astro 首帧把 data-texture-preset 写到 <html>;
  • DisplaySettings.svelte 的「外观 → 背景纹理」提供切换入口;
  • 文案统一走 i18n,十个语言模块同步补齐。

七种预设立在那儿:无、网格、点阵、噪点、斜纹、波纹,以及这次的拼图。

显示设置里的背景纹理
外观分区的背景纹理,七种预设可以即时切换

拼图案为什么直接内联成 data URI#

首页的拼图蒙版是一个周期 224px 的 SVG:112×112 的 pattern 里排两块 56px 的拼图块,卡榫与凹槽交替。为了保证「显示设置里的这个背景」和首页看起来是同一张图,纹理预设没有去重画一遍,而是把同一份路径数据内联进 textures.css:

html[data-texture-preset="jigsaw"] #m3e-texture-canvas {
-webkit-mask-image: url("data:image/svg+xml;...");
mask-image: url("data:image/svg+xml;...");
mask-size: 224px 224px;
}

好处是不多发一个网络请求,也不会因为图片加载晚到而先闪一下空白;代价是 CSS 里多了一段长字符串,所以只给拼图这一个「和首页同源」的预设开了特例。

纹理画布:所有页面共用一层#

mmz 皮肤原来有一张自己的 body 底图,还会把通用纹理层藏起来。这轮把两处都删了,所有页面统一走 #m3e-texture-canvas。切页时 Swup 只换内容,纹理层在持久外壳里,不会重渲染,也就不会有「切页纹理闪一下」的问题。

文章归档页的拼图纹理
归档页也铺同一张拼图纹理,卡片与文字直接压在纹理上

默认预设同时改成了 jigsaw,新访客第一次进来看到的底纹就和首页一致。

二、卡片等高与白色底#

「近期文章」和「我的动态」两列高度不齐,根因是两边都用内容撑高,列表长度不同就错位。改成两列等高 + 面板内部纵向 flex:

.mmz-afterglow__col { display: flex; }
.mmz-afterglow__panel { display: flex; flex-direction: column; flex: 1; }
.mmz-afterglow__list { flex: 1; overflow-y: auto; max-height: 25rem; }

白底则是另一回事:面板默认吃了卡片底色,压在拼图纹理上就是一块突兀的白。这次把面板底色去掉,只留细描边,纹理透上来之后和首屏观感接上了。

三、角色替换:等比缩放 + 透明补边#

新头像是 1504×1664 的透明 PNG,而角色槽位是 1:1 的正方形。直接塞进去会被裁掉耳朵,强行拉伸又会变形。

做法是「等比缩放 + 横向透明补边」:先按高度缩到 1254,再在左右补透明边凑成 1254×1254,导出 AVIF。尺寸和原槽位完全一致,所以布局一行没改,实测渲染仍是 192×192。

顺带验证了一下透明边缘:把图 flatten 到品红底上看,没有白边残留。

四、点一下,喵一声#

参考站的角色点下去会有反馈,这里补上的是声音。第一版踩了个坑:

const src = await resolveAsset("themes/mmz/assets/dialogue/meow.mp3");

resolveAsset 只对图片做 glob,音频会被原样返回成相对路径,浏览器直接 404。改用 Vite 的资源导入:

import dialogueSoundSrc from "../assets/dialogue/meow.mp3?url";

再把结果通过 data-dialogue-sound 传给控制器。播放策略按零额外负担处理:

  • 音频对象第一次点击时才创建,没点过就不发请求;
  • 每次点击 currentTime = 0 重头播,音量 0.55;
  • play() 被自动播放策略拦下来时静默忽略,换词照常进行;
  • destroy 时 pause() 并置空。

音频本身是 Windows 中文 TTS 生成的一声「喵~」,再用 ffmpeg 裁掉尾部静音、升调、淡出,最后是 0.348 秒、3.5KB 的 mp3,内置在主题资源里。

点击角色会换台词并播放一声短喵
对话框出现在角色旁边,点击换下一句台词并播放短喵

五、默认皮肤:mmz#

把 mmz 变成默认皮肤,改的不是代码而是配置。本项目的配置是「内容仓覆盖」模式:内容仓里的 config/site-theme.yaml 是来源,src/user/user-config.ts 是 content:sync 的生成物,不能手改。

shirone-content-fq/config/site-theme.yaml
enable: true
defaultTheme: mmz

改完跑一次同步:

Terminal window
pnpm.cmd content:sync

同步后重新开会话实测:data-site-theme="mmz"、首页命中 mmz 变体、壁纸模式为 none。访客仍然可以在显示设置里切回默认皮肤,只是「第一次进来看到的」变成了 mmz。

六、关于页:把 Shirone 也写上#

关于页原本写「借鉴了两位大佬」,但真正支撑整站组件体系的其实是第三个项目。这次补成三位:

  • MmzMing 的博客——起步启蒙;
  • CuteLeaf 的 Firefly——交互与布局的参考;
  • LyraVoid 的 Shirone——现在这套 fqzlr 博客真正落地的主题底座。

三家都用 ::github{repo="..."} 生成仓库卡片,构建期抓取,运行时不发请求。

关于页新增的 Shirone 仓库卡片
关于页的致谢补到三位,Shirone 的仓库卡片由 ::github 构建期生成

收尾#

这一轮没有大功能,全是「把已有的东西收干净」:纹理从单页变成站点能力、两列卡片对齐、角色有了自己的脸和声音、致谢写全、默认皮肤换成自己最满意的那个。

原则还是那条:可选的东西默认零负担。纹理能关、声音没点过就不加载、仓库卡片构建期抓完就静态化——关掉它们时,站点不该多一行 DOM、一个请求、一字节包体。

全部改动走 npx.cmd astro check(0 errors)+ Playwright 实测 + pnpm.cmd build 验证后上线。

拼图纹理、角色喵声与默认皮肤:这轮站点打磨的零额外负担做法
https://blog.fqzlr.top/posts/tech/texture-character-default-theme/
作者
fqzlr
发布于
2026-11-01
许可协议
CC BY-NC-SA 4.0

分享文章

生成精美分享图或复制链接,与更多人分享本文。

继续阅读

沿着主题读

基于共同的标签与分类

换条路线

从其他文章中稳定抽取