Files
SpineWallpaper/.scratch/wallpaper-optimization/issues/04-aspect-adaptation.md
T
Shuery eb56efbd9e feat: 比例适配、bgm 音源选择与 fps 限流,打包 v3
取景(issue 04)
- 新增 viewport-fitter.js:让立绘层缩放跟随背景图的 cover 缩放——背景放大多少倍、
  特效就放大多少倍。原先背景走 cover、立绘层走 contain,比例一变两者就分叉。
  16:9 下与作者设定逐像素一致(kv37 为 0 差异)。实测证伪了"contain 会裁掉作者视口"
  这个最初的判断:contain 在任何比例下都完整含住作者视口。
- 应用方式为原地改写 config.viewport 字段后调 setViewport(),不重建播放器;
  维护挂在 config.frame 上按画布比例增量进行(config.success 早于 setViewport,拿不到视口)。

WE 属性(issue 05)
- project.json 新增 bgm combo(所有壁纸音源的并集 + 「随预设」);所选项不属于当前壁纸时
  音频回落该壁纸默认,选择本身保留。
- fps 限流包裹实例 drawFrame 并接管排程。修掉两个实测缺陷:朴素丢帧会把 rAF 抖动放大成
  整拍丢失(目标 30fps 实测只有 22.5fps);fps:0 被真值判断吞掉导致无法取消限流。

打包(issue 06)
- project.json version 升至 3 并更新描述;preset 的 combo 取值保持 xilian/kv37 不变。
- preview.gif 821 KB → 347 KB(每 2 帧取 1 帧 + 128 色不抖动,逐帧与源帧像素完全相同)。
- index.html 加内联 favicon,消除 CEF 自动请求 /favicon.ico 造成的 404 报错。

其它
- dist/scripts/wallpaper-engine.js 移到 tools/:它没有任何入口引用,且环境判据依赖真实 WE
  并不存在的 wallpaperRegisterListener,一旦按它自己的说明挂进 index.html 会在生产环境自我激活。
- 修正死文件检查:原检查会拿文件名匹配到文件自身的内容,使这类文件漏网。
- 新增离线算式验证与 resize / 属性 / 全量验收三套浏览器测试脚本。
2026-09-19 20:29:25 +08:00

69 lines
4.2 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 04 比例适配:按容器比例重算视口
Status: resolved
## 问题
viewport 以等比 "contain" 映射到画布:单轴贴边、另一轴多露出世界。
原本的判断是"负 padding 只在目标比例下成立,其它比例会裁掉主体"——**实测证明这个判断是错的**,
真正的病根是另一件事。
## 诊断(实测,非推测)
先量清楚事实,再改代码:
1. **contain 永远不会裁掉作者视口**。可见矩形是宽高比等于画布、且恰好含住作者视口 R 的最小矩形,
所以 R 在**任何**比例下都完整可见。比例不同只是"另一轴多露出世界":
- 21:9 → 6453×2701(竖轴贴边、横轴多露)
- 16:9 → 6050×3403
- 9:16 → 1914×3403(横轴贴边)
三种比例下 `coversView` 全为 true,"裁掉作者视口"这条假设不成立。
2. **这不是我重构引入的回归**。基线 `d8c7e49` 的 `preset-store.js` 里 xilian 的四个 pad
(-25% / -28% / -30% / -23%)与 kv37 的 `x: -1215, y: -410, w: 2048, h: 1024` 与现在一字不差;
基线的容器也是 `#spine-container` 绝对定位铺满、body flex 居中。取景语义未变。
3. **真正的病根:背景走 cover,立绘层走 contain,两者缩放分叉。**
`body { background-size: cover }` 会按需放大裁切,立绘层却按 contain 固定世界矩形。
以 1080×1920 竖屏为例,背景被放大约 1.78 倍,立绘层的世界高度却纹丝不动——
特效于是和背景插画脱节,"多余世界"其实看不太出来,脱节才看得出来。
4. **spine 资源路径是 `./effects/*.json`:立绘层是特效层,背景插画才是主体。**
所以判定标准不该是"特效不被裁切"(粒子飘出画面本来就正常),
而应是"特效与背景插画始终同倍率、同中心"。
## 做法
新增 `dist/scripts/viewport-fitter.js`(纯函数)与 `SpineController` 的取景维护:
- 参考比例 `16/9`:先让播放器自行解出作者视口 R,换算成参考比例下的可见矩形 V0。
**这个比例下与作者设定逐像素一致**。
- 其它比例:让立绘层的像素缩放**跟着背景的 cover 缩放走**——
`V.h = V0.h × coverScale(参考比例) / coverScale(当前比例)`,中心不变。
于是背景放大 1.78 倍时,特效也放大 1.78 倍。
- 应用方式:原地改写 `config.viewport` 的 x/y/width/height 与四个 pad,再调
`player.setViewport(动画名)`,并把 `viewportTransitionStart` 推到足够久以前(避免 0.25s 插值拖影)。
绝不整体替换 `config.viewport`:`setViewport()` 会无条件读 `config.viewport.animations[name]`,
换掉整个对象会在 spine-player 第 14973 行崩溃。
- 维护时机:挂在 `config.frame` 回调上(它在每帧相机计算**之前**被调用),以画布比例作键——
比例没变就是一次浮点比较后返回,比例变了就地重算。
`config.success` 用不了:它在 `setViewport` 之前触发,那时 `currentViewport` 还不存在。
- 预设可用 `framing: "author"` 退回作者原始的 contain 行为。
## 验收
| 项目 | 结果 |
| --- | --- |
| 参考比例回归 | kv37 **逐像素 0 差异**;xilian 仅 1/255(浮点末位舍入) |
| 立绘/背景缩放同步 | 21:9 / 16:9 / 16:10 / 9:16 × 两个预设,**全部精确相等**(离线算式验证 + 渲染实测) |
| 渲染实测吻合预测 | 三种比例可见矩形与公式预测逐值一致(pads 全 0、base == resolved) |
| 对拍留档 | `tools/shots/fit/` 下 6 组(2 预设 × 3 比例)改动前后各一套(该目录按仓库约定不入库,可随时重跑重生成) |
| 不重建播放器 | 9 次改视口 + 连续 20 次压力往返,实例身份标记**从未丢失** |
| 连续 resize 内存 | 强制 GC 后 7.9 MB → 8.0 MB(增长 **0.04 MB**) |
| 回到 16:9 可逆 | 取景完全回到原值,无漂移 |
| 控制台 | 0 报错 / 0 未捕获异常 |
复现:`node .scratch/verify-fitter.mjs`(离线算式)、`node .scratch/test-resize.mjs`(resize 行为)。
## 附带修复
Chromium 会自动请求 `/favicon.ico` 并在控制台留下 404。已在 `dist/index.html` 加
`<link rel="icon" href="data:," />` 消除,使"0 console 报错"这条验收真正可达成。