CSS和JS拾遗 | 青训营笔记

98 阅读14分钟

这是我参与「第五届青训营」笔记创作活动的第5天。这里记录下之前在css布局阶段遗漏的重要知识。

一、本堂课重点内容:

CSS flex布局,元素的定位尺寸

二、详细知识点介绍:

CSS flex布局

一、推荐使用flex缩写语法

CSS官方文档明确推荐flex属性使用缩写语法,描述如下所示:

Authors are encouraged to control flexibility using the flex shorthand rather than with its longhand properties directly, as the shorthand correctly resets any unspecified components to accommodate common uses.

大致意思是,建议使用单值缩写,而不是完整的3个属性值,因为单值属性所对应的flex计算值根据开发者日常最常用的使用进行了优化。

这句话很多人并不知道什么意思,这个说来话长,想想还是讲一下吧。

flex属性是flex-growflex-shrinkflex-basis这3个CSS属性的缩写。按照以往的经验,flex属性只有一个值的时候,另外缺省的值应该使用默认值才对,但是flex属性并不是这样的。

我们可以对比CSS border属性。

CSS border属性是border-widthborder-styleborder-color这3个CSS属性的缩写,当border属性设置了1个值或2个值的时候,剩下的属性值一定是默认值,例如:

  • border:2px等同于border:2px none currentColor,也就是此时border-style是默认值noneborder-color的计算值是当前的色值;
  • border:#fff等同于border:medium none #fff,也就是此时border-width是默认值medium
  • border:solid等同于border:medium solid currentColor

使用下面的代码可以得到flex-basis默认值是autoflex-grow默认值是0flex-shrink默认值是1

console.log(getComputedStyle(document.body).flexGrow);
console.log(getComputedStyle(document.body).flexShrink);
console.log(getComputedStyle(document.body).flexBasis);

实际运行效果如下: Chrome和Firefox浏览器获取的flex子属性的默认值示意

然后再看下flex缩写属性的计算值,就会发现不一样的事情:

  • flex:1等同于flex:1 1 0%flex:1 2等同于flex:1 2 0%,即flex-basis使用的不是默认值auto,而是使用的0%
  • flex:100px等同于flex:1 1 100px,即flex-grow使用的不是默认值0,而是使用的1

这就是上面提到的flex属性站在实用主义的角度对缩写属性的计算值进行了优化。

然后,还有一个重要原因,flex属性的长语法需要理解深刻,反复使用才能驾驭,门槛比较好,因此,实际开发,如果可以,建议使用flex缩写。

常见的flex缩写有下面这几个,flex:0flex:1flex:noneflex:auto,那各个CSS声明应该在什么场景下使用才正确呢?

二、使用flex缩写语法场景

下表展示了常见的flex属性单值语法对应的flex计算值,涵盖了绝大多数的flex属性的使用场景。

单值语法等同于备注
flex: initialflex: 0 1 auto初始值,常用
flex: 0flex: 0 1 0%适用场景少
flex: noneflex: 0 0 auto推荐
flex: 1flex: 1 1 0%推荐
flex: autoflex: 1 1 auto适用场景少

1. flex:initial基本表现和适用场景

flex:initial等同于设置flex: 0 1 auto,可以理解为flex属性的默认值。

该默认值图形示意如下图所示。

flex属性初始值分解示意

其行为表现文字描述为:

flex容器有剩余空间时尺寸不会增长(flex-grow:0),flex容器尺寸不足时尺寸会收缩变小(flex-shrink:1),尺寸自适应于内容(flex-basis:auto)(行为类似fit-content)。

举个例子,我们给flex容器设置深红色的虚线框,如果此时flex子项(设置深天蓝色轮廓)的内容都比较少,就会有如下图所示的效果,剩余空间依然保留。

flex子项内容较少时候的尺寸表现

相关CSS代码和HTML代码如下所示:

.container {
    display: flex;
    border: 2px dashed crimson;
}
.container item {
    border: 2px solid deepskyblue;    
}
<div class="container">
    <item>范张</item>
    <item>范鑫</item>
    <item>范旭</item>
    <item>范帅</item>
    <item>范哥</item>
</div>

如果子项内容很多,由于flex-shrink:1,因此,会缩小,表现效果就是文字换行,效果如下图所示。

flex子项内容较多时候的尺寸表现

'initial'是CSS中的一个全局关键字,表示CSS属性的初始值,通常用来还原已经设置的CSS属性。因此日常开发不会专门设置flex:initial声明,但是不设置并不是说flex默认属性值用的不多。

flex:initial适用场景

flex:initial声明适用于下图所示的布局效果。

适合flex:initial声明的布局轮廓图示意

上图所示的布局效果常见于按钮、标题、小图标等小部件的排版布局,因为这些小部件的宽度都不会很宽,水平位置的控制多使用justify-contentmargin-left:auto/margin-right:auto实现。

除了上图所示的布局效果外,flex:initial声明还适用于一侧内容宽度固定,另外一侧内容宽度任意的两栏自适应布局场景,布局轮廓如图下图所示(点点点表示文本内容)。

适合flex:initial声明的两栏自适应布局轮廓图示意

此时,无需任何其他Flex布局相关的CSS设置,只需要容器元素设置display:flex即可。

总结下就是那些希望元素尺寸收缩,同时元素内容万一较多又能自动换行的场景可以不做任何flex属性设置。

2. flex:0和flex:none的区别和各自适用场景

flex:0等同于设置flex: 0 1 0%flex:none等同于设置flex: 0 0 auto

这两个值的图形示意如下图所示。

flex:0和flex:none分解示意

其中:

  • flex:0 1 0%表示flex-grow是0,flex-shrink是1,因此元素尺寸会收缩但不会扩展,在加上flex-basis:0%表示建议支持是0,因此,设置flex:0的元素的最终尺寸表现为最小内容宽度;
  • flex:0 0 auto表示元素尺寸不会收缩也不会扩展,再加上flex-basis:auto表示固定尺寸由内容决定,由于元素不具有弹性,因此,元素内的内容不会换行,最终尺寸通常表现为最大内容宽度。

举个例子,设置每个flex子项有足够多的内容,HTML代码如下所示:

<h4>flex:0</h4>
<div class="container flex-0">
    <item>范张范张范张</item>
    <item>范鑫范鑫范鑫</item>
    <item>范旭范旭范旭</item>
    <item>范帅范帅范帅</item>
    <item>范哥范哥范哥</item>
</div>
<h4>flex:none</h4>
<div class="container flex-none">
    <item>范张范张范张</item>
    <item>范鑫范鑫范鑫</item>
    <item>范旭范旭范旭</item>
    <item>范帅范帅范帅</item>
    <item>范哥范哥范哥</item>
</div>

然后对flex子项分别设置flex:0flex:none,CSS代码如下所示:

.container {
    display: flex;
}
.flex-0 item {
    flex: 0;
}
.flex-none item {
    flex: none;
}

结果如图下图所示:

flex:0和flex:none的布局效果示意

可以看到应用了flex:0的元素全部高高耸起,一柱擎天,表现为最小内容宽度;而应用了flex:none的元素则无视容器的尺寸限制,直接溢出容器,没有换行,表现为最大内容宽度。

适合使用flex:0的场景

由于应用了flex:0的元素表现为最小内容宽度,因此,适合使用flex:0的场景并不多,除非元素内容的主体是替换元素,此时文字内容就会庇护在替换元素的宽度下从而不会出现“一柱擎天”的排版效果。

该适用场景的布局示意如下图所示。

适用于flex:0的布局轮廓示意图

其中上图左侧部分的矩形表示一个图像,图像下方会有文字内容不定的描述信息,此时,左侧内容就适合设置flex:0,这样,无论文字的内容如何设置,左侧内容的宽度都是图像的宽度。

适合使用flex:none的场景

当flex子项的宽度就是内容的宽度,且内容永远不会换行,则适合使用flex:none,这个场景比flex:0适用的场景要更常见。

例如列表右侧经常会有一个操作按钮,对于按钮元素而言,里面的文字内容一定是不能换行的,此时,就非常适合设置flex:none,例如下面这个例子,示意了按钮使用了flex:none之后的布局变化,HTML和CSS代码如下所示:

<div class="item">
    <img src="1.jpg">
    <p>右侧按钮没有设置flex:none,表现为最小内容宽度。</p>
    <button>按钮</button>
</div>
<div class="item">
    <img src="1.jpg">
    <p>右侧按钮设置了flex:none,按钮正常显示了。</p>
    <button class="none">按钮</button>
</div>
.container {
    display: flex;
    padding: .5rem;
    border: 1px solid lightgray;
    background-color: #fff;
}
img {
    width: 3rem; height: 3rem;
    margin-right: .5rem;
}
button {
    align-self: center;
    padding: 5px;
    margin-left: .5rem;
}
.none {
    flex: none;
}

从代码可以看出两段内容的唯一区别就是下面的布局对按钮元素设置了flex:none,结果就有如下图所示的不同的布局效果。

适用于flex:none的对比效果示意图

不仅按钮正常显示了,整个布局会自动适配按钮的尺寸,也就是按钮文字多了,中间的文字内容宽度就会自动减小,整个布局依然是弹性的。

3. flex:1和flex:auto的区别和各自适用场景

flex:1等同于设置flex: 1 1 0%flex:auto等同于设置flex: 1 1 auto

这两个值的图形示意如下图所示。

flex:1和flex:auto分解示意

结合flex属性值的描述,我们可以得出flex:1flex:auto的行为表现:

元素尺寸可以弹性增大,也可以弹性变小,具有十足的弹性,但是flex:1在尺寸不足时会优先最小化内容尺寸,flex:auto在尺寸不足时会优先最大化内容尺寸。

上面的描述可以通过一个例子明白是什么意思,这一次是仅仅设置第1项的文字内容很多,HTML代码如下所示:

<h4>flex:1</h4>
<div class="container flex-1">
    <item>范张范张范张范张范张范张范张范张范张</item>
    <item>范鑫</item>
    <item>范旭</item>
    <item>范帅</item>
    <item>范哥</item>
</div>
<h4>flex:auto</h4>
<div class="container flex-auto">
    <item>范张范张范张范张范张范张范张范张范张</item>
    <item>范鑫</item>
    <item>范旭</item>
    <item>范帅</item>
    <item>范哥</item>
</div>

可以看出两段HTML结构和内容都是一样的,现在,对上下两端HTML设置不同的CSS样式,代码如下所示:

.flex-1 item {
    flex: 1;
}
.flex-auto item {
    flex: auto;
}

结果就会看到如下图所示的布局效果。

flex:1和flex:auto的对比效果示意

上图鲜明地体现了flex:1flex:auto的区别,虽然都是充分分配容器的尺寸,但是flex:1的尺寸表现更为内敛(优先牺牲自己的尺寸),flex:auto的尺寸表现则更为霸道(优先扩展自己的尺寸)。

从某种程度上讲,flex:1的表现神似table-layout:fixedflex:auto的表现神似table-layout:auto

适合使用flex:1的场景

当希望元素充分利用剩余空间,同时不会侵占其他元素应有的宽度的时候,适合使用flex:1,这样的场景在Flex布局中非常的多。

例如所有的等分列表,或者等比例列表都适合使用flex:1或者其他flex数值,适合的布局效果轮廓如下图所示。

flex:1适合用在固定比例的列表中

以及适用于无规律布局中动态内容元素,我们不妨继续使用flex:none那里演示的例子进行说明。

下面这段HTML和CSS代码下的按钮元素是换行显示的(就是前面出现过的按钮不换行的例子的HTML代码)。

<div class="item">
    <img src="1.jpg">
    <p>右侧按钮没有设置flex:none,表现为最小内容宽度。</p>
    <button>按钮</button>
</div>
.container {
    display: flex;
    padding: .5rem;
    border: 1px solid lightgray;
    background-color: #fff;
}
img {
    width: 3rem; height: 3rem;
    margin-right: .5rem;
}
button {
    align-self: center;
    padding: 5px;
    margin-left: .5rem;
}

除了设置<button>元素flex:none以外,在这个例子中,我们还可以设置<p>元素flex:1实现类似的效果。

p {
    flex: 1;
}

结果就有如下图所示,<p>元素设置了flex:1之后,按钮元素正常显示了。

主体动态的文本元素设置flex:1之后的效果对比示意

适合使用flex:auto的场景

当希望元素充分利用剩余空间,但是各自的尺寸按照各自内容进行分配的时候,适合使用flex:auto

flex:auto多用于内容固定,或者内容可控的布局场景,例如导航数量不固定,每个导航文字数量也不固定的导航效果就适合使用flex:auto效果来实现,我做了个很简单的示意,代码如下所示:

<nav class="flex">
  <span>首页</span>
  <span>排行榜</span>
  <span>我的订单</span>
  <span>个人中心</span>
</nav>
nav span {
    flex: auto;
    line-height: 3rem;
    background: #444;
    color: #fff;
    text-align:center;
}
span + span {
    border-left: 1px solid #eee;
}

此时大家就可以看到一个基于内容自动分配宽度的自适应导航效果了,效果如下图所示,文字越多的导航占据的宽度越大,这完全是浏览器自动分配的。

flex:auto实现的基于内容宽度自动分配的导航效果示意

三、最后总结一下

最后总结一下:

  • flex:initial表示默认的flex状态,无需专门设置,适合小控件元素的分布布局,或者某一项内容动态变化的布局;
  • flex:0适用场景较少,适合设置在替换元素的父元素上;
  • flex:none适用于不换行的内容固定或者较少的小控件元素上,如按钮。
  • flex:1适合等分布局;
  • flex:auto适合基于内容动态适配的布局;

关于CSS中元素尺寸的总结

image.png clientHeight / clientWidth 获取元素的可见的宽度和高度

元素内部的高度 / 宽度,包含内边距,但不包括水平滚动条、边框和外边距。如上图的蓝绿色区域。 此属性会将获取的值四舍五入取整数。这些属性都是不带px的,返回都是一个数字,可以直接进行计算。这些属性都是只读的,不能修改

offsetHeight / offsetWidth 获取元素的整个的宽度和高度

元素的“所有占用区域”的高度 / 宽度,包含内边距、水平滚动条、边框和外边距。如上图被黄色区域包裹起来的所有部分。

scrollHeight / scrollWidth 获取元素整个滚动区域的宽度和高度

类似于上方的 clientHeight / clientWidth,不同在于 clientHeight / clientWidth 在元素设置 overflow 后,不包含隐藏不可见的高度部分。而 scrollHeight / scrollWidth 却包含隐藏的那部分高度。

scrollTop / scrollLeft 获取元素可见区域垂直/水平滚动条滚动的距离(当前滚动的距离)

记 由元素的 clientHeight / clientWidth 框起来的区域——元素的可见区域 为 A;

记 由元素的 scrollHeight / scrollWidth 框起来的区域——元素可以滚动的区域 为B;

A的顶部减去B的顶部,这个距离为 scrollTop——元素可见区域在垂直方向上滚动的距离。

A的左边减去B的左边,这个距离为 scrollLeft——元素可见区域在水平方向上滚动的距离。

  • 当满足scrollHeight - scrollTop == clientHeight,说明垂直滚动条滚动到底了
  • 当满足scrollWidth - scrollLeft == clientWidth,说明水平滚动条滚动到底了

也就是说:元素的外边距是可以超出元素可见区域可以滚动的区域的。

offsetParent 获取当前元素的定位父元素

会获取到离当前元素最近的开启了定位的祖先元素。如果所有的祖先元素都没有开启定位,则返回body。

offsetTop / offsetLeft 当前元素相对于其定位父元素的垂直/水平偏移量(包括父元素的padding等)

getBoundingClientRect()方法

返回元素的大小及其相对于视口的位置(眼睛看得见的文档区域)。注意:这里包括还没有在视口之中出现的元素。即值可以是负数,也可以是大于视口尺寸的值。

返回值为

{
    bottom: xx,  //元素底部离视口顶部的距离
    height: xx,   //元素高度,和元素的clientHeight属性一致,但比它精确,会保留小数
    left: xx,  //元素左边离视口左侧的距离
    right: xx,  //元素右边离视口左侧的距离
    top: xx,   //元素上部离视口顶部的距离
    width: xx,   //元素宽度,和元素的clientWidth属性一致,但比它精确,会保留小数
}

这个方法的支持性为 100%

image.png

Intersection Observer API

在不考虑IE兼容的情况下,Intersection Observer API是性能更高的选择。

三、实践练习例子:

关于视口交互,有很多动画库是通过检测元素和视口的位置关系实现的。比如说 ScrollReveal (scrollrevealjs.org)

四、课后个人总结:

本章的知识点需要大量的实例和参考资料来辅助理解。

五、引用参考:

我参考了MDN和CSDN关于这两个知识点的论述。