132 lines
9.5 KiB
Markdown
132 lines
9.5 KiB
Markdown
# 小工具性能预算与降级规范
|
||
|
||
> 本文提供移动端 WebView 的设计预算与降级要求。Skill 只能检查静态产物和代码设计,不能从源码推断真实帧率、内存或真机表现;没有运行数据时必须明确标记“未实测”。
|
||
|
||
## 目录
|
||
|
||
- §1 交付门禁
|
||
- §2 静态数据与长列表
|
||
- §3 图片、音频与视频
|
||
- §4 WebGL 资源与降级基线
|
||
- §5 运行时降级与兜底
|
||
- §6 交付检查
|
||
|
||
---
|
||
|
||
## 1. 交付门禁
|
||
|
||
| 项目 | 门禁 | 原因 |
|
||
| --- | --- | --- |
|
||
| 最终 zip | **不超过 10 MiB**;建议不超过 2 MiB | 10 MiB 是上传上限,不是性能目标 |
|
||
| 单个 `.html` / `.css` / `.js` / `.json` | 不设硬上限;超过 2 MiB 触发风险提示 | 大源码会增加读取、解析、编译和峰值内存;即使压缩后很小也一样 |
|
||
| 上述文本文件解压后合计 | 不设硬上限;超过 5 MiB 触发风险提示 | 用于发现把数据库或生成产物塞进代码包的情况 |
|
||
| 单条 Base64 | 解码后不超过 1 MiB;超过 100 KiB 提示风险,超过 1 MiB 视为大 Base64,必须改成独立包内文件 | Base64 体积约增加 1/3,还会产生字符串与解码副本 |
|
||
|
||
先检查可用运行时,有哪个就用哪个;两个脚本均为单文件且只使用各自标准库,不要为了审计临时安装 Node、Python 或第三方依赖:
|
||
|
||
```bash
|
||
# 有 Node 18+:脚本零第三方依赖
|
||
node <skill目录>/scripts/audit_artifact.mjs ./dist
|
||
node <skill目录>/scripts/audit_artifact.mjs ./tool.zip
|
||
|
||
# 有 Python 3:脚本只用标准库,并可检查 zip 内部文本
|
||
python3 <skill目录>/scripts/audit_artifact.py ./dist
|
||
python3 <skill目录>/scripts/audit_artifact.py ./tool.zip
|
||
```
|
||
|
||
两个运行时都可用时任选其一;Python 版可额外读取 zip 的文件清单与解压后体积。Node 版对产物目录做完整体积审计,对最终 zip 只检查包体,因此必须在压缩前先审计目录。脚本只依据文件系统和 zip 元数据做确定性检查:zip 超过 10 MiB 报 `ERROR`;zip 超过建议的 2 MiB、单个或合计文本较大报 `WARN`。
|
||
|
||
脚本不解析或猜测 JS / HTML / CSS 语义。Base64、WebGL 结构与降级策略由本规范约束,并在 §6 按代码逻辑审查。仓库发布检查会把两个脚本复制到无项目依赖的临时目录实际执行;依赖 pip / npm 包或相对模块时发布失败。
|
||
|
||
如果 Node 与 Python 都不可用,仍须人工完成 §6 清单,并至少:查看最终 zip 文件大小;确认 Base64 资源的生成来源与实际字节数;检查最大的 HTML / CSS / JS / JSON 是否混入大型数据;逐项检查 WebGL 预算与降级。交付说明中标记“未运行自动审计”及人工检查结果,不得直接声称审计通过。不要通过改后缀或运行时解压字符串来隐藏大数据,这只会把开销推迟到运行时。
|
||
|
||
---
|
||
|
||
## 2. 静态数据与长列表
|
||
|
||
`.js` / `.json` 是静态资源,不是数据库。**不要把数万条记录、完整业务库、日志或抓取结果生成成 JS 数组。** JSON 与 JS 分文件只能改善组织,不能消除下载、解析和内存成本。
|
||
|
||
优先按以下顺序缩减:
|
||
|
||
1. 只保留完成核心功能必需的字段和记录,删除重复字段、长描述和历史快照。
|
||
2. 能预计算的统计、搜索索引和分类结果在构建期生成;不要在首屏对全量数据反复遍历。
|
||
3. 大型只读数据集改为摘要、分段样例或让用户通过 `<input type="file">` 按需导入。若完整离线数据不可删且仍超门禁,应明确说明该需求不适合小工具,而不是继续打包。
|
||
4. 用户产生或导入的数据写入 IndexedDB;IndexedDB 用于运行期持久化,不用于掩盖巨大的内置种子数据。
|
||
|
||
渲染列表时:
|
||
|
||
- 首屏只创建可见项;长列表使用分页或虚拟滚动,不一次性拼接整份 `innerHTML`。
|
||
- 搜索输入按交互频率和数据规模采用防抖,避免每次按键都触发完整查询;最终输入应及时执行。数据量较大时可预先建立小型索引,避免反复扫描所有字段。
|
||
- 非首屏工作分批执行并主动让出主线程,避免把解析、计算和渲染集中在同一个同步流程中。
|
||
- 不在循环中反复读写布局属性;批量生成 DOM 后一次挂载。
|
||
|
||
---
|
||
|
||
## 3. 图片、音频与视频
|
||
|
||
- 小型图片、图标等可以使用 Base64;单条解码后超过 100 KiB 时应优先改为包内文件,超过 1 MiB 不允许内嵌。
|
||
- 图片按真机展示尺寸缩放并压缩,优先 WebP;不要为了 300 px 展示区域打包 4K 原图。
|
||
- 音视频先裁剪时长,再降低分辨率、帧率和码率。5 分钟视频通常不适合随小工具离线交付;优先改为短片段、封面 + 交互说明,或让用户运行时自行选择本地视频。
|
||
- 视频设置 `preload="metadata"` 或 `preload="none"`,提供 `poster`;未进入播放页前不要创建或解码媒体。
|
||
- 同时只保留必要的媒体实例;离开页面后暂停播放、清空不再使用的 `src` 并释放对象 URL。
|
||
|
||
`FileReader.readAsDataURL()` 可用于用户刚选择的小文件预览或 JSBridge 明确要求 data URI 的短暂转换;大文件不要把结果持久写回静态源码,优先用对象 URL 预览,并在不用时 `URL.revokeObjectURL()`。注意:即使体积很小,`<video>` / `<audio>` 的 `data:` 媒体源仍不受容器 CSP 支持,应引用包内媒体文件。
|
||
|
||
---
|
||
|
||
## 4. WebGL 资源与降级基线
|
||
|
||
WebGL 是可用能力,不代表所有设备都能稳定运行复杂场景。以下数值是生成代码时采用的保守设计预算,不是 Skill 对实际性能的测量结果。目标体验按 **30 FPS 可交互**设计,60 FPS 仅作为有运行数据支持时的高档增强。
|
||
|
||
普通 Canvas 2D 不纳入本节的 GPU 分档要求;按实际展示尺寸创建画布、避免无意义的重复全量绘制即可。只有使用 WebGL 上下文时才执行以下资源预算、降档和 context 兜底。
|
||
|
||
### 初始预算
|
||
|
||
| 指标 | 默认档 | 低档 / 降级档 |
|
||
| --- | --- | --- |
|
||
| WebGL drawing buffer DPR | `min(devicePixelRatio, 1.5)` | `1` |
|
||
| WebGL drawing buffer 像素数 | 不超过约 200 万 | 不超过约 100 万 |
|
||
| 单张纹理边长 | 不超过 2048 | 不超过 1024 |
|
||
| 估算纹理显存 | 不超过 64 MiB | 不超过 32 MiB |
|
||
| 每帧 draw call | 不超过 100 | 不超过 50 |
|
||
| 每帧三角形 | 不超过 100k | 不超过 50k |
|
||
| 帧率目标 | 稳定 30 FPS,设备有余量再升档 | 稳定 24–30 FPS |
|
||
|
||
这些是移动 WebView 的保守初始预算。代码可以根据用户提供的设备数据调整;没有实测数据时保持预算与降级路径,不得声称已经达到某个帧率或通过真机性能验收。
|
||
|
||
### 渲染规则
|
||
|
||
- 优先兼容 WebGL 1;使用 WebGL 2 特性时必须检测能力并提供 WebGL 1 或非 WebGL 兜底。
|
||
- 初始化先使用低 / 中档,不按高 DPR 直接创建最大缓冲区。尺寸变化时重新计算并继续受像素预算约束。
|
||
- 纹理使用实际需要的尺寸;复用纹理、材质、几何体和 framebuffer。估算 RGBA8 纹理最低占用:`宽 × 高 × 4`,mipmap 还会额外增加约 1/3。
|
||
- 合并可合并的几何与 draw call,视锥 / 距离裁剪不可见对象;粒子、阴影、后处理、透明叠加和实时反射必须能逐项关闭。
|
||
- shader 在初始化或切换场景时编译;纹理上传和模型解析分批进行,不在动画帧中首次集中完成。
|
||
- 动画循环内避免创建对象 / 数组 / 大字符串;禁止每帧 `readPixels()`、`toDataURL()`、大面积 `getImageData()` 或同步回读 GPU。
|
||
- 页面不可见时通过 `visibilitychange` 停止 `requestAnimationFrame`、媒体和定时器;恢复后重置时间差,避免补算大量帧。
|
||
- 处理 `webglcontextlost` / `webglcontextrestored`;context 丢失时停止渲染并展示轻量状态,不要无限重建。
|
||
|
||
---
|
||
|
||
## 5. 运行时降级与兜底
|
||
|
||
不要依赖机型名单。以实际帧耗时和能力检测决定档位:
|
||
|
||
1. 首次进入采用低 / 中档,逐步加载非必要效果。
|
||
2. 运行时观测到持续掉帧或交互响应变差时,逐级降低 DPR、粒子数、阴影、后处理、可视距离和动画频率。没有运行数据时只确认降级路径存在,不判断是否达到触发条件。
|
||
3. 降到最低档仍不可交互时,停止高成本循环并切换 Canvas 2D、静态图或简化 DOM 视图。
|
||
4. `getContext()` 失败、shader 编译 / 链接失败或 context 反复丢失时,必须进入可理解的兜底界面;不能白屏、死循环重试或持续弹错。
|
||
|
||
如果核心功能并不依赖 3D,默认选择 DOM / CSS / Canvas 2D。WebGL 应解决明确的视觉或计算需求,而不是作为普通表单、列表和信息展示的默认技术栈。
|
||
|
||
---
|
||
|
||
## 6. 交付检查
|
||
|
||
- [ ] 已按环境选择 Node / Python 自动审计;若两者均不可用,已记录人工审计结果与“未运行自动审计”
|
||
- [ ] 未把大数据集、日志或生成内容写进 JS / JSON,长列表已分页或虚拟化
|
||
- [ ] 单条 Base64 解码后不超过 1 MiB;超过 100 KiB 的条目已检查并优先改为独立文件
|
||
- [ ] 静态设计上未把大文件解析、全量 DOM 构建和大资源初始化集中在首屏同步流程;实际耗时没有运行数据时标记“未实测”
|
||
- [ ] WebGL 默认受 DPR、像素、纹理、draw call 和三角形预算约束
|
||
- [ ] WebGL 有动态降档、页面隐藏暂停、context lost 处理和非 WebGL 兜底
|
||
- [ ] 已区分静态检查与运行实测:有用户提供的运行数据时记录设备、首屏时间、帧率和降级情况;没有数据时标记“性能未实测”,不宣称真机性能合格
|