# UNIAPP踩坑
# aspectFill 在列表中使用异常
列表中图片使用 mode="aspectFill" 在 APP 端滚动时出现图片渐显(未渲染完成)的问题,而 mode="aspectFit" 不会。
# 原因分析
# 一、根本原因:scroll-view 的两层架构
APP 端 scroll-view 采用原生滚动容器 + WebView 渲染的两层架构:
scroll-view 滚动 页面滚动
┌─────────────────────┐ ┌─────────────────────┐
│ 原生滚动容器(管滚动)│ │ │
│ ┌───────────────┐ │ │ WebView 引擎 │
│ │ WebView(管渲染)│ │ │ 同时管滚动+渲染 │
│ └───────────────┘ │ │ 纹理生命周期自洽 │
│ 两层分别管理 ⚠️ │ │ 一层统一管理 ✅ │
└─────────────────────┘ └─────────────────────┘
1
2
3
4
5
6
7
8
2
3
4
5
6
7
8
scroll-view:原生层不认识 WebView 的纹理,只按自己的规则管理显存,觉得没用就淘汰- 页面滚动:WebView 自己滚动自己渲染,知道哪些内容"刚滚走可能还会滚回来",会保留缓冲
# 二、触发机制:原生层主动淘汰离屏纹理
原生滚动容器为了节省 GPU 显存,会按优先级淘汰滚出视口的图片纹理:
| 优先淘汰 | 条件 |
|---|---|
| 先淘汰 | 离视口远 + 纹理大 |
| 再淘汰 | 离视口远 + 纹理小 |
| 后淘汰 | 离视口近 + 纹理大 |
| 不淘汰 | 在视口内 / 显存充足时都不淘汰 |
注意:淘汰的是 GPU 纹理,不是 DOM 元素。DOM 节点、图片数据都还在,只是 GPU 需要重新上传纹理才能显示,这个重传过程就是肉眼可见的"渐显"。
# 三、放大因素:aspectFill 纹理更大,更容易出问题
| 方面 | aspectFit | aspectFill |
|---|---|---|
| 缩放方式 | 等比缩小,完整显示,可能留白 | 等比放大填满,可能裁剪 |
| 解码像素量 | 相同(与 mode 无关) | 相同(与 mode 无关) |
| 渲染纹理像素量 | 通常更少(缩小绘制) | 通常更多(放大后中间帧更大) |
| 裁剪操作 | 不需要 | 需要(GPU 层裁剪) |
| GPU 显存占用 | 略低 | 略高(放大后中间帧占用更多显存) |
| 被淘汰概率 | 低(纹理小) | 高(纹理大,优先被淘汰) |
| 重传耗时 | 短(纹理小,上传快) | 长(纹理大,上传慢) |
# 四、三层原因叠加导致肉眼可见
1. scroll-view 两层架构
→ 原生层激进淘汰离屏纹理,WebView 无法干预
2. aspectFill 纹理更大
→ 优先被淘汰 + 重传更慢 → 双重叠加
3. aspectFit 也有同样机制
→ 但纹理小 → 不易被淘汰 / 重传快 → 感知不到
1
2
3
4
5
6
7
8
2
3
4
5
6
7
8
# 一句话概括
APP 端
scroll-view的原生滚动容器会主动淘汰离屏的 GPU 纹理以节省显存;aspectFill因渲染纹理更大,更容易被淘汰且重传更慢,所以滚回时肉眼可见"渐显"。