这一章主要讲解下CSS在渲染的时候的理论原理。没人或书籍能够覆盖到css的所有细节,所以在我们日常使用中遇到css的问题的时候,了解CSS的渲染逻辑将有效帮助大家排查问题。
基本的盒模型
CSS的基本思想就是假定每个元素都是由1个或多个盒子组成的,称为element boxes。在每个box的中间有一块内容区域(content area),而包围内容区域的就是像padding, border, outline和margin的各种可选边框,之所以说是可选的因为所有这些边框的默认宽度都是0,如下图所示:
像padding,border和margin都有各自的设置选项,但outline就没有了。另外内容的背景也只应用与padding内部,margins都设置为透明的,这样可以让所有父元素的背景都看的见。padding是不准有负数的,但margin却可以,在后面咱们会详细讨论。
我们先从border开始,border类型包括solid和inset(inset就是内嵌的意思,看起来效果像凹槽)。border的颜色通过border-color设置,如果没有被设置的话,那就默认采用前景色(foreground color,不清楚么,如果说元素内容的颜色是不是清楚了),最后要记住border的宽度是不准设置负值的。
快览
首先我们来解释下后面要用到的几个重要的概念:
Normal flow:正常的流
这个指的是文本默认的渲染顺序,一般语言都是一样的:从左到右,从上到下。而在某些特殊的语言中会有所不同,而咱们HTML语言其实也是遵循这一规则的,既然前面说过每个元素都是box,所以正常的流的意思就是,元素的box默认以这种顺序排列。那怎么样才能跳出这种normal flow呢?方法也有很多,可以设置为float, position, 或是flexible box 和grid layout。Nonreplaced element: 无法替代的元素
这主要指那种内容放在document的元素,举个例子,就是一个无法替代的元素,因为其文本内容就在元素自己当中。
Replaced element: 可替代元素
和上面相反,这里的元素是可以替换的,例如img元素,他其实就是指向一张图片;同样,大部分的表单元素也都是可替换的。Root element: 根元素
这个我们很熟悉了,就是document文档树的根节点,像咱们常用HTML文档,其根节点就是html;而在XML文档中,根节点可以任意指定,例如RSS文件的根节点就是rss。Block box
block的盒子它就像积木一样是纵向堆叠的,常用的有段落p,标题h1~6,和最常用的div。想要生成block盒子的元素可以通过声明display:block来实现。Inline box
跟block相对的就是inline box, 常见的inline类型有strong和span等。如果想要变成inline box 只要声明display:inline就好。Inline-block box
这个box在内部表现的像block,而在外部表现的像inline。就是有点像可替换元素。
除了以上的这几种盒子,其实还有像table-ce的其他盒子,由于各种原因就不一一细说了。
containing block
对于CSS的盒子来说,父元素的box是很重要的,类似于编程的继承,子元素也会继承父元素的框架上下文(layout context)。在CSS中定义了一系列的规则来确定盒子的containing block。
来看个具体的例子:
This is a paragraph.
p的containing box就是div,而div的就是body。而这个body因为是第一个元素被称为初始的containing box,之所以这么特殊,单独命名因为视口viewport决定了该元素的布局大小,而不是由根元素的内容大小决定的。
调整element显示display
在css中应该是最重要的一个属性了:
改变默认的显示方式
nav默认谁inline的布局,如果我们想要改变他的显示方式,可以这样设置:
nav a {display: block;}
这会使所有的a元素都以block的元素来呈现。
下面再考虑另一种情况,就是把block的元素改变从inline的
- Bob C.
- Marcio G.
- Eric M.
- Kat M.
- Tristan N.
- Arun R.
- Doron R.
- Susie W.
#rollcall li {display: inline; border-right: 1px solid; padding: 0 0.33em;}
#rollcall li:first-child {border-left: 1px solid;}
虽然大部分时候都可以这样来操作,但是h5本身是有限制的,即block元素包含inline元素可以,但是inline元素中包含block是不允许的,尤其是对于某些默认的inline和block元素,来看个例子:
this is wrong!
上面的强行转换其实是没用的,display只能改变元素的展示,而不乏改变元素的类型,所以有些跟类型强绑定的属性是无法通过display来修改的。
接下面我们就来看看不同的box:包括block box, inline box, inline-block box 和list-item box。
Block box
block 盒子其实用的是最多的布局元素,但有时候其显示会超出你的预期,这就需要对该盒子的某些边界有清楚的了解,来看看下面这张图:
默认来说,block box的宽度设置的是left inner edge 到right inner edge的距离,height同理。
不过我们可以通过设置box-sizing属性来修改这一规则:
这个属性就是用来设置widht和height的定义规则的,默认的上面说了,如果设置为border-box,那么其宽度的界定将基于左右border边界的距离,同理可知padding-box的含义。
box-sizing水平的变化
通过改变box-sizing会导致水平方向计算的变化,来看个例子:
wideness?
水平方向的属性
共有7个属性决定水平的长度:margin-left, border-left, padding-left, width, padding-right, border-right 和 margin-right。具体参照下图:
之前有提到过的一个containing block的概念,其宽度就是这7中长度的和。而在这7个值里面,只有三个是默认设置为auto的,那三个呢?左右的margin和width,其余的值都需要设置数值,有些的默认值为0.
而width只能出现2中情况,auto和非负的数值。
使用auto
auto的意思就是自动调整,调整的目标就是子元素的整个长度等于父元素的宽度。
那有人就问啦,如果三个auto都设置了具体值,那怎么计算呢?这在CSS中有个专有名词overcontrained,意思就是限制过多了,那么css就用将margin-right强制设置为auto。
div {width: 500px;}
p {margin-left: 100px; margin-right: 100px;
width: 100px;} /* right margin forced to be 300px */
如果左右margin都设置了,但width为auto,那么width就承担起自动填充的任务:
p {margin-left: 100px; margin-right: 100px; width: auto;}
多个auto的情况
比如margin都设置为auto,那么左右都会设置相同的长度,来看个例子:
div {width: 500px;}
p {width: 300px; margin-left: auto; margin-right: auto;}
/* each margin is 100 pixels wide, because (500-300)/2 = 100 */
所以很多想要居中的内容都是讲margin设置为auto来实现的。
最后一种情况就是三个都设置为auto,那么margin就会都被设置为0,width会尽量填充父元素宽度。
负的margin
来看个例子:
div {width: 500px; border: 3px solid black;}
p.wide {margin-left: 10px; width: auto; margin-right: -50px; }
看起来已经超出了父元素的范围,但在数学上还是对的:
10px+0+0+540px+0+0−50px = 500px
还记得之前说的auto么,在这里,我们同样可以通过设置auto来抵消子元素的边界超出问题:
div {width: 500px; border: 3px solid black;}
p.wide {margin-left: 10px; width: 600px; margin-right: auto;
border: 3px solid gray;}
对应的公式如下:
10px+3px+0+600px+0+3px−116px = 500px
记住padding, border 和width是不能有负数的。
百分比
有时候百分比还是很有用的,尤其是在动态确定宽度比例的情况下:
playing percentages
不过如果要混合使用百分比和具体数值的话会让人很困惑:
mixed lengths
值的注意的是border是不接受百分比值的。
可替换元素
对于图片元素这种可替换元素来说,如果width指定为auto,那么元素的width将会是元素内容的实际宽度:
当然我们应该设置图片的实际宽度:
垂直方向的格式化
刚说完水平的,下来来看看垂直高度的几个有趣的地方。首先是一个元素的内容决定了元素的默认高度,同时内容的宽度也会影响高度。那就会出现两个问题,自己设置的高度高于内容高度,自己设置的高度低于内容高度。对于前者,会保留正常的空白;而对于后者,则依赖于另一个属性overflow:
垂直方向属性
对照水平方向,也是有7个属性:margin-top, border-top, padding-top, height, padding-bottom, border-bottom, and margin-bottom.
跟水平一样,垂直方向的auto也是这三个:
auto的触发规则与水平方向是一样的。
百分比高度
Half as tall
其中p的高度就是3em。由于top和bottom的margin如果设置为auto都会被强制设置为0,因此想要垂直居中的方式就是各设置为25%。但万一父元素的高度没有设置,那么这个百分比高度就会被重置为auto:
NOT half as tall; height reset to auto
垂直方向margin的合并
合并(可能翻译不太准确,英文为collapsing), 只会发生在margin中,而不会在padding和border中。
举个例子:
li {margin-top: 10px; margin-bottom: 15px;}
在一串li中设置每个的上下margin间距,一般是不是都会以为每个li的间距应该是25px,而实际上结果却是15px。这是因为发生了合并,在两个相邻的margin中会选取其中大的那个。
再来看个例子:
ul {margin-bottom: 15px;}
li {margin-top: 10px; margin-bottom: 20px;}
h1 {margin-top: 28px;}
从上面可知,这次li和h1的实际间隔其实是28px
负的margin和合并collapsing
来看另一个例子:
p.neg {margin-top: -50px; margin-right: 10px; margin-left: 10px; margin-bottom: 0; border: 3px solid gray;}
A paragraph.
A div.
再来看个margin为负的例子:
p.neg {margin-bottom: -50px; margin-right: 10px; margin-left: 10px; margin-top: 0;
border: 3px solid gray;}
A paragraph.
List Item
我们常用的list item本身其实是有不少特殊的显示规则的,比如marker,圆点和数字,而且这些都不在内容区域中,所以上面所说的一系列css规则在他们身上也是通用的:
内联元素
介绍完了block元素就可以来看看inline元素了,常见的有em和a标签,这两个都是不可替代的,而像image则是可替代的元素。注意我们后面讨论的内容不适用于table元素,在CSS2中引入了很多新的属性和行为来出来表单元素的显示,而这远远超过了本书的范围。
可替换和不可替换的元素在inline模式下CSS的处理是有所区别的。
Line Layout 线框
首先我们要确定的就是inline的内容是怎么放的,因为这不像block元素那么简单的创建个block 盒子,而且其他元素是不能共存在同一块区域的。那人们自然会很好奇,inline元素在block元素中是如何展示及修改样式的。
为了了解线是怎么生成的,首先来看下单行长文本的情况,如下图所示:
span {border: 1px dashed black;}
我们给span元素设置了一个简单的虚线边框。
基本概念
在我们进一步深入之前,让我们来回顾下线框中用到的基本概念:
- 匿名文本:
这个主要指的是没有包含在inline元素里面的内容,比如
I'm so happy!
其中的i'm happy!就是匿名文本,注意空格也算是字符。
Em box
这是在给定字体中定义的,或者称为字符box。在CSS中期em box的高度由font-size来定义。leading
leading其实是font-size和line-height的差。一般来说,这个差值将会一分为二,均匀分布在内容区域的top和bottom部分。leading只会应用在不可替换的元素上面。
inline 不可替换元素
接下来我们就来一步步 看下inline元素的创建过程,首先引入的是font-size的大小来决定内容区域的高度。如果一个元素的font-size为15px的话,那么内容区域的高度就是15px,如下图所示:
接下来需要考虑的就是元素的行高,同时注意与font-size的高度差。如果元素的font-size=15px而line-height=21px那么这个差就是6px,接下来浏览器会将这6px均分到上下两部分,如下图所示:
让我们假定下面是没问题的:
This is text, some of which is emphasized, plus other text
which is strongly emphasized and which is
larger than the surrounding text.
可以看到,大多数文本的字体大小为12px,而strong的行高为24px,但由于行高是继承的,所以strong继承的行高为12px。对于strong来说line-height与font-size的差为-12px,所以上下都有-6px的分配,所以strong所代表的inline box是要小于内容区域的,最后的效果如下图所示。
从上图看出,中间那行会比其他两行高,但却没有完全包裹全部的文本内容。
垂直对齐
如果我们改变inline box的垂直对齐方式,高度计算的原理还是一样的:
This is text, some of which is emphasized, plus other text
which is strongly emphasized and that is
larger than the surrounding text.
上面的变化同时抬升了storng元素的内容区域和inline box,因为strong元素决定这inline box的上边界,因此这一改变会同时抬升inline box的上边界:
让我们考虑另一种情况:
This is text, some of which is emphasized,
plus other text that is strong
and tall and is
larger than the surrounding text.
虽然样式跟之前是一样的,但又没有发现tall文本的高度对齐到了inline box的上边界。因为tall的文本具有相同的fontsize和line-height。
再来看下:
This is text, some of which is emphasized,
plus other text that is strong
and tall and is
larger than the surrounding text.
由于tall文本的行高小鱼font-size,tall文本的inline box小鱼内容其余,这一小小的改变改变了tall文本的高度:
再来看看设置tall行高高于font-size的情况:
This is text, some of which is emphasized, plus other text
that is strong
and tall and that is
larger than the surrounding text.
最后结合本章内容总结下vertical-align的关键词:
- top:对齐元素inline box的上边界
- bottom: 对齐元素inline box的下边界
- text-top: 对齐父元素文本上边界
- text-bottom: 对齐父元素文本下边界
- middle: 对齐元素inline box的垂直中点
- super: 上移内容区域和inline box,具体依赖浏览器实现
- sub:跟super一样,下移内容
- <百分比>:基于line-height的百分比来移动
管理行高
为了防止重叠,我们建议使用em单位:
p {line-height: 1em;}
big {font-size: 250%; line-height: 1em;}
Not only does this paragraph have "normal" text, but it also
contains a line in which some big text is found.
This large text helps illustrate our point.
通过设置big的行高,我们增大了对应inline box,而且不用改变所有行的行高。同时1em正好对应字体的大小:
设置行高的比例因子
最好的设置行高的方式就是设置纯数字,可以被所有的元素所继承:
body {line-height: 1.5;}
p {font-size: 15px; line-height: 1.5;} small {font-size: 66%;}
big {font-size: 200%;}
This paragraph has a line-height of 1.5 times its font-size. In addition, any elements within it such as this small element also haveline-heights 1.5 times their font-size...and that includes this big element right here. By using a scaling factor, line-heights scale to match the font-size of any element.
p {font-size: 15px; line-height: 1.5;} small {font-size: 66%;}
big {font-size: 200%; line-height: 1em;}
添加box 属性
想看看为inline 元素添加border的效果么:
inline元素的border属性其实是由font-size控制的,而不是line-height。 换句话说,如果一个span元素的font-size为12px然后行高为36px,那么其实际的内容区域的高度还是12px。但是border还是会包裹在inline元素周围的.
接下来来看看padding属性,padding的添加并不会改变内容的高度
span {padding: 4px;}
改变breaking 行为
在上一节,我们看到了一个inline不可替代元素跨行显示的例子,它本身其实是单行的文本,每个字符被单独的box包围,行末被单独分割。这种默认的行为可以通过box-decoration-break来修改:
就两个值:slice和clone。slice就是之前的默认值。而对于clone,其主要特定为将每个片段都作为独立得box,那这个独立是什么意思呢?来看个例子:
发现在下方的clone选项里面,背景图案不依赖文本独立克隆了背景信息。box-decoration-break属性虽然大多用在inline box中,不过其实它适用于任何的存在break的元素中。
inline 可替换元素
说到可替换元素,直接想到的就是img了。如下图所示:
p {font-size: 15px; line-height: 18px;}
img {height: 30px; margin: 0; padding: 0; border: none;}
可替换元素最大的特点就是就有自己的固有高度和宽度,而这是会影响布局的,在inline 元素中嵌入img,那么整行的line box就会被img的高度给撑开(假如img固有高度大于line-height)。上图中,由于图片没有margin,padding,border,因此inline box的高度就等于img的content 高度,也就是30px。
p {font-size: 15px; line-height: 18px;}
img {vertical-align: 50%;}
the image in this sentence will be raised 9 pixels.
替代元素和baseline
从上面的讨论中可以明白inline的可替换元素默认情况下是沿着baseline来排布的。如果元素有底部的padding,margin或是border的话,那么内容区域也会相应的抬升。而由于替代元素自己是没有baseline的,所以对齐的就是inline box的baseline:
inline-block 元素
inline-block是在CSS2.1引入的聚合属性,融合了inline和block。那这个inline-block到底是什么意思呢?其核心就表现为对外显示inline属性,就像image一样,而对内则表现出block属性,可以设置相应的width和height属性。让我们来看个具体的例子:
This text is the content of a block-level level element. Within this block-level element is another block-level element. Look, it's a block-level paragraph.
Here's the rest of the DIV, which is still block-level.
This text is the content of a block-level level element. Within this block-level element is an inline element. Look, it's an inline paragraph.
Here's the rest of the DIV, which is still block-level.
This text is the content of a block-level level element. Within this block-level element is an inline-block element. Look, it's an inline-block paragraph.
Here's the rest of the DIV, which is still block-level.
然后在上面的三个div分别应用下面的样式:
div {margin: 1em 0; border: 1px solid;}
p {border: 1px dotted;}
div#one p {display: block; width: 6em; text-align: center;}
div#two p {display: inline; width: 6em; text-align: center;} div#three p {display: inline-block; width: 6em; text-align: center;}
再举个具体的使用例子,比如在导航栏中有5个链接,一边想要他们inline显示,而同时要设置他们的宽度为父元素的20%,name就可以使用inline-block来设置:
nav a {display: inline-block; width: 20%;}
Flow 显示
flow和flow-root的属性还是值得一说的。其实CSS的显示分为两个类型:外部类型和内部类型。像之前说的inline,block都是控制外部类型显示的,而flow就表示内部元素的排列方式。
display:flow默认会同时绑定block和inline的外部框架,如果没有定义inline,name就以block形式展示:
#first {display: flow;}
#second {display: block flow;}
#third {display: inline flow;}
上面的例子中first和second显示结果为block,而third则表现为inline box。
所以一般在display后面的参数中,第一个表示外部类型,而第二个表示内部类型。比如:display: inline table。含义就是在外面以inline形式表示,而对内则生成一个table的上下文。
说完了flow,下面看看flow-root,这个值和flow不一样,默认就是block类型的。这个值一般是针对root 元素的,一般就是html。
最后来看下display 新老值得对应关系:
老的值 | 新的值 |
---|---|
block | block flow |
inline | inline flow |
inline-block | inline flow-root |
list-item | list-item inline flow |
table | block table |
inline-table | inline table |
flex | block flex |
inline-flex | inline flex |
grid | block grid |
inline-grid | inline grid |
内容显示
另一个新增的display值就是contents,这个值的用处试讲元素从页面格式中去除。来看个例子:
ul {border: 1px solid red;}
li {border: 1px solid silver;}
- The first list item.
- List Item II: The Listening.
- List item the third.
上面的例子会渲染一个带红色白框的ul list,而如果我们在ul上加上display:contents,name浏览器在渲染的时候就会像忽略外框的ul一样,如图所示:
其他的display值
在本章中我们其实还有很多display的值没有被覆盖到,大多数都是跟ruby相关的值,直到2018年底也没得到很好的支持。
计算得出的值
如果元素设置为float或是position,那么计算出的display值也可能发生变化。事实上,display,position和float这三个值是有制约关系的。
如果一个元素的position设置为absolute,那么float就被自动设为none。而对于浮动和绝对定位的元素,其计算值由下表给出:
小结
虽然CSS的format模型在有些地方是反直觉的,但随着使用的深入会慢慢理解它们。block元素是比较好理解的,而inline元素相比较就会显得比较麻烦。