likes
comments
collection
share

CSS之contain,一个连fixed定位都要看它眼色的限制属性

作者站长头像
站长
· 阅读数 14

CSS的布局有太多种方式,元素的表现也有很多的形式。

像我们熟悉的那些:行内元素、块元素、列表元素、表格元素、绝对定位、固定定位、浮动、弹性布局、网格布局等等等等。

一个元素的具体渲染可能会受到父子元素、兄弟元素的影响。

大多数情况我们都可以通过一些手段,来解决我们遇到的布局或表现问题。

比如给一个元素赋予了浮动,那么可以通过清除浮动来消除影响,再比如通过绝对定位来调整元素的位置,也可以通过padding或margin等避免其他元素被覆盖。

但是也有那么一些情况,我们不太好处理,比如某一个元素希望相对于它的父级区域做固定定位,而不是基于整个页面,能做到吗?再比如给定一个盒容器,无论子元素怎么排序布局或者浮动,也不会影响其他相邻盒子的渲染,该怎么做到?

其实不止这些,还有很多的实际场景让我们很棘手,你是否在设计和实现方面做过平衡和妥协?

对于现在的浏览器来说,所支持的CSS功能不允许我们说:我不行,我做不到。

别的不说,今天要讲的contain就能用来解决上面的问题,它能做的事情还有很多,基于自身天然的属性,甚至能轻松提升你的性能!

contain概述

contain表明该元素要独立于页面中的其他元素,该元素中的所有内容都被局限在一个独立的区域,跟其他元素隔离开来,从而使得基于该元素的所有计算都是独立的,被限制在该DOM子树中,而不是整个页面。这样能够让页面的性能提升。

该元素构成的容器,可以控制其产生的尺寸范围、样式作用域、布局方式、绘制区域。会生成新的包含区块、新的层叠上下文、新的区块格式化上下文。这些控制手段都对应着不同的局限属性,在容器内对局限属性的修改,不会影响容器外的部分,也就不会使得页面经常重新渲染,尤其在动态修改页面元素时会带来更好的性能受益。

我们理解它的时候,不要把它想成是包含的意思,理解成它的作用,是对包含内容的一个局限,之后也会多次用到"局限"这个词。

分类

一、关键词

通过关键词,可以指定不同的局限属性,从而产生不同的局限效果。

  1. none:不应用任何局限。
  2. size:元素的尺寸无视子元素而单独计算,在行向和块向上都应用该局限。
  3. inline-size:元素的尺寸无视子元素而单独计算,只在行向上应用该局限。
  4. layout:使得该元素的布局与元素外的任何内容相互独立,互不影响。即该元素所构成的容器从页面中隔离出来,单独计算布局。
  5. style:对于容器内的某些可能影响外部属性的值,被限制在容器内,不参与整体的计算,比如计数器和引号不会参与之前或者之后的计算,会有独立的计算,对于外面来说就好像这个元素没存在过一样,或者说不知道有它的存在。
  6. paint:限制该元素的绘制区域范围,子元素的渲染永远不会出现在容器外,容器的边界限定了子元素的可见内容。例如容器在屏幕外,那么就永远不用绘制,因为其子元素也肯定在屏幕外,不像margin负值放在屏幕外那样,子元素可能延伸到屏幕内。
  7. content:相当于设置了layout、paint、style,也就是除了size之外的所有值。
  8. strict:给该元素应用所有局限规则,相当于设置size、layout、paint、style。

二、组合值

也就是上面2-6关键字的任意组合,跟顺序无关,多个值之间用空格分隔开。跟个数也无关,可以设置任意的数量。不过要注意,size和inline-size同时只能设置一个,因为它俩是冲突的。

三、全局值

全局值的作用,可以参考我在font-size那篇文章中的解释说明,它们的作用机制和原理都是一样的,这里不在重复赘述。

示例

我们来对上面所说的内容,做一些示例,来看看它们的实际工作方式。

先构建一个基本的代码,之后都以这个为基础改造和演示:

<div style="background-color: bisque;">
  我是父元素
  <div style="background-color: coral;">
    我是子元素
  </div>
</div>
<div style="background-color: lightpink;">
  我是父兄弟元素
</div>

看下现在的效果:

CSS之contain,一个连fixed定位都要看它眼色的限制属性

记住这个效果,因为我们接下来就要改变contain属性,观察它所发生的变化。

  1. 通过尺寸来控制
<div style="contain: size;background-color: bisque;">
  我是父元素
  <div style="background-color: coral;">
    我是子元素
  </div>
</div>
<div style="background-color: lightpink;">
  我是父兄弟元素
</div>

给父元素加上contain:size样式:

CSS之contain,一个连fixed定位都要看它眼色的限制属性

可以看到,"我是父元素"和"我是父兄弟元素"重合了,也就说,父兄弟元素的渲染,直接从父元素渲染开始的位置开始渲染的。就好像父元素不存在一样。

这里面的奥妙通过控制台看一下,其实就很容易解开了:

CSS之contain,一个连fixed定位都要看它眼色的限制属性

就是因为父元素的高度为0了。在解释之前先说明一下,行向指的是我们书写的方向,就是指的从左往右,你就可以理解成是多个行内元素排列的方向,一直往后面追加的方向。块向指的是我们折行的方向,也就是指的从上往下,你就可以理解成是多个块级元素排列的方向,一直往下追加的方向。

理解了这两个之后,我们就知道,由于size影响着这两个方向上的局限,它会变得无视子元素。因为如果没有主动设置尺寸的话,就好像子元素不存在一样,那么它就没有高度,所以兄弟元素就自然而然的顶上来了。

这时,我们改一下设置,让它只在行向上有局限:

<div style="contain: inline-size;background-color: bisque;">
  我是父元素
  <div style="background-color: coral;">
    我是子元素
  </div>
</div>
<div style="background-color: lightpink;">
  我是父兄弟元素
</div>

通过设置contain:inline-size:

CSS之contain,一个连fixed定位都要看它眼色的限制属性

可以看到,效果又回来了,这是因为我们没有在块向上做局限,因此高度会自然撑开。

既然说到了这里,我们再看一下,它是如何在行向上进行局限的,构造如下代码:

<div style="contain: inline-size;display: inline-block;background-color: bisque;">
  我是父元素
  <div style="background-color: coral;">
    我是子元素
  </div>
</div>
<div style="background-color: lightpink;">
  我是父兄弟元素
</div>

设置父元素为行内块,增加display:inline-block:

CSS之contain,一个连fixed定位都要看它眼色的限制属性

瞬间又变成了这样,这同样是因为,行内局限使得父元素独立计算尺寸,而我们又没手动指定,因此它的宽度为0,子元素也跟着宽度为0,所以就变成了一个字一换行。

这时即使你给子元素加上宽度,在行向上父元素也会无视你:

CSS之contain,一个连fixed定位都要看它眼色的限制属性

你看我们给子元素加上100px的宽度,但是鼠标查看父元素,依然是宽度为0。

  1. 通过布局来控制

指定contain:layout,可以让该元素独立计算它的内部布局,不受外界影响,我们先将子元素设定一个固定定位:

<div style="background-color: bisque;">
  我是父元素
  <div style="position: fixed;top: 10px;background-color: coral;">
    我是子元素
  </div>
</div>
<div style="background-color: lightpink;">
  我是父兄弟元素
</div>

看下现在的效果:

CSS之contain,一个连fixed定位都要看它眼色的限制属性

完全没毛病,子元素固定到页面顶部的10px位置。

现在应用一下我们的布局限制:

<div style="contain: layout;background-color: bisque;">
  我是父元素
  <div style="position: fixed;top: 10px;background-color: coral;">
    我是子元素
  </div>
</div>
<div style="background-color: lightpink;">
  我是父兄弟元素
</div>

再看下显示的效果:

CSS之contain,一个连fixed定位都要看它眼色的限制属性

咦?明明是固定定位,它的位置却是相对于父元素的。这就是布局限制的作用,相当于父元素告诉页面,从现在开始,这片的布局归我管,所有的行为都向我请示,由我指挥。

同样,其他的position值,也都是基于父元素的布局限制来渲染的,这里就不做一一演示了。

其实,不光是position,只要是关于布局的,都会在此局限下生效,这里再演示一个浮动的例子:

<div style="height: 80px;padding: 5px;background-color: bisque;">
  <h2 style="margin-bottom: 7px;">我是父元素</h2>
  <p style="float: left;background-color: coral;">
    我是子元素
  </p>
</div>
<div style="height: 80px;padding: 5px;background-color: lightpink;">
  <h2>我是父兄弟元素</h2>
  <p style="background-color: coral;">
    我是父兄弟子元素
  </p>
</div>

两个父元素各包含两个子元素,其中第一个父元素的第二个子元素设置为左浮动,为了更好的演示,我把子元素改成了h2和p标签,看下现在的效果:

CSS之contain,一个连fixed定位都要看它眼色的限制属性

父元素的两个子元素正常显示,但是父兄弟元素的第一个子元素里面的文字,被挤的偏移了,这是由于父元素的第二个子元素设置浮动引起的,通过控制台看下就明白了:

CSS之contain,一个连fixed定位都要看它眼色的限制属性

红线是我标出来的,可以看到,父兄弟元素的h2和父元素的浮动有一丁点的重合,导致文字被推开,这就是浮动产生的影响。

我们通过布局局限,来控制浮动只发生在局限内,稍微改下代码:

<div style="contain: layout;height: 80px;padding: 5px;background-color: bisque;">
  <h2 style="margin-bottom: 7px;">我是父元素</h2>
  <p style="float: left;background-color: coral;">
    我是子元素
  </p>
</div>
<div style="height: 80px;padding: 5px;background-color: lightpink;">
  <h2>我是父兄弟元素</h2>
  <p style="background-color: coral;">
    我是父兄弟子元素
  </p>
</div>

给父元素添加了contain:layout,这是浮动就不会影响后面的布局了:

CSS之contain,一个连fixed定位都要看它眼色的限制属性

通过布局局限,我们可以把所有的关于布局的影响,都控制在容器内,这样即使容器内布局发生了改变,也完全不会影响页面其他内容,在动态页面中,如果需要频繁的修改某些元素,通过这种方式管理和设计页面,就能很有效的改善渲染的性能。

  1. 通过样式来控制

这里的样式主要是针对计数器和引号的作用域,能控制它们只在所局限的范围内单独计算,而不会影响全局的结果,就好像它们是单独拿出来作为一个独立的文档一样,看个例子:

body {
  counter-reset: my-list;
}
div > div::before {
  counter-increment: my-list;
  content: "(" counter(my-list) "):";
}
<div>
  <div>第1行</div>
  <div>第2行</div>
  <div>第3行</div>
  <div>第4行</div>
  <div>第5行</div>
  <div>第6行</div>
</div>

我们设计这样一个列表,通过自定义的计数器,设置一个前缀的显示:

CSS之contain,一个连fixed定位都要看它眼色的限制属性

如果我们希望第三行独立出来,进行样式局限,不参与外部计数:

<div>
  <div>第1行</div>
  <div>第2行</div>
  <div style="contain: style;">第3行</div>
  <div>第4行</div>
  <div>第5行</div>
  <div>第6行</div>
</div>

那么它就会展现成这个样子:

CSS之contain,一个连fixed定位都要看它眼色的限制属性

注意第三行的计数,已经重置为1,并且第四行从3开始,接着第二行的值继续计数。

  1. 通过绘制来控制

这个也比较厉害,就是父元素啥样就是啥样,子元素永远不会在容器外渲染:

<div style="background-color: bisque;width: 100px;height: 100px;">
  <div>
  海客谈瀛洲,烟涛微茫信难求。
  越人语天姥,云霞明灭或可睹。
  天姥连天向天横,势拔五岳掩赤城。
    </div>
</div>

有这样一个代码块,容器的宽高为100px,内容有超出部分:

CSS之contain,一个连fixed定位都要看它眼色的限制属性

我们可以通过绘制局限来使容器外的内容不显示:

<div style="contain: style;background-color: bisque;width: 100px;height: 100px;">
  <div>
  海客谈瀛洲,烟涛微茫信难求。
  越人语天姥,云霞明灭或可睹。
  天姥连天向天横,势拔五岳掩赤城。
  </div>
</div>

添加contain:paint,只让绘制区域被限制在容器内部:

CSS之contain,一个连fixed定位都要看它眼色的限制属性

这个跟overflow:hidden有一点区别,就是绘制局限真的就是正常绘制,只不过不绘制容器外的部分,而hidden虽然隐藏,但是依然能通过js进行滚动。

  1. 通过组合值来控制

我们也可以通过任意的值的组合来控制所需要的局限,也可以通过strict或content关键字来快速的做到这一点。这里就不再重复演示了

最后

合理的使用contain,不但能快速实现我们的需求,也能减少我们的修改量,而且会降低不理解现象的情况的出现频率,更能提升页面的性能。

尤其是布局局限,提供给了我们更多的发挥空间,而且任意的复制到其他地方,也不会对外部元素有影响。

每天一点小知识,希望能够帮助到你。