aslan-h5/docs/活动页/竞品H5与性能优化分析.md

339 lines
13 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.

# 竞品 H5 与本项目性能优化分析
## 背景
本次主要排查 `src/views/Activities``src/views/Ranking` 下活动页、榜单页在低端安卓 WebView 中打开和运行卡顿的问题。测试现象中2GB 内存低端机在展示弹幕 `Barrage`、滚动文本、跑马灯等持续动画时明显掉帧,离开这些区域后流畅度恢复。
本次同时参考了两类竞品 H5
- Yalla / qrsshine 活动页:
- `activity2.yalla.live/activity/2025dist/Thursday`
- `activity2.yalla.live/activity/2025dist/sea2`
- `activity2.yalla.live/activity/2025dist/userCarnival2`
- `activity2.qrsshine.com/activity/2024dist/activityMap`
- `activity2.qrsshine.com/activity/2024dist/roomHonorSystem`
- Hawatalk 活动页:
- `cdn.hawatalk.com/activitys/hexactivityprojectv3/guild-world-peak`
- `cdn.hawatalk.com/activitys/hexactivityprojectv3/miss-pk-2026`
- `cdn.hawatalk.com/activitys/hexactivityprojectv3/dynamic-user-id`
- `cdn.hawatalk.com/activitys/hexactivityprojectv3/recharge-activity`
- `cdn.hawatalk.com/activitys/hexactivityprojectv3/tower-tycoon`
- `cdn.hawatalk.com/activitys/hexactivityprojectv3/predator-journey`
## 竞品 H5 的主要特点
### 1. 活动独立包更明显
竞品活动页大多是独立活动目录HTML 很薄,活动自身 JS 较小。Hawatalk 新旧活动页中,活动入口 JS 通常只有几十 KB 到八十多 KB公共 vendor 大约两百多 KB。Yalla 部分页面 vendor 较大,但也是按活动目录独立部署。
这种方式的好处是:
- 单个活动的首屏代码体积更可控。
- 活动之间互相影响小。
- 活动资源可以按目录独立缓存和更新。
### 2. 静态资源缓存策略更稳定
竞品普遍使用内容 hash 文件名。HTML 短缓存或不缓存JS/CSS/图片等静态资源长缓存。这样普通发版时,只要资源内容不变,用户端就能继续命中缓存。
我们的项目之前在构建文件名里带 `buildTimestamp`,每次构建都会导致 JS/CSS 文件 URL 改变,哪怕内容没变也会让缓存失效。这个问题已在本次优化中处理。
### 3. 动画和视觉效果相对克制0
竞品页面也会有活动视觉、排行列表和特效但多数页面不会在首屏同时叠加大量持续动画、canvas 背景、弹幕、跑马灯、模糊滤镜和大批图片解码。
本项目部分页面中,持续动画与图片层叠加较多,在高端机上影响不明显,但低端 WebView 中会明显放大。
## 我们项目的主要差异与问题
### 1. 持续动画叠加较多
`src/views/Activities``src/views/Ranking` 中,存在多类持续动画:
- 弹幕 `Barrage`
- tab 文字滚动
- 奖励列表跑马灯
- 抽奖滚动动画
- 倒计时定时器
其中弹幕和跑马灯最容易在低端机上触发掉帧,因为它们会持续制造合成层,并且通常伴随头像、奖品图、渐变文字和透明背景。
### 2. `backdrop-filter` 成本高
部分页面使用了 `backdrop-filter: blur(...)`,例如:
- `src/views/Ranking/Couple/components/Barrage.vue`
- `src/views/Activities/LuckyDollars/Season5/components/Barrage.vue`
- 多个弹窗、卡片、奖励区域
`backdrop-filter` 在低端安卓 WebView 上成本很高。它会让浏览器每帧采样并合成背景,和弹幕/跑马灯一起出现时尤其容易卡顿。
### 3. canvas 背景内存压力大,已取消该方案
`BackgroundLayer` 曾支持 `useCanvas=true`,会先通过图片缓存加载图片,再把多张背景图绘制到一个 canvas 上。这个设计主要用于提高直接扒取 DOM 背景结构的门槛。
经过低端机性能排查后,该方案已取消:
- 页面继续保留 `useCanvas` 入参,兼容历史调用。
- 当前 `BackgroundLayer` 不再生成 canvas。
- 背景图统一通过 `<img v-smart-img>` 渲染。
- 图片仍继续走受保护路径解析和图片缓存链路。
取消原因是大尺寸 canvas 会占用较高内存和 GPU 资源。如果页面同时有弹幕、跑马灯、头像列表就容易出现明显卡顿。canvas 对防扒取只有“提高一点直接复制 DOM 的门槛”的作用,防不住抓包、运行时 Hook 或最终像素提取,因此优先保证页面流畅度。
### 4. 非关键图片预加载过早
多个页面 mounted 后立即调用 `preloadOtherImages()`。虽然缓存能减少重复网络下载,但打开页面时立即预加载非关键图片,会和首屏渲染、背景绘制、接口返回后的列表渲染、弹幕动画抢资源。
因此缓存不是越激进越好。缓存能减少网络重复下载但不能减少图片解码、DOM 渲染和 GPU 合成成本。
### 5. 部分隐藏内容仍然挂载
一些页面用 `v-show` 切换大模块。`v-show` 只是隐藏,不会卸载组件。隐藏模块中的 DOM、图片、定时器、观察器仍可能占用内存和运行成本。活动页/榜单页中如果大模块较多,低端机压力会更明显。
## 本次已经完成的优化
### 1. 构建文件名改为内容 hash
修改文件:
- `vite.config.js`
已移除构建文件名中的 `buildTimestamp`,改为内容 hash
- `js/[hash].js`
- `css/[hash].[ext]`
- `assets/[hash].[ext]`
这样普通发版时,内容没变的 JS/CSS/图片 URL 不会改变,能继续命中浏览器/CDN 缓存。
### 2. Nginx 静态资源长缓存
修改文件:
- `nginx/default.conf`
- `nginx/default_hooka.conf`
静态资源长缓存范围从 JS/CSS 扩展到图片、字体、视频等:
- `js`
- `css`
- `png`
- `jpg`
- `jpeg`
- `gif`
- `webp`
- `svg`
- `ico`
- `ttf`
- `otf`
- `woff`
- `woff2`
- `mp4`
缓存头为:
```nginx
Cache-Control "public, max-age=2592000, immutable"
```
### 3. 图片缓存容量下调并支持单图版本
修改文件:
- `src/config/imageCacheVersion.js`
- `src/config/imagePaths.js`
- `src/utils/image/imageCacheManager.js`
缓存容量从最多 200 张、200MB 调整为:
```js
cacheLimits: {
maxItems: 120,
maxSizeMB: 80,
}
```
原因是低端机内存有限,过大的本地图片缓存会增加内存和存储管理压力。
曾评估过 `assetVersions` 单图/目录版本机制,让图片 URL 自动追加 `?iv=版本号`。但当前我们只能处理 H5 项目和 OSS 图片替换,无法稳定控制 CDN/浏览器缓存链路如果服务端、CDN 或 WebView 仍返回旧对象,这个功能会给维护带来误判。因此该方案已取消。
使用建议:
- 普通发版:不要改全局 `version`
- 单张图片替换:上传新文件名,并同步修改页面中的图片引用,例如 `top1RewardBg_20260515`
- 大批量图片迁移、缓存污染:才更新全局 `version`
- `updateTime` 只作为人工记录,不参与实际缓存命中逻辑。
### 4. 图片预加载改为限流和 idle
修改文件:
- `src/utils/image/imagePreloader.js`
优化点:
- 默认并发限制为 3。
- 去重空值和重复 URL。
- 支持 `preloadImagesIdle()`
- 单张图片失败不阻塞整个预加载。
页面中非关键图片预加载已改为 idle
- `src/views/Ranking/Couple/index.vue`
- `src/views/Ranking/GamesKing/index.vue`
- `src/views/Activities/LesserBairam/index.vue`
- `src/views/Activities/SpringFestival/index.vue`
- `src/views/Activities/LuckyDollars/Season3/index.vue`
- `src/views/Activities/LuckyDollars/Season4/index.vue`
- `src/views/Activities/LuckyDollars/Season5/index.vue`
### 5. App 先挂载,再后台检查版本
修改文件:
- `src/main.js`
之前版本检查会在 app mount 前执行,可能影响打开速度。现在先挂载页面,再通过 idle/timeout 后台检查版本。
### 6. 新增通用低端机性能模式
新增文件:
- `src/utils/performanceMode.js`
低端机判断不针对单一型号,而是通用条件:
- `navigator.deviceMemory <= 2`
- 安卓低核心设备
- 旧安卓低核心 WebView
- 用户系统开启 `prefers-reduced-motion`
启动时会给 `html` 添加:
- `low-performance-mode`
- `reduce-motion-mode`
这些 class 用于全局 CSS 和组件内降级。
### 7. 取消 canvas 背景渲染
修改文件:
- `src/components/BackgroundLayer.vue`
当前即使页面传入 `useCanvas=true`,也会渲染为普通图片背景。这样仍然走图片缓存和受保护路径解析,但避免大 canvas 带来的额外内存和绘制成本。
`useCanvas` 入参暂时保留,避免大量历史页面需要同步改模板;后续新页面不再依赖它做防扒取。
### 8. `v-smart-img` 低端机关闭淡入动画
修改文件:
- `src/directives/smartImage.js`
低端机下关闭图片 opacity fade 动画,并设置:
```js
el.decoding = 'async'
```
这样可以减少图片加载时的主线程和合成压力。
### 9. 全局低端机移除高成本模糊,滚动动画保持可用
修改文件:
- `src/styles/global.css`
低端机下统一移除:
```css
backdrop-filter
-webkit-backdrop-filter
```
低端机模式和系统“减少动态效果”模式都不再暂停 `.scroll-wrapper` 这类跑马灯动画,避免 iOS 或低端安卓上排行榜、奖励列表、用户昵称等文本滚动完全不动。系统开启“减少动态效果”时,只暂停弹幕这类更重的装饰动画。
同时,多个活动页和榜单页的 `isLoading` 预加载阶段已取消渲染 `BackgroundLayer`、背景图片和其他装饰节点,只保留 `LoadingSpinner` 和 loading 文案。两者统一包在 `.loading-content` 容器中使用半透明黑底、8px 圆角和 10px 内边距,避免加载态和正式页面重复创建背景图层。
### 10. 弹幕组件专项优化
修改文件:
- `src/views/Activities/LuckyDollars/Season3/components/Barrage.vue`
- `src/views/Activities/LuckyDollars/Season4/components/Barrage.vue`
- `src/views/Activities/LuckyDollars/Season5/components/Barrage.vue`
- `src/views/Ranking/Couple/components/Barrage.vue`
优化点:
- 低端机弹幕最大同时显示数降到每轨 1 条。
- 低端机弹幕生成间隔至少 4500ms。
- 页面隐藏时暂停弹幕并清空当前动画 DOM。
- 弹幕区域离开视口时暂停并清空当前动画 DOM。
- 去掉 `watch(..., { deep: true })`,避免深度监听大数组。
- 弹幕文字在入轨时预计算,减少模板每次渲染重复计算。
- 动画结束时直接从轨道移除,不再先标记 completed 再由 interval 清理。
- Couple 弹幕在低端模式下隐藏中间徽章图,减少一张持续参与动画的图片。
## 后续建议
### 1. 优先把大模块从 `v-show` 改为按需挂载
活动页中排行榜、玩法、奖励、弹窗等大模块,如果不是首屏必须展示,建议逐步改成:
- 首次进入时 `v-if` 挂载。
- 数据和图片进入模块时再加载。
- 离开模块后保留必要状态,释放重图片 DOM。
本次已按体验权衡做了选择性调整:
- 高频按钮、轻量图片状态仍保留 `v-show`,避免点击切换时闪烁。
- 会触发接口、预加载、定时器或 `IntersectionObserver` 的大组件改为 `v-if`
- 已调整 `Ranking/Couple``Ranking` / `GamePlay` 一级模块。
- 已调整 `Ranking/Couple/GamePlay` 内的 `lottery` / `signIn` / `exchange` 子模块。
- 已调整 `SpringFestival``Task``Ranking``RankingReward``RechargeReward` 模块。
### 2. 继续压缩活动页图片
建议优先处理:
- 背景长图
- 弹窗背景
- 奖励图
- 大面积 PNG
能用 WebP 的尽量用 WebP。静态装饰图可以做多尺寸或压缩版低端机使用轻量图。
### 3. 跑马灯文本尽量避免模板内测量 DOM
例如 `isTextOverflow(text)` 这类函数,如果在模板中直接调用,并且内部创建 DOM + `getBoundingClientRect()`,容易触发布局计算。建议后续改成:
- 数据变化后统一测量一次。
- 缓存结果。
- 低端机直接省略号展示,不滚动。
### 4. 弹幕建议抽成公共组件
现在 Season3/4/5 和 Couple 都有独立弹幕组件。后续可以抽一个公共 `PerformanceBarrage`,不同活动只传渲染内容和样式配置,避免同类性能修复重复做多次。
### 5. 真机验证顺序
建议在低端安卓 WebView 上按下面顺序对比:
1. 打开 LuckyDollars Season5 首页,观察弹幕区域 FPS。
2. 进入/离开弹幕区域,确认离开视口后不再持续掉帧。
3. 打开 Couple 首页,观察告白墙弹幕和顶部跑马灯。
4. 切换 Ranking / GamePlay观察内存是否持续上涨。
5. 冷启动和二次打开分别测试,确认缓存命中后网络变少,同时页面不卡顿。
## 构建验证
已使用项目依赖执行生产构建,构建通过。
仍保留两个历史警告,和本次优化无直接关系:
- `Azizi/secondBg.png` 构建时未解析,会保留到运行时解析。
- `src/utils/http.js` 同时被静态和动态导入,无法单独拆到动态 chunk。