未声明尺寸会造成布局跳动
别名: 图片未设宽高 · intrinsic size 未知 · 无尺寸图片 CLS
概念解释
图片在解码出固有尺寸之前,文档流不知道该给它多高的盒子。未写 width / height 或 aspect-ratio 时,盒子先按零或行高塌缩,解码完成后再按固有尺寸撑开,后续节点被推走。这是未声明尺寸造成的布局跳动,迟到的信息特指这张图的固有宽高,不是任意后到模块插入文档流。后到的是“这张图有多大”,不是“这里忽然多了一块内容”。
机制
替换元素的默认尺寸来自资源的 intrinsic size。资源未就绪时,引擎没有这组数字,只能用零、alt 行盒或 CSS 里仅有的宽度去猜高度。decode 完成后 intrinsic size 到达,高度从猜值跳到真值,这是一次确定的布局。声明宽高或宽高比,等于把 intrinsic size 提前写成约束,解码只往已有盒子里填位图。只声明宽度、高度仍是 auto,仍会在解码后改高。
这与“任何迟到内容都会推节点”同属布局偏移,但归因不同:即使没有新 DOM 插入,单凭一张已在树里的 <img> 也会在解码瞬间跳动。懒加载只推迟解码时刻,不消除跳动;视口外解码引起的跳动用户可能看不见,进视口后解码则正好撞上阅读位置。
怎么研究
把同一页在“标记含宽高”和“不含”两种条件下冷加载,记录 layout-shift 是否指向该图片节点。实验室若命中磁盘缓存,解码几乎瞬间完成,跳动窗口极短,CLS 会偏低;现场慢网、未缓存时固有尺寸来得晚,跳动完整出现,这是典型的 metric mismatch。自变量:是否声明尺寸、是否懒加载、是否在视口内解码。因变量:该节点贡献的 CLS、解码完成与位移的时间差。不要用缓存命中的实验室会话代表首访图片。
边界
背景图不占文档流高度,未声明尺寸不会撑开邻居,但内容区可能在图上面“漂”。SVG 若自身带 viewBox 且 CSS 写了宽,高度可从 viewBox 推,不一定要位图像素宽高。固定高的裁切容器(正方形头像槽)已经把几何锁死,内部图未声明尺寸也不会改外盒。打印和预渲染若等图齐再出页,跳动发生在看见之前。
怎么落地
- 每张
<img>/<video>/ 嵌入框在标记里写上宽高或aspect-ratio,与文件固有比一致。 - 响应式图用
width/height表达固有比,用 CSS 控制展示宽,让高度跟比走,而不是只写width: 100%。 - 懒加载的图同样要先占位;延后解码不是延后占位。
- 验证:禁用缓存冷加载,解码前后对比图片以下节点的 Y 坐标,应重合。现场 CLS 归因到图片节点的条目,应在补齐尺寸后消失。