本文章内容来自图书《CSS世界》
以上内容针对CSS2.1,不包括CSS3
一、流特性
流是CSS世界一种基本的定位和布局机制,是引导元素排列和定位的一条看不见的水流。可以理解为现实世界的一套物理规则,“流”跟现实世界的“水流”有异曲同工的表现。
1.1 块级元素
特征:一个水平流上只能单独显示一个元素,多个块级元素则换行显示。
盒子理论:
每个元素都有两个盒子,外在盒子和内在盒子。外在盒子负责元素是可以一行显示,还是只能换行显示;内在盒子(又叫容器盒子)负责宽高、内容呈现什么的。
于是,按照display的属性值不同,值为block的元素的盒子实际由外在的“块级盒子”和内在的“块级容器盒子”组成,值为inline-block的元素则由外在的“内联盒子”和内在的“块级容器盒子”组成,值为inline的元素则内外均是“内联盒子”。
标记盒子,专门用来放圆点、数字这些项目符号。list-item元素会出现项目符号是因为除了块级元素,还生成了附加的标记盒子。
1.2 width/height 作用细节
1.2.1 width/height 作用在哪个盒子上:是内在盒子,也就是容器盒子
1.2.2 width: auto
width的默认值是auto。它在不同场景下 至少包含了以下4种不同的宽度表现。
(1)充分利用可用空间。比如说,
这些元素的宽度默认是100%于父级容器
(2)收缩与包裹。典型代表就是浮动、绝对定位、inline-block元素或table元素
(3)收缩到最小。最容易出现在table-layout为auto的表格中。当每一列空间都不够时,文字能断就断,中文随便断,英文单词不能断。于是,第一列被无情的每个字都断掉demo.cssworld.cn/3/2-1.php
(4)超出容器限制。存在一些特殊情况,内容很长的连续的英文和数字,或者内联元素被设置了white-space: nowrap,则表现文字超出容器也不换行示意。
在CSS世界,盒子分“内在盒子”和“外在盒子”,显示也分“内部显示”和“外部显示”,尺寸也分“内部尺寸”和“外部尺寸”。内部尺寸表示尺寸由内部元素决定,外部尺寸表示宽度由外部元素决定。
上面介绍的width:auto的第一个表现,也就是div默认宽度是100%,是唯一一个外部尺寸表现。也正是“流”的精髓。
1、外部尺寸与流体特性
(1)正常流宽度
水平流动性,不是看上去的宽度100%显示这么简单,而是一种margin/padding/border/content内容区域来自动分配水平空间的机制。
实践应用:
给块级元素设置width: 100%是多余的,避免出现类似代码;
无宽度原则。尽量借助流特性,外部尺寸的块级元素一旦设置了宽度,流动性就丢失了。demo.cssworld.cn/3/2-3.php
(2)格式化宽度
仅出现在“绝对定位模型”中,也就是position属性值为absolute或fixed的元素中。默认情况下,绝对定位元素的宽度表现是“包裹性”,即宽度由内部尺寸决定,有一种情况宽度由外部尺寸决定,当left/right或top/bottom对立方位的属性值同时存在时,元素的宽度表现为“格式化宽度”,“格式化宽度”具有完全的流体性。
实践应用:水平居中,或自适应布局等
2、内部尺寸与流体特性
内部尺寸有下面三种表现形式:
(1)包裹性
除了包裹,还有自适应性。自适应性,指的是元素尺寸由内部元素决定,但永远小于包含块容器的尺寸。例如按钮是inline-block元素,是展示包裹性最好的例子,按钮文字越多宽度越多,但如果文字足够多,则会在容器的宽度处自动换行。(button会自动换行,input标签按钮,默认white-space:pre不会换行,需要将pre值重置为默认的normal)
实践应用:文字少的时候居中显示,文字超过一行的时候居左显示。demo.cssworld.cn/3/2-5.php
(2)首选最小宽度
指的是元素最适合的最小宽度。外部容器的宽度240像素如果设为0,里面的inline-block元素的宽度不是0.在CSS中,图片和文字的权重要远大于布局,此时所表现的宽度是“首选最小宽度”。具体规则如下:
- 东亚文字(如中文)最小宽度为每个汉字的宽度
- 西方文字最小宽度是由特定的连续的英文字符单元决定。如果想让英文字符和中文一样,可以试试word-break:break-all。
实践应用:方便对症下药排查问题。
(3)最大宽度
最大宽度就是元素可以有的最大宽度。最大宽度实际等同于包裹性元素设置white-space:nowrap声明后的宽度。如果内部没有块级元素或者块级元素没有设置宽度值,则“最大宽度”实际上是最大的连续内联盒子的宽度。
实践应用:比方说5张图片,每张图片的宽度是200px,假设图片元素紧密排列,最大宽度就是1000像素。 实际开发时候,懒得计算,可能直接设置容器width:2000px,宽度足够大,作用是保证图片不会因为容器宽度不足而不在一行内显示。
有一种实现自定义滚动的方法就是根据内部元素的尺寸和容器的关系,通过修改内部元素的位置实现滚动,如果是模拟水平滚动,只能使用最大宽度,这样,滚动到底的时候才是真的到底。
1.2.3 width值的作用细节
以上都是width: auto相关表现。width属性使用具体数值,比如width: 100px,是作用在了content-box上。因此如果水平方向给定padidng和border的大小,元素尺寸会超过100px。
这种宽度表现不合理,如以下2点:
1、流动性丢失,上面讲到width: auto,则元素的流体特性表现的会如水流般充满整个容器,而一旦设定了width具体数值,则元素的流动性会被阻断。
2、设置padding/border时会导致元素宽度变大,与常规认知不一致。
为了避免这种不合理,可以采用CSS流体布局下的宽度分离原则。
使用方法:width独立占用一层标签,而padding、border、margin利用流动性在内部自适应呈现。
原理:父元素定宽,子元素因为width使用的默认值auto,所以会如水流般自动填满父级容器。
.father {
width: 180px;
}
.son {
margin: 0 20px;
pading: 20px;
border: 1px solid;
}
复制代码
除了上述的无宽度布局,有没有即无需计算,又无需额外嵌套标签的实现呢?有,就是可以改变width作用细节的box-sizing属性。
1.2.4 改变width/height作用细节的box-sizing
box-sizing属性的作用是改变width的作用细节。内在盒子的4个盒子,分别是content box、padding box、border box和margin box。默认情况下,width是作用在content box上的,box-sizing的作用就是可以把width作用的盒子变成其他几个。
如果设置width: 100px,box-sizing: border-box就是让100像素的宽度直接作用在border box上,content box可以形成局部流动性,和padding一起自动分配width值。
注意:box-sizing不支持margin-box,只有当元素没有水平margin的时候,box-sizing才能真正无计算。
box-sizing可以设置的值如下:
content-box | 默认值 |
---|---|
padding-box Firefox | 曾经支持过 |
border-box | 全线支持 |
margin-box | 从未支持过 |
1.2.6 height相关
height: auto相比width: auto更单纯,正常情况下,有几个元素盒子,每个多高,然后一加,就是最终的高度值了。
- 关于height:100%
height和width还有个比较明显的区别就是对百分比单位的支持。对于width属性,就算父元素width为auto,其百分比值也是支持的;但是,对于height属性,如果父元素height为auto,只要子元素在文档流中,其百分比值完全就被忽略了。因此,对于普通文档流中的元素,百分比高度值要想起作用,其父级必须有一个可以生效的高度值。
- 如果让元素支持height: 100%
(1)设定显示的高度值,或者可以生效的百分比值高度
html,body {
height: 100%;
}
复制代码
(2)使用绝对定位
此时的height: 100%就会有计算值,即使祖先元素的height计算为auto也是如此。
注意:绝对定位的宽高百分比计算是相对于padding box的,也就是说会把padding大小值计算在内,但是,非绝对定位元素则是相对于content box计算的。
div {
height: 100%;
position: absolute;
}
复制代码
1.3 CSS min-width/max-width和min-height/max-height二三事
- max-width和max-height的初始值是none,min-width和min-height初始值是auto(min-*的初始值与MDN不符,是本书作者的分析和测试得出的结论)。
- max-width会覆盖width,超越!important。 demo.cssworld.cn/3/3-1.php
- min-width也会覆盖max-width,此规则发生在min-width和max-width冲突的时候,例如:
.container {
min-width: 1400px;
max-width: 1200px;
}
复制代码
最后会按照min-width表现,max-width被忽略,.container元素表现为至少1400px宽。
1.4内联元素
- 内联盒模型,一些概念
- 内联盒子
这里的内联盒子实际指的是元素的外在盒子。用来决定元素是内联还是块级。该盒子可以分为内联盒(下方截图中红色部分)子和匿名内联盒子(没有内联标签,下方截图中黄色部分)
- 行框盒子
每一行就是一个“行框盒子”,每个“行框盒子”又是由一个一个“内联盒子”组成的。
- 包含盒子,又叫包含块
标签就是一个“包含盒子”,此盒子由一行一行的“行框盒子”组成。
- 幽灵空白节点:
是内联盒模型中非常重要的一个概念,具体指的是:在HTML5文档声明中,内联元素的所有解析和渲染表现就如同每个行框盒子的前面有一个“空白节点”一样。这个“空白节点”永远透明,不占据任何宽度,看不见也无法通过脚本获取,就好像幽灵一样,但又确确实实存在,表现如同文本节点一样。
二、流的破坏与保护
CSS中正常的流内容或流布局实现的是方方正正、规规矩矩的效果,有时候我们希望一些特殊的布局表现,例如文字环绕、元素固定在某个位置等则有些捉襟见肘。CSS中有一类属性,专门通过破坏正常的“流”来实现一些特殊的表现样式。有破坏就有保护。
2.1 float
浮动的本质是为了实现文字环绕效果。浮动常被用作布局,其缺点在于缺少弹性,容错性很糟糕。浮动的特性包括:
- 包裹性,由包裹和自适应两部分组成。
包裹。假设浮动元素父元素宽度200px,浮动元素子元素是一个128px宽度的图片,则此时浮动元素宽度表现为包裹,就是里面图片的宽度128px,代码如下:
.father {width: 200px;}
.float {float: left;}
.float img {width: 128px}
<div class="father">
<div class="float">
<img sr="1.jpg">
</div>
</div>
复制代码
自适应。如果浮动的子元素不只是一张128px的图片,还有一大波普通的文字,例如:
.father {width: 200px;}
.float {float: left;}
.float img {width: 128px}
<div class="father">
<div class="float">
<img sr="1.jpg">我是帅哥我是帅哥我是帅哥我是帅哥我是帅哥我是帅哥我是帅哥我是帅哥我是帅哥我是帅哥
</div>
</div>
复制代码
此时浮动元素宽度就是自适应父元素的200px的宽度,最终的宽度表现也是200px。
- 块状化并格式化上下文:元素一旦float值不为none,则其display计算值就是block或者table,因此text-align属性也无法控制元素的左右对齐,也永远不会出现下面的样式组合:
span {
display: block:
float: left;
}
span {
float: left;
vertical-align: middle;
}
复制代码
- 破坏文档流,float作用机制
float属性有个著名的特性表现,就是会让父元素高度塌陷。
高度塌陷只是让跟随的内容可以和浮动元素在一个水平线上,但这只是实现“环绕效果”的条件之一,要想实现真正的环绕效果,就需要另一个特性“行框盒子和浮动元素的不可重叠性”,也就是“行框盒子如果和浮动元素的垂直高度有重叠,则行框盒子在正常定位状态下只会跟随浮动元素,而不会发生重叠”。
// demo演示 float-principle.html
块状盒子中的行框盒子被浮动元素限制,行框盒子的区域永远就这么大,只要不改变当前的布局方式,无法通过其他CSS属性改变这个区域大小的。这就是为什么浮动后面元素margin负无穷大依然无效的原因,只有外部的块状容器盒子尺寸变大,而和浮动元素垂直方向有重叠的“行框盒子”依然被限死在那里。
如果为一个元素设置了具体的高度值,就不需要担心float属性造成的高度塌陷的问题了?
“文字环绕效果”是由“父级高度塌陷”和“行框盒子区域限制”共同作用的结果,定高只能解决“父级高度塌陷”带来的影响,但是对“行框盒子区域限制”却没有任何效果,结果导致的问题是浮动元素垂直区域一旦超出高度范围,或者下面元素margin-top负值上偏移,就很容易后面的元素发生“环绕效果”。
演示地址:demo.cssworld.cn/6/1-1.php
原因:内联状态下的图片底部是有间隙的,也就是.float这个浮动元素的实际高度并不是64px,而是要比64px高几个像素,带来的问题就是浮动元素的高度超出.father几像素。于是下面的文字所在的“行框盒子”和浮动元素在垂直位置有了重叠,区域被限制。
实践应用:了解float属性的作用机制,可以让我们一下子知道一些意料之外场景发生的原因以及如何快速对症下药。
- 没有任何margin合并
float与流体布局:
两栏布局,float配合margin。demo.cssworld.cn/6/1-2.php
如果是百分比宽度,或者多栏布局,也同样适用。
clear属性:是让自身不能和前面的浮动元素相邻,clear属性对后面的浮动元素不闻不问。
clear属性只有块级元素才有效,而::after等伪元素默认都是内联水平,这就是借助伪元素清除浮动影响时需要设置display属性值的原因。
.clear:after {
content: "";
display: "table"; //也可以是block或者list-item
clear: both;
}
复制代码
然而,clear:both只能在一定程度上清楚浮动,仍然存在导致自适应布局错位的情况。demo.cssworld.cn/6/2-1.php
2.2 BFC
块级格式化上下文。如果一个元素具有BFC,内部子元素无论设置什么属性,都不会影响外部的元素。因此如果触发了BFC,就无须用clear:both属性去清除浮动的影响了。
触发BFC的常见情况:
- 根元素
- float值不为none
- overflow的值为auto、scroll或hidden
- display的值为table-cell、table-caption和inline-block中的任何一个;
- position的值不为relative和static
BFC最重要的用途是实现更健壮、更智能的自适应布局。
2.3 overflow
想要彻底清除浮动的影响,最适合的属性不是clear而是overflow。一般使用overflow:hidden,利用BFC的“结界”特性彻底解决浮动对外部或兄弟元素的影响。并且overflow:hidden声明不会影响元素原先的流体特性或宽度表现。
overflow本职工作:剪裁。
一个设置了overflow:hidden声明的元素,假设同时存在padding和border属性,则当子元素内容超出容器宽度高度限制的时候,剪裁的边界是border-box的内边缘。
关于overflow-x和overflow-y
如果overflow-x和overflow-y属性中的一个值设置为visible而另一个设置为scroll、auto或hidden,则visible的样式表现会如同auto。也就是说,除非overflow-x和overflow-y的属性值都是visible,否则visible会被当成auto来解析。即永远不可能实现一个方向溢出剪裁或滚动,另一个方向内容溢出显示的效果。
2.4 position:absolute
- 特性:
- 块状化:设为position:absolute或fixed的元素,其display计算值就是block或table。float值不为none也会使元素块状化,当absolute和float同时存在时,float属性是无任何效果的,没有理由将absolute和float同时使用。
- 破坏性:指的是破坏正常的流特性。absolute虽然破坏流,但是本身还是受普通的流体元素布局、位置甚至一些内联相关的CSS属性影响的。
- 包裹性
- 包含块:绝对定位元素计算的容器是第一个position不为static的祖先元素。
- 根元素(很多场景可以看成是)被称为初始包含块,其尺寸等同于浏览器可视窗口的大小
- 对于其他元素,如果该元素的position是relative或者static,则“包含块”由其最近的块容器祖先盒的content box边界形成。
- 如果元素position:fixed,则包含块是初始包含块
- 如果元素position:absolute,则包含块由最近的position不为static的祖先元素建立。
- 小知识:height:100%和height:inherit,对于普通元素两者没什么区别,对于绝定位元素就不一样了,height:100%是第一个具有定位属性值的祖先元素的高度,而height:inherit则是单纯的父元素的高度继承。
- 具有相对特性的无依赖absolute绝对定位: 推荐使用
一个经典的问题,一个绝对定位元素,没有任何left/top/right/bottom属性设置,并且其祖先元素全部都是非定位元素,其位置在哪里?答案是当前位置,不是浏览器窗口左上方。
absolute是非常独立的CSS属性值,其样式和行为表现不依赖其他任何CSS属性就可以完成。无依赖绝对定位本质上是“相对定位”,仅是不占据CSS流的尺寸空间。建议只在静态交互效果上使用,动态的列表还是建议使用js来计算和定位。
- 应用1:各类图标定位 示例地址 demo.cssworld.cn/6/5-4.php demo.cssworld.cn/6/5-5.php
position:absolute配合margin偏移实现定位,与inline-block对齐相比的好处在于,inline-block对齐最终行框高度并不是20px,因为中文下沉,图标居中,要想视觉上水平,图标vertical-align对齐要比实际低一点儿,这就会导致最终整个行框的高度不是预期的20px,而是21px或者更大。但是,如果使用无依赖绝对定位实现,则不用担心这一问题,因为绝对定位元素不会改变正常流的尺寸空间,行框高度一直是纯文本所在的20px高度。
-
应用2: 示例地址demo.cssworld.cn/6/5-6.php
-
应用3:下拉列表的定位 示例地址 demo.cssworld.cn/6/5-7.php
-
深入“无依赖绝对定位”:
元素position: absolute后的display计算值都是块状的,但是其定位的位置和没有设置position:absolute的时候的位置相关。
如下,“标题”文字后面跟随的标签,一个是内联的,一个是块状的
<h3>标题<span class="follow">span</span></h3>
<h3>标题<div class="follow">div</div></h3>
复制代码
- absolute与text-align:
示例地址demo.cssworld.cn/6/5-8.php (注意:只有原本是内联水平的元素绝对定位后可受text-align影响)
absolute和float一样,都可以让元素块状化,但是text-align居然可以改变absolute元素的位置。这里之所以产生了位置变化,本质上是“幽灵空白节点”和“无依赖绝对定位共同作用的结果。”
具体渲染原理如下:
(1)由于是内联水平,
标签中存在一个宽度为0、看不见摸不着的“幽灵空白节点”,也是内联水平,于是受text-align:center影响而水平居中显示。
(2)设置了position:absolute,表现为“无依赖绝对定位”,因此在幽灵空白节点后面定位显示;同时由于图标不占据空间,这里的幽灵空白节点正好在
元素水平中心位置显示,于是我们就看到了图片从
元素水平中间位置显示的效果。
- absolute与overflow:
如果overflow:hidden不是定位元素,同时绝对定位元素和overflow容器之间也没有定位元素,则overflow无法对absolute元素进行剪裁。
- absolute与clip:剪裁属性,要想起作用,元素必须是绝对定位或者固定定位。
- absolute的流体特性:
当absolute遇到left/top/right/bottom属性的时候,absolute元素才真正变成绝对定位元素。此时原本的相对特性丢失,但是如果仅设置一个方向的绝对定位,另一个方向的定位依然保持相对特性。
当一个绝对定位元素,其对立定位方向属性同时具有定位数值时,流体特性就发生了,表现为自适应宽度。(left和right属于水平对立定位方向,而top和bottom属于垂直对立定位方向)
当绝对定位元素处于流体状态的时候,各个盒模型相关属性的解析和普通流体元素都是一模一样的。margin负值可以让元素的尺寸更大,并且可以使用margin:auto让绝对定位元素保持居中。
例:使用absoluete流体特性实现绝对定位元素水平居中效果
.element {
width: 300px;
height: 200px;
position: absolute;
left: 0;
right: 0;
top: 0;
bottom: 0;
margin: auto
}
复制代码
2.5 position:relative
虽说relative/absolute/fixed都能对absolute的“包裹性”以及“定位”产生限制,但只有relative可以让元素依然保持在正常的文档流中。
relative的定位有两大特性:一是相对自身进行偏移定位;二是无侵入,即当relative进行偏移定位的时候,一般情况下不会影响周围元素的布局。demo.cssworld.cn/6/6-1.php
注意:
- 相对定位元素是相对于自身进行定位的,但是相对定位元素的left/top/right/bottom的百分比值是相对于包含块计算的,而不是自身。
- 当相对定位元素同时应用对立方向定位值的时候,只有一个方向的定位属性会起作用。默认的文档流是自上而下、从左往右,因此top/bottom同时使用,top生效,left/right同时使用的时候,left生效。
应用建议,relative的最小化影响原则:
(1)尽量不使用relative,如果想定位某些元素,看看能否使用“无依赖的绝对定位”
(2)如果场景受限,一定要使用relative,则该relative务必最小化。关于最小化的理解,参考下方代码:
需求:在某个模块的右上角定位一个图标,初始HTML结构如下:
<div>
<img src="https://juejin.cn/post/icon.png">
<p>内容1</p>
<p>内容2</p>
<p>内容3</p>
<p>内容4</p>
...
</div>
复制代码
如果让大家去实践的话,我估计十有八九都会如下面这样实现:
<div style="position:relative">
<img src="https://juejin.cn/post/icon.png" style="position:absolute;top:0;right:0;">
<p>内容1</p>
<p>内容2</p>
<p>内容3</p>
<p>内容4</p>
...
</div>
复制代码
但是,如果采用“relative的最小化影响原则”则应该是如下面这般实现:
<div>
<div style="position:relative">
<img src="https://juejin.cn/post/icon.png" style="position:absolute;top:0;right:0;">
</div>
<p>内容1</p>
<p>内容2</p>
<p>内容3</p>
<p>内容4</p>
...
</div>
复制代码
此时,relative影响的元素只是图标,后面的“内容1”之类的元素依然保持干净。“relative的最小化影响原则”可以规避复杂场景可能出现的样式问题隐患(层级被提高等),也更便于维护。比方说过了一个月,我们不需要右上角的图标了,直接移除这个relative最小化的单元即可,但是,如果relative是外层容器上的,这段样式代码你敢删吗?万一其他元素定位也需要呢?万一relative还有提高层叠顺序的作用呢???万一呢????
2.6 position:fixed
- position:fixed 不一样的包含块
position:fixed 固定定位元素的包含块是根元素,我们可以将其近似看成元素。所以,如果想把某个元素固定定位在某个模块的右上角,下面的做法是没有用的:
<div class="father">
<div class="son"></div>
</div>
.father {
width: 300px;
height: 200px;
position: relative;
}
.son {
width: 40px;
height: 40px;
position: fixed;
top: 0;
right: 0;
}
复制代码
.son元素只会跑到窗体的右上角,是不会在.father的右上角的,relative对fixed定位没有任何限制作用。
但是,事实上是可以实现把.son元素精确定位到.father的右上角的,和“无依赖的绝对定位”类似,就是“无依赖的固定定位”,利用absolute/fixed元素没有设置left/top/right/bottom的相对定位特性,可以将目标元素定位到我们想要的位置,处理如下:
<div class="father">
<div class="right">
<div class="son"></div>
</div>
</div>
.father {
width: 300px;
height: 200px;
position: relative;
}
.right {
height: 0;
text-align: right;
overflow: hidden;
}
.son {
display: inline;
width: 40px;
height: 40px;
position: fixed;
margin-left: -40px;
}
复制代码
- position:fixed的absolute模拟
有时候我们希望元素既有不跟随滚动的固定定位效果,又能被定位元素限制和精准定位。可以使用position:absolute进行模拟,原来就是:页面的滚动使用普通元素替代,此时滚动元素之外的其他元素自然就有了“固定定位”效果。
使用positon:absolute进行模拟需要一个滚动容器,假设类名是.page:
<html>
<body>
<div class="page">固定定位元素</div>
<div class="fixed"></div>
</body>
</html>
html, body {
height: 100%;
overflow: hidden;
}
.page {
height: 100%;
overflow: auto;
}
.fixed {
position: absolute;
}
复制代码
整个网页的滚动条由.page元素产生,而非根元素,此时.fixed元素虽然是绝对定位,但是并不在滚动元素内部,自然滚动时不会跟随,如果固定定位效果,同时本身绝对定位,可以使用relative进行限制或overflow进行剪裁。
应用建议:谨慎使用。将网页的窗体滚动变成内部滚动,很多窗体滚动相关的小js组件需要跟着进行调整,并且可能会丢失一些浏览器内置行为.
- position:fixed与背景锁定
蒙层弹窗的交互,其中黑色半透明全屏覆盖的蒙层基本上都是使用position:fixed定位实现,但是蒙层无法覆盖浏览器右侧的滚动栏,并且鼠标滚动的时候后面的背景内容依然可以被滚动。该如何实现背景锁定?
position:fixed蒙层之所以出现背景依然滚动,那是因为滚动元素是根元素,正好是position:fixed的包含块。如果希望背景被锁定,可以借鉴上面“absolute模拟fixed定位”的思路,让页面滚动条由内部的普通元素产生即可。如果网站的滚动结构不方便调整,这需要借助JS来实现锁定。