盒尺寸中的 4 个盒子 content box、padding box、border box 和 margin box 分别对应 CSS 世界中的 content、padding、border 和 margin 属性,我把这 4 个属性称为“盒尺寸四大家族”,下面我们一个一个揭开“四大家族”鲜为人知的一面。
content
根据“外在盒子”是内联还是块级我们可以把元素分为内联元素和块级元素,而根据是否具有可替换内容,我们也可以把元素分为替换元素和非替换元素。那什么是替换元素呢?
通过修改某个属性值呈现的内容就可以被替换的元素就称为“替换元素”。因此,<img>、<object>、<video>、<iframe>
或者表单元素<textarea>
和<input>
都是典型的替换元素。
替换元素除了内容可替换这一特性以外,还有以下一些特性:
- 内容的外观不受页面上的 CSS 的影响。用专业的话讲就是在样式表现在 CSS 作用域之外。如何更改替换元素本身的外观?需要类似 appearance 属性,或者浏览器自身暴露的一些样式接口。
- 有自己的尺寸。在 Web 中,很多替换元素在没有明确尺寸设定的情况下,其默认的尺寸(不包括边框)是 300 像素×150 像素,如
<video>、<iframe>
或者<canvas>
等,也有少部分替换元素为 0 像素,如<img>
图片,而表单元素的替换元素的尺寸则和浏览器有关,没有明显的规律。 - 在很多 CSS 属性上有自己的一套表现规则。 比较具有代表性的就是 vertical-align属性,对于替换元素和非替换元素,vertical-align 属性值的解释是不一样的。比方说vertical-align 的默认值的 baseline,很简单的属性值,基线之意,被定义为字符 x 的下边缘,在西方语言体系里近乎常识,几乎无人不知,但是到了替换元素那里就不适用了。为什么呢?因为替换元素的内容往往不可能含有字符 x,于是替换元素的基线就被硬生生定义成了元素的下边缘。
替换元素的display值
所有的替换元素都是内联水平元素,也就是替换元素和替换元素、替换元素和文字都是可以在一行显示的。但是,替换元素默认的 display 值却是不一样的。见表4-1
替换元素有很多表现规则和非替换元素不一样,其中之一是宽度和高度的尺寸计算规则,简单描述一下就是,替换元素的 display 是 inline、block和 inline-block 中的任意一个,其尺寸计算规则都是一样的。
替换元素尺寸计算
我个人将替换元素的尺寸从内而外分为 3 类:固有尺寸、HTML 尺寸和 CSS 尺寸。
- 固有尺寸指的是替换内容原本的尺寸。例如,图片、视频作为一个独立文件存在的时候,都是有着自己的宽度和高度的。这个宽度和高度的大小就是这里的“固有尺寸”。对于表单类替换元素,“固有尺寸”可以理解为“不加修饰的默认尺寸”。比方说,你在空白页面写上
<input>
,此时的尺寸就可以看成是<input>
元素的“固有尺寸”。这就是输入框、下拉框这些表单元素默认的 font-size/padding/margin 等属性全部使用 px 作为单位的原因,因为这样可以保证这些元素的“固有尺寸”是固定大小,不会受外界 CSS 的影响。 - HTML 尺寸这个概念略微抽象,我们不妨将其想象成水煮蛋里面的那一层白色的膜,里面是“固有尺寸”这个蛋黄蛋白,外面是“CSS 尺寸”这个蛋壳。“HTML 尺寸”只能通过HTML 原生属性改变,这些 HTML 原生属性包括
<img>
的 width 和 height 属性、<input>
的 size 属性、<textarea>
的 cols 和 rows 属性等。 - CSS 尺寸特指可以通过 CSS 的 width 和 height 或者 max-width/min-width 和max-height/min-height 设置的尺寸,对应盒尺寸中的 content box。
这 3 层结构的计算规则具体如下
- 如果没有 CSS 尺寸和 HTML 尺寸,则使用固有尺寸作为最终的宽高。例如,下面的HTML 代码:
<img src="1.jpg">
假设1.jpg这张图片原尺寸是256 ×192,则在页面中此图所呈现的宽高就是 256 像素×192 像素。 - 如果没有 CSS 尺寸,则使用 HTML尺寸作为最终的宽高。仍以图片举例:
<img src="1.jpg" width="128" height="96">
我们通过 HTML 属性 width 和 height 限定了图片的 HTML 尺寸,因此,最终图片所呈现的宽高就是 128 像素×96 像素。 - 如果有 CSS 尺寸,则最终尺寸由 CSS 属性决定。我们继续上面的例子:img { width: 200px; height: 150px; }
<img src="1.jpg" width="128" height="96">
此时固有尺寸、HTML 尺寸和 CSS 尺寸同时存在,起作用的是 CSS 属性限定的尺寸,因此,最终图片所呈现的宽高就是 200 像素×150 像素。 - 如果“固有尺寸”含有固有的宽高比例,同时仅设置了宽度或仅设置了高度,则元素依然按照固有的宽高比例显示。
- 如果上面的条件都不符合,则最终宽度表现为 300 像素,高度为 150 像素,宽高比 2:1。例如:
<video></video>
在所有现代浏览器下的尺寸表现都是 300 像素×150 像素。 - 内联替换元素和块级替换元素使用上面同一套尺寸计算规则。例如:
img { display: block; }
<img src="1.jpg">
虽然图片此时变成了块级,但是尺寸规则还是和内联状态下一致,因此,图片呈现的宽高还是 256 像素×192 像素。这也是为何图片以及其他表单类替换元素设置 display:block 宽度却没有 100%容器的原因。
如果单看规则,似乎面面俱到,无懈可击。但是,实际上,意外还是发生了,这个意外就是最常用的<img>
元素。如果任何尺寸都没有,则元素应该是 300 像素×150 像素,这条规则、和`这些元素都符合,唯独图片例外。如果一个img元素不设置任何属性、样式,他在不同浏览器中的宽高是不同的。并不是固定的300*150。
其实尺寸不一样不打紧,因为我们平时使用都会设置尺寸,不可能像这样放任不管,但是,如果表现型也不一样,那就麻烦了。我们从一个常用功能的前端小技巧说起。
Web 开发的时候,为了提高加载性能以及节约带宽费用,首屏以下的图片就会通过滚屏加载的方式异步加载,然后,这个即将被异步加载的图片为了布局稳健、体验良好,往往会使用一张透明的图片占位。例如:
<img src="transparent.png">
实际上,这个透明的占位图片也是多余的资源,我们直接:
<img>
然后配合下面的 CSS 可以实现一样的效果:
img { visibility: hidden; }
img[src] { visibility: visible; }
注意,这里的直接没有 src 属性,再强调一遍,是直接没有,不是 src="",src=""在很多浏览器下依然会有请求,而且请求的是当前页面数据。当图片的 src 属性缺省的时候,图片不会有任何请求,是最高效的实现方式。
内容的外观不受外部css影响?
上面说到替换元素的内容外观不受外部css影响,但是我们经常使用css设置img宽度等属性,都可以成功,这样看来岂不是发生了冲突?其实并不冲突:
因为图片中的 content 替换内容默认的适配方式是填充(fill),也就是外部设定的尺寸多大,我就填满、跟着一样大。换句话说,尺寸变化的本质并不是改变固有尺寸,而是采用了填充作为适配 HTML 尺寸和 CSS 尺寸的方式,且在 CSS3 之前,此适配方式是不能修改的。
CSS3 新世界中,<img>
和其他一些替换元素的替换内容的适配方式可以通过object-fit 属性修改了。例如,<img>
元素的默认声明是 object-fit:fill,如果我们设置 object-fit:none,则我们图片的尺寸就完全不受控制,此时不管我们怎么设置css宽高,显示的也只会是图片本来的尺寸。
替换元素与非替换元素的距离
观点一:拿img举例,他和非替换元素的区别可以看成有无src属性,当img没有src属性时,不同浏览器的触发其成为非替换元素的条件不同。下面为img为非替换元素时的提示小技巧,当img有src后就会立马变成替换元素,就会正确显示对应图片
img::after {
/* 生成 alt 信息 */
content: attr(alt);
/* 尺寸和定位 */
position: absolute; bottom: 0;
width: 100%;
background-color: rgba(0,0,0,.5);
transform: translateY(100%);
/* 来点过渡动画效果 */
transition: transform .2s;
}
img:hover::after {
/* alt 信息显示 */
transform: translateY(0);
}
观点二: 替换元素和非替换元素之间只隔了一个 CSS content 属性!
替换元素之所以为替换元素,就是因为其内容可替换,而这个内容就是 margin、border、padding 和 content 这 4 个盒子中的 content box,对应的 CSS 属性是 content,所以,从理论层面讲,content 属性决定了是替换元素还是非替换元素。
前面已经证明了,没有src属性的<img>
是非替换元素,但是,如果我们此时使用content属性给它生成一张图片呢?
img { content: url(1.jpg); }
<img>
// 结果和下面 HTML 的视觉效果一模一样:
<img src="1.jpg">
另外还有一点很有意思,如果图片原来是有 src 地址的,我们也是可以使用 content 属性把图片内容给置换掉的,于是,我们就能轻松实现 hover 图片变成另外一张图片的效果
<img src="laugh.png">
img:hover {
content: url(laugh-tear.png);
}
然后,还有一点有必要说明一下,content 属性改变的仅仅是视觉呈现,当我们以右键或其他形式保存这张图片的时候,所保存的还是原来 src 对应的图片。
不仅如此,使用 content 属性,我们还可以让普通标签元素变成替换元素。举个例子,官网的标志往往都会使用<h1>
标签,里面会有网站名称和标志图片使用背景图,类似下面的代码:
<h1>《CSS 世界》</h1>
h1 {
width: 180px;
height: 36px;
background: url(logo.png);
/* 隐藏文字 */
text-indent: -999px;
}
下面展示一个创新的方法,大家可以在移动端试试。还是一样的 HTML 代码,但是 CSS代码微调了一下:
h1 {
content: url(logo.png);
}
我们简单分析一下:传统 CSS 代码的<h1>
是一个普通元素,因此需要设定尺寸隐藏文字;
但是,后面使用 content 属性实现,<h1>
分分钟就变成了替换元素,文字自动被替换,同时尺寸规则就是替换元素的尺寸规则,完美适应原始图片大小。
此外,虽然视觉上文字被替换了,但是屏幕阅读设备阅读的还是文字内容,搜索引擎 SEO抓取的还是原始的文本信息,因此,对页面的可访问性等没有任何影响。看起来这是一个完美的文字换图显示方案,但还是有一些局限。前文也说到了,替换元素的固有尺寸是无法设置的,
如今在移动端 retina 屏幕几乎是标配,为了图片显示细腻,往往真实图片尺寸是显示图片尺寸的两倍。于是问题就来了,使用 content 生成图片,我们是无法设置图片的尺寸的,只能迫不得已使用一倍图,然后导致图片看上去有点儿模糊。所以,要想在移动端使用该技术,建议使用 SVG 矢量图片
content属性与替换元素关系剖析
CSS 世界中,我们把 content 属性生成的对象称为“匿名替换元素”
content 属性生成的内容都是替换元素?没错,就是替换元素!
也正是这个原因,content 属性生成的内容和普通元素内容才会有很多不同的特性表现。我这里举几个简单的例子。
- 我们使用 content 生成的文本是无法选中、无法复制的,好像设置了 userselect:none 声明一般,但是普通元素的文本却可以被轻松选中。同时,content 生成的文本无法被屏幕阅读设备读取,也无法被搜索引擎抓取
- 不能左右:empty 伪类。
- content 动态生成值无法获取
padding
padding对于内联元素(不包括图片等替换元素)表现则有些许不同。这种不同的表现让很多很多的前端同事有这么一个错误的认识:内联元素的 padding 只会影响水平方向,不会影响垂直方向。
这种认知是不准确的,内联元素的 padding 在垂直方向同样会影响布局,影响视觉表现。只是因为内联元素没有可视宽度和可视高度的说法(clientHeight 和 clientWidth 永远是0),垂直方向的行为表现完全受 line-height 和 vertical-align 的影响,视觉上并没有改变和上一行下一行内容的间距,因此,给我们的感觉就会是垂直 padding 没有起作用。
如果我们给内联元素加个背景色或者边框,自然就可以看到其尺寸空间确实受 padding影响了。尺寸虽有效,但是对上下元素的原本布局却没有任何影响,仅仅是垂直方向发生了层叠
CSS 中还有很多其他场景或属性会出现这种不影响其他元素布局而是出现层叠效果的现象。比如,relative 元素的定位、盒阴影 box-shadow 以及 outline 等。这些层叠现象虽然看似类似,但实际上是有区别的。其分为两类:一类是纯视觉层叠,不影响外部尺寸;另一类则会影响外部尺寸。 box-shadow 以及 outline 属于前者,而这里的 inline 元素的padding 层叠属于后者。
这种效果可以用来增加a元素的垂直方向的点击面积而不会影响布局
padding值
其一,和 margin 属性不同,padding 属性是不支持负值的;其二,padding 支持百分比值,但是,和 height 等属性的百分比计算规则有些差异,差异在于:padding 百分比值无论是水平方向还是垂直方向均是相对于宽度计算的!
padding实现图形
margin
margin百分比也是都基于宽度。
margin合并
块级元素的上外边距(margin-top)与下外边距(margin-bottom)有时会合并为单个外边距,这样的现象称为“margin 合并”。
这种合并分为两种情况:
情况一:兄弟元素margin-top与margin-bottom合并
<p style="margin-bottom: 10px;">123</p>
<p style="margin-top: 10px;>456</p>
情况二:父子元素同一方向margin合并,子元素为第一个(margin-top)或最后一个子元素(margin-bottom)
<div class="father" style="margin-top:80px;">
<div class="son" style="margin-top:80px;"></div>
</div>
那该如何阻止这里 margin 合并的发生呢?
对于 margin-top 合并,可以进行如下操作(满足一个条件即可):
• 父元素设置为块状格式化上下文元素;
• 父元素设置 border-top 值;
• 父元素设置 padding-top 值;
• 父元素和第一个子元素之间添加内联元素进行分隔。
对于 margin-bottom 合并,可以进行如下操作(满足一个条件即可):
• 父元素设置为块状格式化上下文元素;
• 父元素设置 border-bottom 值;
• 父元素设置 padding-bottom 值;
• 父元素和最后一个子元素之间添加内联元素进行分隔;
• 父元素设置 height、min-height 或 max-height。