上一篇讲的是样式怎么落到元素上:选择器命中谁、冲突时谁赢。现在假设你已经拿到了想要的那条声明——给一个盒子写了 width: 200px。打开页面量一下,屏幕上它却有 240px 宽。
差的 40px 不是浏览器算错了,而是 width 在默认情况下根本不管整个盒子。要解释这件事,得先看清一个元素在 CSS 里究竟是什么形状。
每个元素都是一个四层的矩形盒子
浏览器排版时,把每个元素表示成一个矩形盒子,由内到外分成四个区域,每个区域有自己的边界 1:
- 内容区(content):文字、图片真正放置的地方。默认情况下
width、height量的就是这一块。 - 内边距(padding):内容和边框之间的空白,用
padding设置。它在盒子内部,背景色会铺满它,所以看起来像是盒子的一部分。 - 边框(border):包在内边距外面,用
border设置。 - 外边距(margin):盒子最外层的空白,用来把相邻元素推开,用
margin设置。
四层是 叠加 的关系,不是四选一。所以“这个元素占多大空间”有两个不同的答案:盒子自身可见的宽度到边框外沿为止,而它实际占用、把邻居推开的范围,要一直算到外边距外沿。说清大小,必须指明是哪一层。
默认的 width 量的是内容区
box-sizing 的初始值(也就是默认值)是 content-box。在这个模式下,你写的 width、height,以及 min-width、max-width 这些同类属性,作用在 内容区 上,内边距和边框之后再往上加 2。
看一个具体例子:
1.box {
2 width: 160px;
3 height: 80px;
4 padding: 20px;
5 border: 8px solid red;
6}按默认的 content-box 算:
- 内容区宽 px;
- 左右各加 px 内边距,共 px;
- 左右各加 px 边框,共 px;
- 屏幕上盒子的实际宽度是 px。
高度同理,实际是 px。这组数字来自 MDN 的官方示例 2。回到开头的困惑:width: 200px 再加上 padding: 20px,正好多出 40px,盒子就变成了 240px。
注意计算里的最后一步:width 只写了一次,但内边距和边框都是左右各一份,所以要乘以 2。忘了乘 2 是最常见的算错方式。
box-sizing: border-box 改变了什么
每次都要心算着加上内边距和边框很麻烦,box-sizing: border-box 就是把这个加法交给浏览器。它让 width 表示的正好是盒子对外的可见宽度,也就是内容区、内边距、边框三者合起来的外沿(border box),但不含外边距 2。
同一个盒子改成 border-box 之后:
- 总宽仍然是 px;
- 内容区宽变成 px;
- 总高 px,内容区高变成 px。
区别在这里最清楚:内边距和边框不再是“加上去”,而是从你给的那个数字里 吃掉 一块。盒子对外的尺寸变成了你写多少就是多少,代价是内容区被压小。
两条不能忘的边界
第一条:内容区不能是负数,最小被压到 0。如果内边距和边框加起来已经超过你指定的宽度,盒子会变得比你写的更宽 4。比如指定 width: 100px; box-sizing: border-box; padding: 0 60px;,左右内边距一共 120px,已经超过 100px,浏览器只能把内容区压成 0,盒子实际宽 120px。所以 border-box 并不能把元素压到任意小。
第二条:外边距永远不在 width 里面。不管用哪种 box-sizing,外边距都在盒子外部,等盒子的宽高都算完了才加在四周 3。所以“让这个盒子连同左右各 10px 的间距一共占 250px”,是要你自己从 250px 里减掉 20px 的,box-sizing 帮不上。
另外,并非所有元素都默认用 content-box:浏览器给 table、select、button,以及 type 是 submit、checkbox、color 这类按钮和勾选形的 input 设的就是 border-box;普通文本框的 input 用的仍是 content-box 2。同样是设 width,在按钮上和在自己的 div 上含义并不一样。
实际代码里通常怎么设
既然 border-box 更好用,常见做法是全局统一:
1html {
2 box-sizing: border-box;
3}
4*,
5*::before,
6*::after {
7 box-sizing: inherit;
8}这段写法有三处值得解释。
第一,必须把 * 也写进去,因为 box-sizing 本身 不继承 4。只给 html 设了 border-box,页面上的 div 还是 content-box。
第二,这里用的是 inherit(从父元素继承),而不是直接在 * 上写 border-box。这样如果哪个元素被单独改回 content-box,它内部的后代也会跟着变回去,不会出现一半一种模式的情况。这也是 MDN 教程推荐的写法 3。
第三,*::before、*::after 是伪元素,它们在页面上同样是盒子,同样会被 box-sizing 影响。现在照着抄下来就行,不用深究这两个词的写法。
遇到了尺寸不对,先在 DevTools 里看
这类问题不需要靠心算排查。在 Chrome 的开发者工具里打开 Elements 面板,把鼠标移到某个元素上,页面上会出现四色叠加层:内容区蓝色、内边距绿色、边框黄色、外边距橙色。选中元素后,右侧 Computed 面板里有一张盒模型图,直接把四个区域各自四个方向的数值都标出来。
尺寸和预期不符时,就在这张图上看是哪一层超出了你的估计,再回去改对应属性,比在脑子里做加减法可靠得多。
这套规则适用的范围
以上内容完整适用于块级盒子,比如 div、p、h1。行内元素只用到盒模型的一部分:给一段文字里的 <a> 设 width、height 通常不起作用,padding 和 margin 的表现也和块级元素不同。
原因不在盒模型本身,而在于“元素在页面上怎么排布”这套规则,也就是正常流(normal flow)和 display——那是下一篇要讲的事。
小练习(答案在折叠里)
一个 div 写了 box-sizing: content-box; width: 300px; padding: 0 25px; border: 5px solid gray; margin: 10px;。它在页面上横向占多少空间?
答案:内容区 300px,内边距 px,边框 px,盒子可见宽度是 360px;再加上左右外边距 px,它把邻居推开、实际占用的横向空间一共是 380px。如果改成 border-box,可见宽度就是 300px,内容区被压到 px,加上外边距一共占用 320px。