先记住这个答案
页面上任何一个元素尺寸变了,浏览器默认要考虑它会不会影响兄弟、父元素,甚至整页布局,所以一次小改动可能引发大范围重排。contain 就是开发者主动告诉浏览器「这块区域是独立的」。contain: layout 表示内部布局不影响外部,浏览器重排时可以把范围限制在这个元素内。contain: paint 表示后代不会画到它的边界外,元素不在视口里时整块内容都可以不绘制。从盒模型上看,paint 相当于在内边距盒边缘加了一道裁剪,layout 则让它成为一个独立的格式化上下文,同时两者都会让它成为定位后代的包含块和一个层叠上下文。
layout:内部布局变化不影响外部paint:后代内容被裁在盒子内,屏幕外可跳过绘制- 盒子尺寸随内容变时,要配合
size或固定尺寸 - 两者都会建立包含块、层叠上下文和独立格式化上下文
典型用法:长列表里的每张卡片
信息流、评论列表这类页面,单条内容的展开、图片加载、点赞数变化都会触发布局。给每条加上 containment,改动就被关在各自的卡片里。
.feed-item {
contain: content; /* 等于 layout paint style */
/* 某张卡片里点赞数、展开状态变化时,
浏览器知道内部布局不会波及其它卡片 */
}
.chat-panel {
height: 480px;
contain: strict; /* 等于 size layout paint style,尺寸固定时隔离最彻底 */
}
.long-article section {
content-visibility: auto; /* 屏幕外的区块跳过布局和绘制 */
contain-intrinsic-size: auto 800px; /* 跳过期间先按 800px 占位,避免滚动条跳动 */
}.feed-item 的高度仍由内容决定,所以卡片变高时外部还是要跟着挪,但卡片内部的计算不会牵连别人。.chat-panel 尺寸是写死的,里面消息怎么滚动增减,外面都不需要重新布局。content-visibility: auto 是在 containment 基础上更进一步:元素离开视口时,浏览器连它内部的布局和绘制都跳过,适合很长的文章或列表。
两个值在盒模型上具体改变了什么
contain 不只是性能提示,它会实实在在地改变元素的布局行为,这也是它会「弄坏页面」的原因。
- layout 和 paint 各自带来的变化
contain: layout让元素建立独立格式化上下文,内部的浮动出不来,第一个子元素的margin-top也不会再和它合并。它还会成为position: absolute和fixed后代的包含块,并创建层叠上下文,里面元素的z-index再大也只在这张卡片内部比较。contain: paint除了上面包含块、层叠上下文、独立格式化上下文这几点,还会把后代裁在元素的内边距盒以内,效果接近overflow: clip。元素自己的box-shadow、outline不受影响,但子元素的阴影、弹出菜单超出去的部分都会被切掉。
容易答错的地方
- 以为加了 contain: layout 外面就永远不用重排
layout隔离的是内部排版过程,不是元素尺寸。高度是auto的元素,内容变多了它照样变高,兄弟元素照样要挪。要让外部完全不受影响,需要尺寸不依赖内容,也就是写固定尺寸或者加上contain: size。- 给有下拉菜单、tooltip 的组件加 contain: paint
- 弹层超出卡片的部分会被裁掉。更隐蔽的是
position: fixed的弹层:因为元素成了fixed后代的包含块,弹层不再相对视口定位,而是相对这张卡片。这类组件要么不加 containment,要么把弹层用 Portal 渲染到body下。 - 把 contain: size 随手加上
size表示元素尺寸计算时当作没有内容,如果没有写宽高,高度会直接变成 0。它要么配合固定尺寸,要么配合contain-intrinsic-size给一个占位尺寸。
面试官还会怎么问?
contain 和 will-change 有什么区别?
will-change: transform 提示浏览器某个属性即将变化,通常会提前把元素提升成单独的合成层,优化的是动画阶段。contain 限定的是布局和绘制的影响范围,优化的是重排和重绘。两者解决的问题不同,也都会创建层叠上下文,都不能滥用。
怎么确认 contain 真的起作用了?
用 Chrome DevTools 的 Performance 面板录制一次交互,对比加 contain 前后 Layout 任务的耗时和影响的节点数量。Rendering 面板里的 Paint flashing 可以直观看到每次重绘的区域有没有缩小。
content-visibility: auto 有什么坑?
屏幕外的内容被跳过渲染,高度按 contain-intrinsic-size 的占位值算。不写占位尺寸,元素会塌成 0,滚动时滚动条会跳。写了 auto 800px 这种形式,浏览器会记住渲染过一次后的真实高度,下次离开视口时就用真实值占位。另外被跳过的内容仍然可以被页内搜索和无障碍工具访问到。
参考资料
示例用于理解所注明的运行环境与边界;延伸学习可结合原文中的更多案例。