CSS:深入盒模型
块状盒子和内联盒子瓜分了CSS布局的几乎全部江山,是流体布局的本质所在。
块状盒模型和内联盒模型
块状盒子为布局而生,内联盒子为内容而生。
块级元素对应的盒子就是块状盒子,它在水平方向上像水流一样铺开,一行只有一个块级盒子。而内联元素对应的是内联盒子,它会将自己的尺寸自然收缩到可以包裹住里面的内容,一行可以并排放置若干个内联盒子。
块状盒模型
从浮动清除说起
我们知道对于没有高度的容器是关不住浮动的,如:
1 |
|
对img设置浮动,不对div设置高度,图片会浮动出来,解决的办法通常是使用内墙法,升级版的内墙法应当是使用after伪元素代替添加的内墙,但是,这个after要这样添加:
1 |
|
即要把display设置为block才可以生效,如果是默认的inline,是没有用的,就是因为block天然具有换行的特性,用来清除浮动正合适,而inline只会让它跟在浮动元素的后面,无法起到清除浮动的作用。
list-item
真的是只有block才可以这样清除浮动吗?实际上display设置为table和list-item也是可以的,它们可以看作是特殊的块状元素,但是list-item在清除浮动的同时,会产生副作用:出现一个项目符号,可以通过设置list-style为none来消除掉,但这就太麻烦了,增加了代码量。
还有一个不推荐使用list-item的原因是:兼容性问题。在IE浏览器是不支持伪元素为list-item的,普通元素这样设置还是有效的。
那么问题是,为什么IE浏览器不支持伪元素为list-item?
块状元素的盒子
一说起块状元素的盒子,大家首先会想到四层盒模型,而从另一个角度来说,块状元素的盒子还有另一种组成方式。
首先是标记盒子。
起初人们以为块状盒子和内联盒子就能解决所有问题,可是没想到人们又需要一种list-item这样的东西来显式列表项,它和块状盒子挺像,但有个区别就是需要显式项目符号,于是,人们改造了它的盒子结构,在它的前面添加了一个附加盒子,叫做标记盒子,专门用来显示项目标记(圆点、方块、数字等项目符号)。
而IE浏览器无法将伪元素设置为list-item,也许就是没有为其设计标记盒子。
然后是双层盒子。
随着各种场景的需求,人们逐渐需要inline-block盒子来设计元素:外部表现为内联元素,不会在水平方向铺满容器,能够同一行并排显示;又在内部表现为块状,可以设置宽和高。那么此时人们又开始改造盒子了,inline-block盒子就可以看作是个双层盒子,外面是inline盒子,而内层是block盒子,这样才表现出了这样的行为。
当然也有inline-table盒子,表现为可以和文字(内联元素)同一行显示的表格,如:
HTML:
1 |
|
CSS:
1 |
|
内联盒模型
内联元素
内联元素不只是display为inline的元素,inline-block和inline-table也应当算作内联元素。从表现上看,内联元素就是可以和文字显示在一行的元素。
内联盒模型
(1)内容区域:在IE浏览器和FireFox浏览器下,内联的的内容区域基本可以认为是使用鼠标选中一段文字(或其他内联内容)时,显示出选中底色的部分。本质上内联盒的内容区域就相当于块状盒模型中的content-box。关于内联元素的内容区域,在“CSS:深入内联元素”一文中有更详细说明。
在IE浏览器和FireFox浏览器下,选中区域就和内容区域的范围一样;而在Chrome浏览器中,在图文混排时,选中区域要明显比内容区域大。
(2)内联盒子:内联标签(如span、a、em等)构成内联盒子,而光秃秃的文字构成匿名内联盒子。
(3)行框盒子:连续个内联盒子排列起来,可能排成一行,也可能排成多行,每行都构成一个行框盒子。
(4)包含盒子:一行行的行框盒子构成包含盒子,主要以标签为分界线,如p标签构成一个包含盒子包含多行。
支柱strut
(1)概念:在HTML5文档声明中,内联元素渲染的表现就像每个内联盒子以及行框盒子前方都有一个空白节点一样,这个空白节点永远透明、宽度为0、无法通过脚本获取,但它是确实存在的。规范中将这个空白节点称为strut,即支柱。
(2)证明:对于以下例子:
HTML:
1 |
|
CSS:
1 |
|
结果这个div是有高度的,并不是0,但span标签的高度却是0,也就是说在紧挨着span的前面有一个看不见的支柱。如果没有支柱的存在,没有内容的标签的高度应该是0,但这里因为有内联标签span,导致空标签被撑起了高度,实际上这个高度就是行高。
而为什么要为span设置inline-block呢,我们发现让span保持默认的inline的话,这个div高度就没有了,支柱就不见了。实际上是因为这个支柱与line-height行高息息相关,设置为inline-block才会使行高生效。
padding
padding是一位敦厚老实的农民,和它相处你会觉得温和又淳朴,实际使用中使用它很少会出现意料之外的情况,它只是安静又可靠地发挥着自己的作用。
padding指盒子的内补间,也就是填补在盒子边框和内容之间的空白,是为了使内容更加层次分明,使显示更加舒适美观。
块状元素的padding
对于块状元素,默认情况下box-sizing是content-box,因此使用padding是会增加盒子尺寸的,然而使用border-box就一定能使用width控制住盒子尺寸吗?不一定的。
如果padding设置得过于大了,盒子的实际尺寸会被撑大的,这时候width设置的值就如同摆设一样,此时的content表现为首选最小宽度。
块状元素padding的属性值只需要注意几点:
- padding的属性值是不支持负值的,和margin不同。
- padding的百分比值和margin的百分比值一样,都基于父容器的宽度来计算,这里的宽度不是width,而是内容区域(content-box)的宽度!但是,如果排版方向改为了纵向排版,那就基于高度来计算。
内联元素的padding
对于内联元素,大家总会有一种误会,以为padding只会影响到水平方向,是因为设置padding之后在垂直方向上看不到任何变化,但看不到变化并非没有。
内联元素没有可视宽度和高度的说法,垂直方向的表现完全取决于line-height和vertical-align,视觉上也就给我们一种垂直方向padding不起作用的感觉,这时候我们不妨给内联元素加上背景色:
这里为了突出显示垂直方向上padding的效果,只改变了垂直方向上的值。可以看到垂直方向上的内联元素padding对上下元素原本的布局没有任何影响,和relative元素的定位、盒阴影box-shadow以及轮廓outline类似。
但它们还是有区别的,有的是纯视觉效果,而有的会影响元素的外部尺寸,区分的方法是给父容器添加overflow:auto,会不会出现滚动条。如果层叠区超出父容器时有滚动条,说明元素尺寸被改变了,而没有滚动条则说明只是视觉效果罢了。
上述的属性中box-shadow和outline是纯视觉层叠效果,而内联元素的padding则改变了元素尺寸。
内联元素的padding属性值和块状元素一样的百分比计算方式,但内联元素的padding会断行!断行断到了下一行的padding如果有背景颜色还可能会遮挡上一行的文字。
对于div我们可以使用padding为50%得到一个正方形的元素,可是对一个空白的span得到的却是一个高比宽更大的长方形,原因是内联元素支柱的存在。此时设置font-size为0,或者通过别的方式使得支柱的高为0,这个span就会成为一个正方形了。
按钮的padding
ol、ul等很多元素和一些表单元素都会有默认的padding值的,而其中按钮button的padding值最常见且难以控制。
在chrome浏览器下,我们设置
1 |
|
这时按钮的padding就变成了0,但在Firefox浏览器下,左右依然会有padding,需要真正让按钮padding为0需要使用
1 |
|
在IE浏览器下就更离谱了,随着按钮文字变多,两边的padding会逐渐增大,要解决需要设置
1 |
|
此外按钮的padding和元素高度的计算在不同浏览器中还会有差别,有时候按钮的实际高度都不知道怎么算出来的,导致在编写代码时出现不可预知的结果,这不好,因此需要找到替代方案,即不使用原生的button:
(1)如果能满足需要的话,使用a标签来模拟。
(2)在表单中有些按钮自带交互行为,a标签无法模拟,可以使用label标签和button标签配合使用来解决:
HTML:
1 |
|
CSS:
1 |
|
只需要把label的for设置为按钮的id就可以在点击标签的时候具有和按钮同样的行为,而按钮又是隐藏的,来了一招偷天换日用label替换了按钮,这时只需要把label伪装成按钮即可。
padding的应用
增加链接或按钮的点击区域
对于按钮和链接,如果在移动端需要用手指来点击的话,比较小的按钮和链接可能会很难用手指点到,这时就可以使用内联元素的padding在不影响布局的情况下来增加它们的可点击区域大小,使得移动端用手指点击更加容易。
实现高度可控的分割线
如“登录 | 注册”中间的分隔竖线,如果使用管道符号“|”,因为它是字符,导致我们不好去控制它的高度,这时我们可以使用内联元素的padding来实现:
HTML:
1 |
|
CSS:
1 |
|
之所以如此设置padding和margin的值,是为了让其两边的空白相等,看一下图示就知道了:
带偏移的锚点定位
如果需要通过锚点定位到文档上的某个位置,但是文档带有导航栏,导航栏会遮挡内容,那么我们可以把原本的HTML:
1 |
|
改为:
1 |
|
并为span设置padding,这样就可以实现跳转时偏移。但这种方法要注意IE浏览器的兼容问题。
控制元素宽高比例
通过子绝父相来实现,父元素相对定位并用百分比padding来控制宽高比例,子元素在其中绝对定位来铺满父容器即可。如:
HTML:
1 |
|
CSS:
1 |
|
绘制导航栏三条杠
不使用伪元素,一层标签实现绘制导航栏三条杠图标:
1 |
|
在这里width属性控制图标整体的宽度,border-top控制上边一条杠,border-bottom控制下边一条杠,height控制中间一条杠,当然三条杠按理说应当设置为一样线宽,padding则用来控制三条杠之间的间距。
绘制轮播图双层圆点
轮播图下面会有圆点点来标记当前切换到了第几张图,当前的图可以表示为大一些的圆点,也可以表示为双层原点,这里使用一层标签实现双层圆点:
1 |
|
在这里width和height用来控制中间圆点的大小,border控制外层圆圈,padding用来控制内外侧之间的间距。
margin
padding主内,敦厚老实本分,而margin主外,热烈而又张扬,因此margin总有一些让你头疼的特性,但它能做的事也很多很多。
我们此处规定:
- 元素的尺寸:元素border-box的尺寸;
- 元素的内部尺寸:元素的padding-box的尺寸;
- 元素的外部尺寸:元素包括其margin的尺寸。
其中元素的外部尺寸竟然可能会是负值,因为margin可能是负的。
margin合并
垂直流的margin合并
(1)兄弟元素的上下margin合并:上下两个元素都有margin,那么上面元素的margin-bottom和下面元素的margin-top合并。
(2)父子元素的外部margin合并:如果父元素和它的第一个子元素都有margin-top,那么它们的margin合并,如果只有其中的一个有margin-top,效果都等同于父元素有对应的margin-top。父元素与最后一个子元素的margin-bottom同理。
对于(1),我们并不需要可以去解决,而(2)会使我们在一些情况下产生困扰,解决的方法有:
- 为父元素设置格式化上下文,即 overflow:hidden ;
- 为父元素设置 border-top/border-bottom 值;
- 为父元素设置 padding-top/padding-bottom 值;
- 父元素与子元素之间添加其他(内联)元素分隔。
空块级元素的margin合并
空的块级元素如果同时有上下margin,那么它的上下margin也会合并,如:
HTML:
1 |
|
CSS:
1 |
|
这里的box不是一个宽高1:1的格子,反而是2:1,这和padding实现的不同,原因就是双份的上下margin被合并了一份。
margin合并的计算
正正取大值,正负值相加,负负取最负。
margin合并的意义
(1)兄弟margin合并:最初的CSS设计的目的就是为图文排版服务的,对于图文,如果没有兄弟之间的margin合并,连续的段落就会有两份margin,而第一段与上面的元素却只有一份,视觉上显然不合理,合并之后且p标签的默认margin设置为1em,就实现了单倍行距的效果。
(2)父子margin合并:之所以子元素的margin就像加在了父元素上,是因为当父元素是一层空标签时,它应当只用来分组或分隔,而不具有语义,那么它就不能阻止子元素与上下元素进行margin合并。
(3)空标签margin合并:防止遗落的空标签太占据空间位置,让空标签作为一段空白成为间距,这个间距恰好是margin值。
margin自适应
如果父元素尺寸固定,子元素的宽度表现为自适应(充分利用可用空间),那么可以使用margin来改变元素宽度,而对于垂直方向上依然如此。总之,margin在自适应尺寸时可以决定元素尺寸。这种特性可用来实现一侧定宽的两栏自适应布局。
而margin值的自适应即设置了 margin:auto 的情况下,如果一侧有固定值,另一侧auto,则auto的一侧自动分配剩余空间,如果两侧都是auto呢?
对于宽度来说,div的宽度是自动铺满容器的,如果两侧都是 margin:auto 那么元素将会水平居中(两侧margin将会平分剩余空间),而高度却不行,因为高度不是自动填充容器的。因此是有自动填充特性的尺寸可以使用 margin:auto 来实现居中。
但其实有一种情况下高度也是有自动填充属性的,即在绝对定位下:
1 |
|
这时就可以使用 margin:auto 来实现水平垂直两个方向上的居中。
其实 margin:auto 天然就适合实现块状元素的左右对齐和居中,text-align则用来实现内联元素的左中右对齐居中。
margin无效的情况
(1)display为inline的非替换元素的垂直margin是无效的,但替换元素是有效的,且不会发生margin合并。
(2)表格中的tr、td元素或者display为table-row或table-cell的元素margin是无效的。毕竟表格内部的单元格是贴合的,肯定不能设置margin。
(3)元素浮动导致环绕文字的margin无效。
(4)内联导致过大的负margin无效。
margin的应用
一侧定宽的两栏布局
HTML:
1 |
|
CSS:
1 |
|
但是这里的视觉表现和DOM顺序是相反的,如果希望顺序一致:
HTML:
1 |
|
CSS:
1 |
|
元素两端对齐
(1)对元素设置统一样式,然后使用CSS3的nth-of-type选择器处理最后一个元素:
1 |
|
(2)让父元素带走空隙:
1 |
|
滚动容器底部留白
如果容器可以滚动,在IE和Firefox浏览器下是会忽略padding-bottom值的,Chrome等浏览器则不会。也就是说,在IE和Firefox浏览器下:
1 |
|
底部没有50像素的padding-bottom间隙。
此时只能使用子元素的margin-bottom来实现滚动容器的底部留白。
两栏等高布局
1 |
|
先看padding再看margin会更好理解一些:padding为容器提供了9999px的带背景色的高度,这时候下面的元素会被顶下去,再使用margin将下面的元素移回来9999px,使得下面的元素布局正常,再配合overflow: hidden隐藏掉多余的高度背景色,对布局没有影响的同时提供了足够的背景色高度,但是唯一的副作用是锚点定位或scrollIntoview时会出问题,因此只在保证不会有这些情况时使用该技巧。
border
border是个功勋卓越的英雄,其在图形构建、体验优化以及网页布局几方面大放异彩,同时保证了良好的兼容性和稳定性表现,因此当之无愧此殊荣。
边框宽度值
不支持百分比值
border-width不支持百分比值,因为边框在语义上是不应该随着元素变大变小而变粗变细的。
此外CSS中其他不支持百分比值的属性如outline、box-shadow、text-shadow,也是类似的原因。
关键字值
边框线宽的关键字值有:
- thin:薄的,值为1px。
- medium:默认值,值为3px。
- thick:厚的,值为4px。
那么问题来了,两个问题:
- 为什么默认值是3px的medium?平时使用时1px似乎更常用?
- 为什么是1、3、4的宽度值,而不是1、2、3?
其实这两个问题都是同一个原因:因为border-style为double时,只有3px才会有效果。
边框类型
double
既然说起了double类型的边框,那就先说它吧,以下是它的表现形式:
可以看到,当宽度为1px和2px时,根本体现不出来它是双线边框,只有3px时才会有效果!
所以border-width默认值才会是3px,是为了让所有的边框类型都能显示出它的效果;所以thin、medium、thick的值才会是1px、3px、4px而非1px、2px、3px,因为后者的thin和medium对于double边框都显现不出区别。
none
border-style的默认值,即默认无边框。因此这也是为什么设置了边框颜色和线宽也看不到边框的原因,你还必须设置边框类型才能看得到边框。
dashed
虚线边框。虚线边框可以看作是颜色区和透明区的方块交替出现组成的边框,那么颜色区的宽高以及颜色区与透明区的长度比例就不一样了。
在Chrome和Firefox浏览器下,颜色区宽高比例为3:1,颜色区与透明区的宽度比例为1:1;而在IE浏览器下颜色区宽高为2:1,颜色区和透明区宽度之比也是2:1。
dotted
虚点边框。值得说的也是不同浏览器下的差异。
在Chrome和Firefox浏览器下,虚点边框的点曾是方点,现在已经改为了规范中的圆点;而在IE浏览器下,它一直是规范中所规定的圆点。
其他边框
包括最常用的solid边框和最不常用的花里胡哨还难看兼容性还垃圾的其他边框:
- solid:实线边框。
- inset:内沟。
- outset:外沟。
- groove:沟槽。
- ridge:山脊。
边框颜色
色值继承
border-color的默认颜色就是color色值。
具有类似特性的CSS属性还有outline、box-shadow和text-shadow等。
如果我此时想要绘制一个添加图片的按钮:
方案一:使用背景色绘制加号
1 |
|
方案二:使用边框绘制加号
1 |
|
这两者的区别就在于方案一使用背景色绘制加号,因此设置hover时要设置两个伪元素的背景色,还要设置边框颜色的改变;而方案二全都使用边框实现,而边框颜色又默认计算自color,color属性又可以父子继承,因此设置hover时只需要设置一个color属性即可。
透明边框
透明边框即设置border-color为transparent,透明边框一般用于占位。
如果我们希望点击图标,在图标下方展示出一条横线表示它是被激活状态,如果点击图标出现下边框,那么图标会被往上挤,影响了布局,因为边框是有实际尺寸的。此时可以给边框设置透明,点击之后出现颜色,由于边框一直就在那儿,就不会有影响布局的困扰了。
border的应用
三道杠小标志
1 |
|
增加点击区域大小
某些小图标尤其是在移动端的时候,手指很不容易点到它,此时就需要为它增加点击区域大小。
之前我们用padding实现过增加内联元素的点击区域大小,原因是内联元素的padding丝毫不影响布局,而块状元素除了padding之外,还可以用透明边框来实现,或者外嵌一层标签专门控制元素点击区域。
三角形绘制
倒立的等腰三角形图标:
1 |
|
通过控制border-width可以绘制出不同胖瘦的等腰三角形。
还可以让两个边透明:
1 |
|
这样可以绘制出倒立的直角三角形,可以用于类似于人物对话框的绘制。
甚至可以两个直角三角形叠加绘制出任意的三角形。
两栏等高布局
之前用margin和padding结合实现了有弊端的两栏等高布局,而这里可以使用边框来创建没有锚点定位和滚动问题的两栏等高布局:
HTML:
1 |
|
CSS:
1 |
|
注意父级容器不能使用overflow:hidden清除浮动影响,因为溢出隐藏是基于padding-box的,如果设置了overflow:hidden,则左浮动的导航列表元素就会被隐藏掉。
其也是有局限性的,局限性在于border不支持百分比宽度,因此只适用于至少一侧是定宽的两栏等高布局。(如果不考虑IE8及以下版本的浏览器,可以考虑使用vw,近似实现百分比)
更多栏更稳健的多栏等高布局还是用table-cell更好。