Better

Ethan的博客,欢迎访问交流

再读《CSS 世界》内容总结

由于之前读 《CSS 世界》有些匆忙,趁这段时间,重新阅读了一下,收获还是特多的,尤其是内联盒模型那一块,对于 CSS 一些奇怪的现象有了很好的理解。

概述

CSS 世界构造的基石是 HTML,而 HTML 最具代表的两个基石 div 和 span 正好是 CSS 世界中块级元素和内联级元素的代表。

CSS2 的设计是面向图文展示,而 CSS3 新世界

  • 布局更为丰富
    • 媒介查询以及响应式布局特性(如图片元素 srcset,CSS 的 object-fit 属性)
    • flex 布局
    • grid 布局
  • 视觉表现长足进步
    • 圆角、阴影和渐变
    • transform 变化
    • filter 滤镜和混合模式
    • animation 动画

流、元素与基本尺寸

正是由于块级元素具有换行的特性,因此理论上它都可以配合 clear 属性来清除浮动带来的影响

.clearfix::after {
    content: '';
    clear: both;
    display: block; /* 或 table 或 list-item,很重要,不可缺少*/
}

按照 display 的属性值不同,值为 block 的元素的盒子实际由外在的块级盒子和内在的块级容器盒子组成,值为 inline-block 的元素则由外在的内联盒子和内在的块级容器盒子组成,值为 inline 的元素则内外均是内联盒子。

三无准则:无宽度、无图片、无浮动,目的是使CSS布局模块化以及增强可扩展性。

  • 无宽度:具体指的是没有固定的宽度值,外部尺寸的块级元素一旦设置了宽度,流动性就丢失了,所谓流动性并不是看上去的宽度 100% 那么简单,而是一种 margin/border/padding 和 content 内容区域自动分配水平空间的限制
  • 无图片:能用 CSS 实现的就不要使用图片。好处:减少http的请求数,降低加载资源大小,提高可维护性、扩展性与重用性
    • CSS/CSS3 图形生成
    • 字符图形生成技术
  • 无浮动:浮动布局容错性差、高度塌陷、兼容性问题

在 CSS 世界中,图片和文字的权重远大于布局

实现自定义滚动有两种原理:一种借助原生的滚动,scrollLeft/scrollTop 值变化,优点是简单,不足是效果呆板。另一种是根据内部元素的尺寸和容器的关系,通过修改内部元素的位置实现滚动效果,优点是效果可以很绽放,iScroll 就是利用的后者。

height 和 width 一个比较明显的区别就是对百分比的支持,对于 width 属性,就算父元素 width 为 auto,其百分比也是支持的,但是对于 height 属性,如果父元素 height 为 auto,只要子元素在文档流中,其百分比值完全就被忽略了

对于普通文档流中的元素,百分比高度值要想起作用,其父级必须有一个可以生效的高度值。

为何高度 100% 无效呢?

  • 民间有个版本说是会陷入死循环,其实这是错误的解释,因为如果这样,宽度也会存在类似问题。
  • 正确的解释要从浏览器渲染的基本原理入手,先下载文档内容,加载头部样式资源,然后从上而下、从外而内的顺序渲染 DOM 内容。因此当渲染到父元素的时候,子元素并没有渲染,当渲染到这个子元素的时候,父元素宽度已经固定,此时 width: 100% 就是已经固定好的父元素的宽度,宽度不够怎么办,溢出就好了,overflow 属性就是为此而生。
  • 既然如此,为何宽度支持,高度就不支持呢?规范中说明:如果包含块的高度没有显式指定(即高度由内容决定),并且该元素不是绝对定位,则计算值为 auto。然而 auto 和百分比计算,肯定是算不了的。

如何让 height: 100% 生效

  • 设定显示高度
  • 使用绝对定位

绝对定位元素和非绝对定位元素的百分比计算是有区别的,区别在于绝对定位的宽高百分比计算是相对于 padding box 的,也就是说会把 padding 大小值计算在内,但是非绝对定位元素则是相对于 content box 计算的

内联盒模型

这部分知识可以说是入门 CSS 开发人员和熟练 CSS 开发人员之间的分水岭。

  • 内容区域:一种围绕文字看不见的盒子,为了便于理解,可以把文本选中的背景色区域作为内容区域
  • 内联盒子:内联盒子不会让内容成块显示,而是排成一行。指的是元素的外在盒子,用来决定元素是内联还是块级
  • 行框盒子:每一行就是一个行框盒子,每个行框盒子又是由一个一个内联盒子组成
  • 包含盒子:由一行一行的行框盒子组成

幽灵空白节点:在 HTML5 文档声明中,内联元素的所有解析和渲染表现就如同每一个行框盒子前面有一个"空白节点"一样。

例子中,为何给 span 添加 display: inline-block 呢?难道 span 不是内联元素不成,原因自然不是如此。查阅了资料大概理解如下,可以理解为一个特例

如果行框盒子没有文本,同时 margin/padding/border 为 0,并且不是 inline-block 和 inline-table 级别,会被当做一个 0 高的行框盒子,就和不存在一样

由此可见 inline-block 和 inline-table 可以创建独立的行框盒子,这在设置 line-height 的时候很有用,因为同一个行框盒子中,最终的高度由 line-height 最大的那个元素决定

注意不要将幽灵空白节点和换行符导致内联元素之间有间隙的情形搞混

盒尺寸四大家族

什么是替换元素,顾名思义,内容可以被替换,官方一点:浏览器根据元素的标签和属性,决定具体显示内容值

替换元素特性

  • 内容的外观不受页面上的 CSS 的影响。如何更改替换元素本身的外观?需要类似 appearance 属性
  • 有自己的尺寸
  • 很多 CSS 属性有自己的一套表现规则,比如 vertical-align 默认值 baseline,基线之意,被定位为字符 X 的下边缘,但在替换元素中不适用,因为替换元素内容不可能含有字符 X,因此被硬生生定义成了元素的下边缘

温和的 padding 属性

错误认识:内联元素的 padding 只会影响水平方向,不会影响垂直方向。这种认知是不准确的,只是因为 inline 元素没有可视宽高的说法(clientHeight 和 clientWidth 永远是 0),垂直方向的行为表现完全受 line-height 和 vertical-align 的影响,视觉上并没有改变和上一行下一行内容的间距,感觉垂直 padding 没起作用

CSS 中还有很多其他场景或属性会出现这种不影响其他元素布局而是出现层叠效果的现象,比如 relative 定位,盒模型 box-shadow 以及 outline 等。层叠现象虽然类似,但实际是有区别的,分为两类,一类是纯视觉层叠,不影响外部尺寸;另一类则会影响外部尺寸。box-shadow 以及 outline 属于前者,而 inline 元素的 padding 层叠属于后者。

对于非替换元素的内联元素,不仅 padding 不会加入行盒高度的计算,margin 和 border 也都是如此,都是不计算高度,但实际上在内联盒周围发生了渲染。

padding 百分比值:无论是水平方向还是垂直方向均是相对于宽度计算的。为什么这么设计呢?因为 CSS 默认的是水平流,计算值一直都会有效,而且我们还可以根据这一特性实现一些有意思的布局效果,可以轻松实现自适应的等比例矩形效果。

标签元素内置 padding:很多表单元素都内置 padding,比如 button,input 等。按钮 padding 与高度计算在不同浏览器千差万别,这是我们平常制作网页的时候很少使用原生 button 按钮作为点击按钮,而是使用 a 标签模拟。但是表单中,有时候按钮是自带交互行为的,a 标签无法模拟,推荐使用 label 元素进行模拟,既语义良好行为保留,同时 UI 效果棒兼容效果好。

激进的 margin 属性

尺寸概念

  • 元素尺寸:包括 padding 和 border,也就是元素的 border box 尺寸,在原生 DOM API 中写作 offsetWidth 和 offsetHeight。
  • 元素内部尺寸:包括 padding 但不包括 border,也就是元素的 padding box 尺寸,在原生 DOM API 中写作 clientWidth 和 clientHeight。
  • 元素外部尺寸:不仅包括 padding 和 border,还包括 margin,没有相对应的原生 DOM API。

margin 改变元素内部尺寸:与 padding 互补态势,对于 padding,元素设定了 width 或者保持包裹性的时候,会改变元素可视尺寸,而 margin 则相反,元素设定了 width 值或者保持包裹性的时候,margin 对尺寸没有影响,只有元素是"充分利用可用空间"状态的时候,margin 才可以改变元素的可视尺寸。

书中一个例子,我觉得很不错,利用负 margin 扩展容器的大小。常见的一行三个布局,左右对其边缘,每个元素间隔 20 px;我们可能会如此实现

li {
    float: left;
    width: 200px;
    margin-right: 20px;
}

这样就导致了我们右边空了 20 px;你一定可以很轻松的想到使用 nth-of-type 选择器来抹平差异,但有没有更加简单的方式,同时兼容性更好呢?答案就是使用负数 margin。

ul {
    margin-right: -20px;
}

负 margin 在很多时候都是大杀器,赶紧加入你的工具箱吧

margin 与外部尺寸:margin 与外部尺寸的关系应该是我们在工作用的最多的了。需要注意的就是,margin 对尺寸的影响是针对具有块状特性的元素而言的,对于纯内联元素(inline)则不使用。和 padding 不同,内联元素垂直方向的 margin 是没有任何影响的,既不会影响外部尺寸,也不会影响内部尺寸。对于水平方向而言,由于内联元素宽度表现为"包裹性",也不会影响内部尺寸。

margin 百分比值:和 padding 一样,无论是水平方向还是垂直方向都是相对于宽度计算的。由于 margin 垂直方向无法改变元素自身内部尺寸,需要父元素作为载体,此外 margin 合并的存在,垂直方向往往需要双倍尺寸才能和 padding 表现一致,应用价值比 padding 低了一截。

margin 合并

  • 块级元素
  • 只发生在垂直方向(不考虑 writing-mode 的情况下才一致,因为默认文档流是水平流,因此发生 margin 合并的就是水平方向)

margin 合并三种场景

  • 相邻兄弟元素 margin 合并
  • 父级和第一个/最后一个子元素
  • 空块级元素的 margin 合并

margin 合并计算规则

  • 正正取大值
  • 正负值相加
  • 负负最负值

首先 margin 合并并不是 bug

  • 兄弟元素:图文信息的排版更加舒服
  • 父子合并:在网页中任何地方嵌套或者直接放入任何裸 div,都不会对影响原来的块状布局
  • 自身合并:避免不小心遗落或者生成的空标签影响排版和布局

div 是网页布局中非常常用的一个元素,其语义是没有语义,也就是不代表任何特定类型的内容,是一个通用型的具有流体特性的容器,可以用来分组或分隔。

HTML 标签默认内置的 CSS 属性值完全就是为了更好地进行图文信息展示而设计的。

我们平时进行网站开发的时候都会重置各种默认的 margin 尺寸,这是件需要好好审视的事情。对于绝大多数网站,确实需要这样的处理,因为这些网站鲜有传统的图文信息展示区域。但是如果你的站点是博客、新闻门户或公众号文章,应该做的是统一标签的 margin 大小,而不是一股脑的重置或 0。

深入理解 margin: auto

  • 为填充闲置空间而设计的
  • 如果让某个块状元素右对齐,脑子不要就一个 float: right,很多时候 margin-left: auto 才是最佳实践,浮动毕竟是个小魔鬼
  • margin 属性的 auto 计算就是为块级元素左中右对齐而设计的,和内联元素使用 text-align 控制左中右对齐遥相呼应

margin 无效情形

  • display 计算值 inline 的非替换元素的垂直 margin 无效。对于内联替换元素,垂直 margin 有效,并且没有 margin 合并问题
  • display 计算值 table-cell 或 table-row 元素的 margin 都是无效的
  • 注意 margin 合并时,更改 margin 值可能没有效果的情形
  • 绝对定位元素非定位方位的 margin 值无效
  • 定高容器的子元素的 margin-bottom 或者宽度定死的子元素的 margin-right 无效:原因在于若想是用 margin 属性改变自身的位置,必须是和当前元素定位方向一样的 margin 属性才可以,否则 margin 只能影响后面的元素或者父元素
  • 鞭长莫及导致 margin 无效(float 和 overflow)
  • 内联特性导致的 margin 无效

功勋卓越的 border

不支持百分比值:从语义和使用场景进行理解

border-width 支持若干关键字

  • thin:薄薄的,等同于 1px
  • medium:默认值,等同于 3px
  • thick:厚厚的,等同于 4px

为什么 border-width 默认宽度大小是 medium,也就是 3px 呢,因为 border-style: double 至少 3px 才有效果。

border-style:默认值是 none,这也就是单纯设置 border-width 或 border-color 没有边框显示的原因

border-color:默认颜色就是 color 色值

border 与透明边框技巧:color: transparent 在 IE9 以上版本的浏览器才支持,但是 border-color: transparent 在 IE7 浏览器就开始支持了

  • 右下方 background 定位技巧:background 定位有一个比较大的局限性,就是只能相对于左上角数值定位,不能相对于右下角。使用头像边框就可以完美解决这个限制了,使用background-position: 100% 百分比功能,因为 background 背景图片是相对于 padding box 定位的,也就是说默认不会把 border 计算在内
  • 优雅地增加点击区域大小
  • 三角等图形绘制:三角形、梯形

应用:border 实现等高布局

内联元素与流

字母 x 的特性

  • 基线:line-height 行高的定义就是两基线的间距,vertical-align 的默认值就是基线,字母 x 的下边缘就是我们的基线
  • x-height:小写字母 x 的高度,术语描述就是基线和等分线之间的距离,在 CSS 世界中,vertical-align: middle 指的是基线往上 1/2 x-height 高度,可以近似理解为字母 x 交叉点那个位置
  • 单位 ex:IE6 就老早支持,指的就是 x-height,可以用来实现不受字体和字号影响的内联元素的垂直居中对齐效果

line-height

内联元素基石 line-height

  • 内联元素的高度之本:对于非替换元素的纯内联元素,其可视高度完全由 line-height 决定,指定了用来计算行框盒子高度的基础高度
  • 理解行距和半行距:内联元素的高度由固定高度和不固定高度组成,这个不固定的部分就是这里的行距,line-height 之所以起作用,就是通过改变行距来实现的

行距的作用是可以瞬间明确我们的阅读方向,让我们阅读文字更轻松

传统印刷的行距是上下两行文字之间预留的间隙,是一个独立的区域,也就意味着第一行文字的上方是没有行距的,但是在 CSS 中,行距分散在当前文字的上方和下方,也就是即使第一行文字,其上方也是有行距的,只不过行距的高度仅仅是完整行距高度的一半,因此,也被称为半行距。

行距的计算:行距 = 行高 - em-box,em-box 高度正好就是 1em,1em 等用于当前一个 font-size 大小,这就是"行距 = line-height - font-size"的由来。有了行距,一分为二就有了半行距,分别加在 em-box 上面和下面就构成了文本的完整高度了。

em-box 是一个比较虚的东西,但为了理解,我们需要相办法感知其存在,这时候就要说到"内容区域"了。在大多数情况下,内容区域和 em-box 不一样,内容区域受 font-family 和 font-size 双重影响,而 em-box 仅受 font-size 影响,通常内容区域高度要更高一些,除了这种情况,当我们的字体是宋体(CSS 代码:simsun)的时候,内容区域和 em-box 是等同的

理解了这些,可以帮助我们解决具体开发中和设计师之间的问题,设计师间隙标注是不会考虑行距的,所有与文字相关的间距都是从文字的上边缘和下边缘开始标注,除非我们全局设置行高为 line-height: 1,否则这些标注的间距和我们使用的 margin 间距都是不一致的。理解了半行距,结合网页中设置的 line-height 大小,就能根据标注获取准确的间距值。这里有个需要注意的问题,border 以及 line-height 等传统 CSS 属性并没有小数像素的概念,如果我们计算的半行距是小数,则需要取整处理

  • 如果标注的是文字上边距,则向下取整
  • 如果是文字下边距,则向上取整,因为绝大多数字体在内容区域都是偏下的

line-height 不能影响替换元素高度,如果内联替换元素和内联非替换元素在一起时,line-height 只能决定这个行盒的最小高度。

对于块级元素,line-height 对其本身是没有任何作用的,平时改变 line-height 块级元素的高度跟着变化实际上是通过改变块级元素里面内联级别元素占据的高度实现的。

为什么 line-height 可以让内联元素垂直居中?肯定听说过这种说法,要想让单行文字垂直居中,只要设置 line-height 大小和 height 高度一样就可以了。

  • 误区一:只需要设置 line-height 这一个属性即可
  • 误区二:行高控制文字垂直居中,不仅适用于单行,多行也是可以的
  • 误区三:仅仅是近似垂直居中

为何可以垂直居中:原因在于 CSS 中行距的上下等分机制,如果行距的添加规则是在文字的上方和下方,则行高是无法让文字垂直居中的。

近似是因为文字字形的垂直中线位置普遍比真正的行框盒子的垂直中线位置低。

多行文本或者替换元素的垂直居中实现原理和单行文本需要 line-height 的好朋友 vertical-align 帮助

.box {
    line-height: 120px;
    background-color: yellow;
}
.content {
    display: inline-block;
    line-height: 20px;
    vertical-align: middle;
}

代码解析

  1. 多行文字使用一个标签包裹,设置为 display-block 块,用来重置外部的 line-height 为正常值,又能保持内联元素特性,从而可以设置 vertical-align 属性,以及产生关键的行框盒子,我们需要的是每个行框盒子都会附带的一个产物 -- 幽灵空白节点,有了这个节点,父元素的 line-height 就有了作用的对象
  2. 因为内联元素都是默认基线对齐,所以对 content 元素设置 vertical-align: middle 来调整多行文本的垂直位置。高能预警:这里也是近似垂直居中,这次不是字体下层导致,而和 vertical-align 有关

line-height 各类属性值

  • 默认值 normal:normal 是一个和 font-family 有着密切关联的变量值
  • 数值:最终计算值是和当前 font-size 相乘后的值
  • 百分比值:最终计算值是和当前 font-size 相乘后的值
  • 长度值:如设置具体的 px 值,这里提到 em 单位,也是个相对于 font-size 的相对单位,因此最终计算值也是和当前 font-size 相乘后的值

乍一看,似乎 1.5 150% 1.5em 最终计算值都是一样的,实际上数值和另外两个有一点儿不同,就是表现在继承上。如果使用数值,那么所有的子元素继承的都是这个值,但如果使用百分比或者长度值作为属性值,那么所有的子元素继承的是最终的计算值。

实际上,百分比值和长度值想要实现类似数值的继承效果,也是可以实现的,那就是使用通配符

* {
    line-height: 150%;
}

你也许会说既然数值可以让元素天然继承相对计算属性,通配符岂不是完全没有必要?其实还是有差别的,HTML 中很多替换元素,如输入框、按钮之类的,很多具有继承特性的 CSS 属性其自己也有一套,如 font-family、font-size 以及这里的 line-height,由于继承是属于最弱的权重,因此 body 中设置的 line-height 是无法影响到这些元素的,但是 * 就不一样了,会直接重置这些替换元素默认的 line-height。考虑到 * 的性能以及明明有继承却不好好利用,可以折中按下面的方式使用

body {
    line-height: 1.5;
}
input, button {
    line-height: inherit;
}

只要字体确定,各个浏览器下的默认 line-height 解析值基本上都是一样的,关键是不同浏览器所使用的默认中英文字体并不一样,并且不同操作系统的默认字体也不一样,也就是说不同浏览器的默认 line-height 都是有差异的,因此在实际开发中,对 line-height 的默认值进行重置是势在必行的

那么对 line-height 到底应该重置为多大呢?

  • 重图文展示的网页或者网站,如博客、论坛、公众号之类的,一定要使用数值作为单位,考虑到阅读舒适度,可以设置在 1.6 - 1.8
  • 重布局结构精致的,使用长度值或者数值都是可以的,目的只是为了兼容,因为无论使用哪种类型值,都存在需要局部重置的场景
    • 如果使用长度值,考虑到大部分场景,可以设置为 20px
    • 如果使用数值,使用方便计算的数值。在 CSS 计算行高时,行高值一定不要向下舍入,而要向上舍入。

大值特性,对于如下例子,box 的最终高度是多少?

// 情形一
.box {
    line-height: 96px;
}
.box span {
    line-height: 20px;
}

// 情形二
.box {
    line-height: 20px;
}
.box span {
    line-height: 96px;
}

正确答案均是96px

  • 情形一:span 是内联元素,因此自身是一个内联盒子,有内联盒子,就一定有行框盒子,每个行框盒子里面有一个高度为 0 的具有该元素的字体和行高属性的看不见的幽灵空白节点。此时父元素的 96px 作用在了幽灵空白节点上,因此高度为 96
  • 情形二:span 的高度变成了 96,而行框盒子的高度是由高度最高的那个内联盒子决定的

vertical-align

凡是 line-height 起作用的地方,vertical-align 也一定起作用,所以他们是一对好基友。

家族认识

  • 线类:baseline、top、middle、bottom
  • 文本类:text-top、text-bottom
  • 上标下标类:sub、super
  • 数值百分比类:20px、2em、20%:根据计算值的不同,相对于基线往上还是往下偏移,负值往下偏移,正值往上偏移
    • 兼容性非常好
    • 可以精确控制内联元素的垂直对齐位置
    • 百分比值相对于 line-height 计算,很少使用

vertical-align 作用前提:只能应用于内联元素以及 display 值为 table-cell 的元素

浮动和绝对定位会让元素块状化,因此 vertical-align 是没有效果的。

看几种开发中觉得奇怪的问题,一个固定高度的容器下有一个图片,图片设置 vertical-align: middle 无效。其实并不是无效,是行框盒子前面的幽灵空白节点高度太小,因此需要让幽灵空白节点高度足够,只要给容器设置 line-height 为高度值即可。

对于 table-cell 元素而言,vertical-align 起作用的是 table-cell 元素自身

.cell {
    height: 128px;
    display: table-cell;
    vertical-align: middle;
}
.cell img {
    height: 96px;
}

容器高度不等于行高?我们有时候会发现容器的实际高度可能比我们设置的行高大一点点,这主要是由于幽灵空白节点,vertical-align,font-size 的影响,假设有个空白节点和 span 内联盒

  • line-height 同时作用在 span 和空白节点上,此时高度其实是一样的
  • 通常 span 字体大小和外部容器字体大小(也就是空白节点字体大小)不一致,font-size 越大的字符的基线位置越往下(因为半行距变小了),默认全部基线对齐
  • 当字号大小不一样的两个文字在一起的时候,彼此就会发生上下偏移,如果偏移距离足够大,就会超过行高的限制,导致出现意料之外的高度

知道问题发生的原因,我们解决问题就简单了

  • 使得空白节点和元素字号一样大
  • 改变对齐方式

再来看看更为常见的图片底部留有间隙的问题,三大元凶就是幽灵空白节点、line-height 和 vertical-align 属性

  • 对于图片等替换元素,基线就是 UI 效果的下边缘,空白节点可以想像成一个 x 字母,x 下边缘和图片下边缘对齐
  • 由于 line-height 的存在,x 的下方肯定是有半行距的存在,这个半行距就是间隙的元凶了,而 line-height 对替换元素是没有作用的

因此解决办法如下

  • 图片块状化:直接砍掉这三者
  • line-height 足够小,只要半行距小到 x 的下边缘位置或者再往上,比如设置成 0
  • font-size 足够小,和 line-height 原理类似,因此要求 line-height 属性值和当前 font-size 有关,比如设置成 0
  • 图片设置成其他 vertical-align 属性值,比如 top、middle、bottom 中的任意一个

基线 baseline,对于文本之类的内联元素就是字符 x 的下边缘,对于替换元素则是替换元素的下边缘,如果是 inline-block 元素,则规则要复杂一些,这个很重要,可以用来解决一些奇怪的现象

一个 inline-block 元素,如果里面没有内联元素,或者 overflow 不是 visible,则该元素基线就是其 margin 底部,否则其基线就是元素里面最后一行内联元素的基线。

应用 inline-block + baseline 处理背景小图标和问题对齐效果。日常开发中设置背景图片时,通常会用到 i 标签,由于要设置背景图片大小,通常要设置为 inline-block 块,此时后面的文本就会和图片不对齐,之前不理解本质原理,写了一些乱七八糟的代码,理解了之后我们就好解决了

  • 避免错位产生高度溢出,因此我们要保证 i 标签的元素基线是其 margin 底部,具体操作如下
    • 图标标签里面永远有字符,可以借助伪元素生成空字符轻松搞定
    • 不使用 overflow: hidden 保证基线为里面字符的基线,但要让里面潜在的字符不可见,使用 letter-space 和 text-indent
  • 设置图标高度和当前行高都是 20 px(经验值)

最佳图标实践 CSS

.icon {
    display: inline-block;
    width: 20px; height: 20px;
    background: url(sprite.png) no-repeat;
    white-space: no-wrap;
    letter-spacing: -1em;
    text-indent: -999em;
}
.icon::before {
    content: '\3000';
}
.icon-xxx {
    background-position: 0 -20px;
}

vertical-align: top/bottom

  • 内联元素:元素顶部(底部)和当前行框盒子的顶部(底部)对齐
  • table-cell 元素:元素顶(底)padding 边缘和表格行的顶部对齐

通俗来说:如果是内联元素,则和这一行位置最高的内联元素的顶部(底部)对齐;如果是 table-cell 元素,脑补成 td,则和 tr 上边缘(下边缘)对齐

vertical-align: middle 与近似垂直居中

  • 内联元素:元素的垂直中心点和行框盒子基线往上 1/2 x-height 处对齐
  • table-cell 元素:单元格填充盒子相对于外面的表格行居中对齐

vertical-align 可以让内联元素的真正意义上的垂直中心点位置和字符 x 的交叉点对齐,之所以说近似,是因为基本上所有字体中,字符 x 的位置都是偏下一点儿的,font-size 越大偏移越明显,这才导致默认状态下实现的都是近似垂直居中

如果要实现真正意义上的垂直居中,需要让字符 x 的中心位置就是容器的垂直中心位置,通常做法就是设置 font-size: 0,此时整个字符 x 就缩小成一个看不见的点。根据 line-height 的半行距上下等分规则,就是实现了真正意义上垂直居中。

通常开发时,font-size 可能就 12px 或者 14px,虽然是近似垂直居中,偏差大概也就 1px - 2px 的样子,用户其实很难察觉到差异,因此是否要真正意义上垂直居中,还是要根据项目实际情况权衡做出决策

文本类属性值

  • text-top:盒子顶部和父级内容区域的顶部对齐
  • text-bottom:盒子底部和父级内容区域的底部对齐

关键词:父级内容区域:当前 font-size 和 font-family 下应有的内容区域大小

实际开发中,没有任何作用,原因大概如下

  • 使用场景缺乏,对齐文本的场景少
  • 文本类垂直对其理解成本高
  • 内容区域不直观且易变:易变是因为和 font-family 有关

上标下标类属性:super 和 sub,对应 sub 和 sup 标签,实际应用就是数学和化学公式,本意之外的使用价值几乎就是零。需要注意的就是这两个属性值并不会改变元素的文字大小,不要被对应的标签误导,因为这两个标签默认 font-size 就是 smaller。

因此为减轻记忆负担,重点理解 top/bottom,baseline/middle 即可

  • top/bottom:对齐看边缘,看行框盒子
  • baseline/middle:对齐和字符 x 打交道

最后来自书中一个很实用的例子:基于 vertical-align 属性的水平垂直居中弹窗

.container {
    position: fixed;
    top: 0; right: 0; bottom: 0; left: 0;
    background-color: rgba(0, 0, 0, .5);
    text-align: center;
    font-size: 0;
    white-space: nowrap;
    overflow: auto;
}
.container:after {
    content: '';
    display: inline-block;
    height: 100%;
    vertical-align: middle;
}
.dialog {
    display: inline-block;
    vertical-align: middle;
    text-align: left;
    font-size: 14px;
    white-space: normal;
}

实现原理

  • 借助伪元素创建一个和外部容器一样高的宽度为 0 的 inline-block 元素,类似"幽灵空白节点"
  • 设置 font-size: 0,因此 x 中心点位置就是 container 的上边缘,此时高度为 100% 的伪元素和这个中心点对其,如果中心点位置不动,这个伪元素上面一半就应该在 container 外边,但是 CSS 默认是左上方排列的,因此伪元素和 x 中心点一起往下移动了半个容器高度,此时 x 中心点就在容器的垂直中心线上
  • 弹窗元素 .dialog 也设置了 vertical-align: middle。根据定义弹窗的垂直中心点和 x 中心点位置对齐,x 此时中心点就在容器的垂直中心位置,从而实现了垂直居中

按照初衷,块级元素负责布局,内联元素负责内容,这里的实现却是将块级元素内联化,利用一些内联属性实现垂直居中效果,因为 vertical-align 等内联属性确实比块级属性强悍,也因为 CSS 世界在布局上的弱势,后来多栏布局、弹性盒子布局以及栅格布局都出来补强了

意料之外的高度总结

主要是由 line-height、vertical-align 和内联盒子导致的现象

  • line-height 决定了行距,当设置为相对单位时,则等于和 font-size 相乘后的值
  • vertical-align 属性值
    • top/bottom 行框盒子的顶部/底部
    • baseline 为字母 x 的下边缘
    • middle 近似理解为字母 x 交叉点
  • 幽灵节点
    • 内联布局产生,文档中称为 struts
    • 受 vertical-align 和 font-size 影响
    • 容器的实际高度比设置的行高大一点点,当字号大小不一样的两个文字在一起的时候,会发生上下便宜,如果偏移距离足够大,超过行高的限制,导致出现意料之外的高度
    • 解决方式:使空白节点和元素字号一样大,或改变对齐方式

流的破坏与保护

float

浮动的本质就是为了实现文字环绕效果。

浮动是魔鬼,少浮动,更多的去挖掘 CSS 世界本身的流动性和自适应性,以构建能够适用于各种环境的高质量的网页布局。

float 特性

  • 包裹性:包裹 + 自适应性
  • 块状化并格式化上下文:float 属性值不为 none,则其 display 属性就是 block 或 table
  • 破坏文档流
  • 没有任何 margin 合并

从 CSS 设计的角度而言,父元素高度塌陷不是 bug,而是有意为之,就是为了实现文字环绕效果。只不过后来发展超出设计者意料,图文展示 Web 展示的一部分,文字环绕已然不流行,于是 float 很少发挥其原本作用,反而被大肆使用满屏布局,显然布局是不需要父元素塌陷的。于是反而成为了一个坑。

高度塌陷只是让跟随的内容可以和浮动元素在一个水平线上,但这只是实现环绕效果的条件之一,实现真正的环绕效果,还需要一个特性:行框盒子和浮动元素的不可叠加性。也就是说行框盒子如果和浮动元素的垂直高度有重叠,则行框盒子在正常定位状态下只会跟随浮动元素,而不会发生重叠

文字环绕效果是由两个特性共同作用的结果

  • 父级高度塌陷
  • 行框盒子区域限制

日常开发中,很多开发人员只考虑到高度塌陷问题,因此经常会给元素设置具体的高度值来解决高度塌陷的问题,但是只能解决父级高度塌陷带来的影响,但是对行框区域限制却没有任何效果,结果导致的问题是浮动元素垂直区域一旦超出了高度范围,或者下面元素 margin-top 负值上偏移,就很容易使后面的元素发生环绕效果。

因此当使用浮动元素的时候,比较稳妥的做法还是采用一些手段干净地清除浮动带来的影响,以避免很多意料之外的样式问题的发生。

float 与流体布局:使用 float 配合 margin 轻松实现多栏自适应布局。

clear 属性

  • 官方定义:元素盒子的边不能和前面的浮动元素相连
  • 设置了 clear 属性的元素自身如何,而不是让 float 元素如何,可以理解成"抗浮动"

重要:clear 属性只有块级元素才有效,而 ::after 等伪元素默认都是内联水平,这就是借助为元素清除浮动影响时需要设置 display(block、table、list-item) 属性值的原因

由于 clear: both 的作用本质是让自己不和 float 元素在一行显示,并不知真正意义上的清除浮动,因此 float 一些不好的特性依旧存在

  • 如果 clear: both 元素前面的元素就是 float 元素,则 margin-top 负值无论怎么设置也不会有效果
  • clear: both 后面的元素依旧可能发生文字环绕现象

因此 clear: both 只能在一定程度上消除浮动的影响,要想完美的去除浮动元素的影响,则需要 BFC 声明

CSS 世界的结界 - BFC

如果一个元素具有 BFC,内部子元素在怎么翻江倒海,都不会影响外部元素,所以

  • BFC 不可能发生 margin 重叠,因为 margin 重叠会影响外面元素
  • BFC 可以用来清除浮动的影响,因为如果不清除,子元素浮动则父元素高度塌陷,必定会影响后面元素布局和定位

啥时候触发 BFC

  • float 值不为 none
  • overflow 不为 visible
  • display: table-cell、table-caption、inline-block
  • position 的值不为 relative 和 static

BFC 的结界特性最重要的用途其实不是去 margin 重叠或者清除 float 影响,而是实现更健壮、更智能的自适应布局。比如之前谈到过的 float 两栏布局

.left {
    width: 200px;
    float: left;
}
.right {
    margin-left: 200px;
}

将 margin-left 也可以换成 overflow: hidden 来创建一个 BFC,此时表现更加智能,有如下优点

  • 自适应内容由于封闭而更健壮,容错性更强,比如内部设置 clear: both 不会和 float 元素互相干烧
  • 自适应内容自动填满浮动以外区域,无须关心浮动元素宽度,可以整站大规模通用

话又说回来了,既然 BFC 那么牛逼,那么为何没有口口相传呢,主要是由于绝大多数的触发 BFC 属性自身有一些古怪的特性,实际操作中能兼顾流体特性和 BFC 特性来实现无敌布局的属性并不多

  • float:有破坏性和包裹性,失去了元素本身的流体自适应性
  • absolute:脱离文档流
  • overflow: hidden:这个超棒,流体特性保留,BFC 特性拥有,IE7 就兼容,唯一的缺点就是盒子外的元素可能会被隐藏掉
  • inline-block:元素尺寸包裹收缩,不是 block 水平的流动特性
  • table-cell:IE8及以上支持,和 inline-block 一样,跟随内部元素的宽度显示,但是单元格有个特别神奇的特性,就是宽度值再大,实际宽度也不会超过表格容器的宽度,因此我们可以把 table-cell 这个 BFC 元素宽度设置的很大,此时就跟 block 水平元素自动适应容器空间一模一样了,比如设置成 9999px
  • table-row 对 width 无感,table-caption 一无是处

最终提炼出两套 IE7 及以上版本浏览器适配的自适应解决方案

/* 方案一 */
.lbf-content {
    overflow: hidden;
}
/* 方案二 */
.lbf-content {
    display: table-cell; width: 9999px;
    /* 如果不需要兼容 IE7,下面可以忽略 */
    *display: inline-block; *width: auto;
}

缺点

  • 方案一:子元素定位到父元素外面可能会被隐藏
  • 方案二:无法直接放连续英文字符换行

最佳结界 overflow

这里了解下 overflow 的细节。

当子元素内容超出容器高度限制的时候,剪裁的边界是 border box 的内边缘,而非 padding box 的内边缘

HTML 中有两个标签是默认可以产生滚动条的,一个是根 html,另一个是 textarea,之所以可以出现滚动条,是因为这两个标签默认的 overflow 属性不是 visible,IE8 开始都使用 auto 作为默认的属性值。

  • 在 PC 端,无论什么浏览器,默认滚动条均来自 html,PC 端窗体滚动高度可以用 document.documentElement.scrollTop 获取,但在移动端,可能要使用 document.body.scrollTop 获取
  • 在 PC 端滚动条会占用容器的可用宽度或高度(17px),移动端不会有这样的问题,因为移动端尺寸本身就有限,滚动条一般都是悬浮模式,不会占据可用宽度

依赖 overflow 的样式表现,单行文字溢出点点点效果

.ell {
    text-overflow: ellipsis;
    white-space: nowrap;
    overflow: hidden;
}

对 -webkit- 私有前缀支持良好的浏览器还可以实现多行文字打点效果,但无须依赖 overflow: hidden

.ell-row-2 {
    display: -webkit-box;
    -webkit-box-orient: vertical;
    -webkit-line-clamp: 2;
}
overflow 与锚点定位

实现锚点定位的方法有两种

  • a 标签以及 name 属性
  • 标签的 id 属性

锚点定位行为的触发条件

  • URL 地址中的锚链与锚点元素对应并有交互行为(点击、重新打开):让元素定位到浏览器窗体的上边源
  • 可 focus 的锚点元素处于 focus 状态:让元素在浏览器窗体范围内显示即可,不一定在上边缘

锚点定位行为的发生,本质上是通过改变容器滚动高度或者宽度来实现的。需要注意的是,这里说的是容器的滚动高度,而不是浏览器的滚动高度,这一点小小的区分很重要,知识我们平时接触锚点定位都是浏览器窗体滚动条级别的,因此容易产生错误的认知。

锚点定位也可以发生在普通的容器元素上,而且定位行为的发生是由内而外的。指的是普通元素和窗体同时可滚动的时候,会由内而外触发所有可滚动窗体的锚点定位行为。

设置了 overflow: hidden 的元素也是可以滚动的。如果发生锚点定位,滚动依旧是可以发生的。锚点定位本质上是改变了 scrollTop 和 scrollLeft 值。

时刻牢记 overflow: hidden 元素依然可以滚动这一点,可以让我们以更简单、更原生的方式实现一些交互效果。比如用锚点定位实现选项卡切换效果、实现自定义滚动条效果。

传统实现都是父元素设置 overflow: hidden,然后子元素使用一个大的 div 包起开,设置绝对定位,通过改变 top 或者使用 transform 进行偏移。推荐的还是基于父元素自身的 scrollTop 值改变来实现自定义滚动条效果

  • 实现简单吗,无需边界处理,因为即使越界,浏览器也会按照最小最大值处理
  • 与原生 scroll 事件天然集成,无缝对接
  • 无须改变子元素的结构
  • 不足:无法实现类似 Bounce 回弹动效

float 兄弟 absolute

absolute 同样具有块状化、包裹性、破坏性特性,但 absolute 能力更霸道,当和 float 同时存在的时候,float 属性是没有任何效果的

和 float 或其它包裹性声明带来的自适应性相比,absolute 的自适应性最大宽度往往不是由父元素决定的,因为其具有与众不同的包含块。

普通元素的百分比宽度是相对于父元素的 content box 宽度计算的,而绝对定位元素的宽度是相对于第一个 position 不为 static 的祖先元素计算的。

包含块规则

  • 根元素(很多场景可以看成是 html 标签)被称为初始包含块,尺寸等同于浏览器可视窗口的大小
  • 其他元素,如果 position 是 relative 或者 static,则包含块由其最近的块容器祖先盒的 content box 边界行成
  • 如果元素 fixed,则包含块是初始包含块
  • 如果元素 absolute,则包含块有最近的 position 不为 static 的祖先元素建立
    • 如果是纯 inline 元素,规则略微复杂
      • 内联元素的前后各生成一个宽度为 0 的内联盒子,则这两个内联盒子的 padding box 外面的包围盒就是内联元素的包含块
      • 如果该内联元素被跨行分隔了,那么包含块是未定义的,浏览器自行发挥
    • 否则由该祖先的 padding box 边界行成

可以看出 absolute 和常规元素相比,有三个明显差异

  • 内联元素也可以作为包含块
  • 包含块所在的元素不是父块级元素,而是最近的 position 不为 static 的祖先元素
  • 边界是 padding box,而不是 content box,原因和 overflow 隐藏也是 padding box 边界类似,都是由使用场景决定的

相对特性的无依赖绝对定位

  • absolute 是非常独立的 CSS 属性值,其样式和行为表现不依赖其他任何 CSS 属性值就可以完成
  • 无依赖绝对定位本质上就是相对定位,仅仅是不占据 CSS 流的尺寸空间而已,配合 margin 可以实现偏移
  • 虽然说元素 absolute 后 display 计算值都是块状的,但是其定位的位置和没有设置 position: absolute 时候的位置相关

absolute 与 text-align

  • 按道理讲,absolute 与 float 一样,让元素块状化,不会受控制内联元素对齐的 text-align 属性影响,但结果是 text-align 可以改变 absolute 元素位置
  • 但特别的是,text-align: center,并没有让元素居中,但位置确实发生了改变
  • 之所以产生了位置变化,本质上是"幽灵空白节点"和"无依赖绝对定位"共同作用的结果
  • 只有原本是内联水平的元素绝对定位后可以受 text-align 属性影响

absolute 与 overflow

  • 如果 overflow 不是定位元素,同时绝对定位元素和 overflow 容器之间也没有定位元素,则 overflow 无法对 absolute 元素进行裁剪
  • 如果 overflow 的属性值不是 hidden 而是 auto 或者 scroll,即使绝对定位元素高度比 overflow 元素高度还要大,也都不会出现滚动条

CSS3 新世界的到来,规则在不经意间发生了一些变化,其中最明显的就是 transform 属性对 overflow 裁剪规则的影响。overflow 元素自身 transform 的时候,Chrome 和 Opera 浏览器下的 overflow 剪裁是无效的,这是唯一和有定位属性时的 overflow 剪裁不一样的地方。

transform 除了改变 overflow 属性原有规则,对层叠上下文以及 position: fixed 的渲染都有影响,因此当遇到 absolute 元素被裁剪或者 fixed 固定定位失效时,可以看看是不是 transform 属性在作祟

absolute 与 clip

  • CSS 世界中有些属性必须和其他属性一起使用才有效,比方说裁剪属性 clip。clip 要想起作用,元素必须是绝对定位或者固定定位
  • 使用场景
    • fixed 固定定位的裁剪
    • 最佳可访问行隐藏

隐藏文本技术选型

  • 下策是 display: none 或者 visibility: hidden 隐藏,因为屏幕阅读设置会忽略这里的问题
    • 按钮无法被 focus
    • IE8 下浏览器提交行为丢失
  • text-indent 缩进是中策,但问题如果缩进过大,大到屏幕之外,屏幕阅读设备也是不会读取的
  • color: transparent 是移动端上策,却是桌面端中策,因为 IE8 并不支持它
  • clip 剪裁隐藏是上策,既满足视觉上的隐藏,屏幕阅读设备等赋值设备也支持很好
  • 透明度 0 覆盖也是不错的实践,移动端建议这么做
  • 屏幕外隐藏,left 和 top 设置为一个负的大值
    • 问题:控件元素被 focus 时,浏览器会自动改变滚动高度,让这个控件元素在屏幕内显示,此时会发生糟糕的体验问题

clip 隐藏仅仅是决定了哪部分是可见的,非可见部分无法响应点击事件等,虽然视觉上隐藏,但是元素尺寸依然是原本的尺寸,也就是说使用 clip 进行剪裁的元素其 clientWidth 和 clientHeight 包括样式计算的宽高都还是原来的大小

流体特性

absolute 遇到 left/top/right/bottom 时,absolute 元素才真正变成绝对定位元素

绝对定位元素也具有流体特性,当然不是默认就有,而是特定条件才具有:对立方向同时发生定位的时候

绝对定位元素的流体自适应特性从 IE7 就开始支持了,但处于历史习惯或是其他原因,同行会有这种写法

.box {
    position: absolute;
    left: 0; right: 0;
    width: 100%; height: 100%;
}

那么这两种方式有什么区别呢,通过设定宽高都是 100% 的 box,实际上已经完全丧失了流动性。因为设置 padding 和 margin 的表现不符合流体表现特性。而设置对立定位属性的绝对定位元素,无论设置 padding 还是 margin,其占据的空间一直不变,变化的就是 content box 的尺寸,这就是典型的流体表现特性。

绝对定位元素的这种流体特性比普通元素要更强大,普通元素流体特性只有一个方向,默认水平方向,但是绝对定位元素可以让垂直方向和水平方向同时保持流动性。

relative 定位

  • 相对自身、无侵入:无侵入的意思是,进行定位偏移时,一般情况下不会影响周围元素的布局
  • 相对定位元素的 left/top/right/bottom 的百分比值是相对于包含块计算的,而不是自身
  • top 和 bottom 百分比值计算跟 height 是一样的,都是相对高度计算的,如果包含块高度是 auto,那么计算值是 0,偏移无效。也就是说父元素没有设定高度或者不是格式化高度,偏移无效
  • 对立方向同时设定值的时候,由于默认文档流是从上而下,从左而右,因此 top/bottom 同时使用的时候,bottom 被干掉,left/right 同时使用的时候,right 被干掉

relative 最小化影响原则:主要原因 relative 会影响层级,层叠顺序会提高,从而留下隐患

  • 尽量不要使用 relative,想定位某些元素,看看能否使用无依赖的绝对定位
  • 如果场景受限,一定要使用 relative,则务必最小化

固定定位 fixed

  • 包含块是 html 根元素
  • 有类似的无依赖固定定位

fixed 与背景锁定:蒙层弹窗基本上都是使用 position: fixed 定位实现的,但你会发现蒙层无法覆盖浏览器右侧的滚动栏,并且鼠标滚动的时候后面的背景内容依然可以滚动,并没有被锁定,如何解决呢

  • 页面滚动条由内部普通元素产生
  • 滚动不方便调整的,借助 JS 实现锁定
    • 移动端阻止 touchmove 默认行为,桌面端根元素 overflow: hidden
    • 桌面端滚动条消失会导致页面可用宽度变化,从而产生晃动问题,可以用 border 模拟

层叠规则

首先我们需要明确的是:z-index 只是 CSS 层叠规则中的一小部分。

z-index 属性只有和定位元素(position 不为 static 的元素)在一起的时候才有作用,可以是正数也可以是负数。随着 CSS3 新世界的到来,z-index 已经并非只对定位元素有效,flex 盒子的子元素也可以设置 z-index 属性

理解层叠上下文和层叠水平

  • 层叠上下文:是一种层叠结界,自成一个小世界,这个小世界中可能有其他的层叠结界,而自身也可能处于其他层叠结界中
  • 层叠水平:决定了同一个层叠上下文中元素在 z 轴上的显示顺序
  • 层叠上下文本身就是一个强力的层叠结界,而普通元素的层叠水平是无法突破这个结界和结界外元素去较量层叠水平的

层叠顺序规则,有下到上依次是:background/border -> 负 z-index - > block 块状水平盒子 -> float 浮动盒子 -> inline 水平盒子 -> z-index: auto 或看成 z-index: 0 -> z-index

为方便理解和记忆,大概规则就是:内容 > 布局 > 装饰

层叠准则

  • 谁大谁上
  • 后来居上

深入理解层叠上下文

  • 层叠上下文的层叠水平比普通元素要高
  • 层叠上下文可以阻断元素的混合模式
  • 层叠上下文可以嵌套
  • 层叠上下文和兄弟元素独立
  • 层叠上下文是自成体系的,当元素发生重叠时,整个元素被认为是在父层叠上下文的层叠顺序中

层叠上下文的创建

  • 天生派:页面根元素天生具有层叠上下文,称为根层叠上下文
  • 正统派:z-index 值为数值的定位元素的传统层叠上下文(可能存在的兼容性问题)
    • IE6/7 z-index: auto 的定位元素也会创建层叠上下文
    • fixed 定位在 WebKit 内核浏览器中,天然层叠上下文,无需 z-index
  • 扩招派
    • flex 布局元素,同时 z-index 值不为 auto
    • 元素 opacity 值不是 1
    • 元素的 transform 值不是 none
    • 元素的 mix-blend-mode 不是 normal
    • 元素的 filter 值不是 none
    • 元素的 isolation 是 isolation
    • 元素的 will-change 属性值为上面 2-6 的任意一个
    • 元素的 -webkit-overflow-scrolling 设为 touch

普通元素一旦具有的层叠上下文,其层叠顺序就会变高,那它的层叠顺序究竟在哪个位置,哪个级别呢

  • 如果层叠上下文元素不依赖 z-index 数值,则其层叠顺序是 z-index: auto,可看做 z-index: 0 级别
  • 如果层叠上下文依赖 z-index 数值,其层叠顺序 z-index 值决定

还有个特别需要注意的是:元素一旦成为为定位元素,其 z-index 就会生效,此时 z-index 就是默认的 auto,也就是 0 级别。按照层叠顺序表,就会覆盖 inline 或 block 或 float 元素

z-index 负值在实际项目作用

  • 可访问行隐藏:需要层叠上下文内某个父元素加个背景色就可以了。与 clip 相比的一个优势是,元素无需绝对定位,对原来的布局以及元素的行为没有任何影响。缺点是不具有普遍适用性,需要其他元素配合进行隐藏
  • IE8 下多背景模拟

不犯二准则:对于非浮层元素,避免设置 z-index 值,z-index 值没有任何道理需要超过 2。这是一条经验准则,可以有效降低日后遇到 z-index 样式问题的风险。

  • 定位元素一旦设置了 z-index 值,就从普通定位元素变成了层叠上下文元素,相互间的层叠顺序就发生了根本的改变,很容易出现设置了巨大的 z-index 值也无法覆盖其他元素的问题
  • 避免 z-index 一山比一山高的样式混乱问题

如果 DOM 顺序确实无法调整,不得不使用 z-index 值,请记住,z-index 不要超过 2。如果你的定位发现必须 z-index: 3 或者以上才能满足,建议你检查自己的代码

  • 试试应用 relative 的最小化原则
  • 试试利用元素原生的层叠顺序进行层级控制

不犯二准则:并不包括页面上哪些飘来飘去的元素定位、弹框、出错提示、下拉效果等不受这一准则限制。

对于这些 JS 驱动的浮层附件,借助层级计数器管理

  • 总会遇到一些意想不到的高层级元素
  • 组件的覆盖规则具有动态性

层级计数器实际上就是一段 JS 脚本,会遍历所有 body 处于显示状态的子元素,并得到最大 z-index 值,和默认的 z-index 做比较,如果超出,则显示的组件的z-index 自动加 1,这样就不会出现有组件被其他组件覆盖的问题,如果不超过,就是用默认的 z-index 值,习惯设成 9。

文本处理

理解 font-size

  • ex、em、rem
    • ex 字符 x 的高度
    • em:等同于当前元素所在的 font-size 计算值,可以将其想象成当前元素中汉字的高度
    • rem:即 root em,根元素 em 大小
    • 要想实现具有缩放性质的弹性布局,使用 rem 是最佳策略,但 rem 是CSS3 单位,IE9 以上浏览器才支持
  • 关键字属性值
    • 相对尺寸:相对当前元素的 font-size 计算,larger 大一点, smaller 小一点
    • 绝对尺寸:仅受浏览器设置的字号影响,xx-large 好大好大,和 h1 元素计算值一样;x-large 好大,和 h2 元素计算值一样;large 大,和 h3 计算值近似;medium 不上不下,是 font-size 初始值,和 h4 计算值一样,small 小,和 h5 计算值近似;x-small 好小,和 h6 计算值近似;xx-small 好小好小,无对应 HTML 元素

现代网页设计的很精致,要想网页布局跟随字体内容缩放实在两难,要么使用 em,但 em 计算与当前 font-size 耦合,不好维护,要么使用 rem,但 IE8 不支持,桌面端使用刚噶,显示压迫我们是能使用 px 进行布局,尤其桌面端网页。如何权衡"易于实现维护","视觉还原","可访问性",作者提供了两点建议

  • 即使是定宽的传统桌面端网页,也需要做到响应式出来看i,尤其是针对 1200 像素宽度设计的网页,但只需要响应到 800 像素即可,可以保证至少有 1.5 倍的缩放空间
  • 如果因各种原因无法做响应式处理,也没有必要全局都使用相对单位,毕竟成本摆在哪里。只需要在图文内容为主的重要局部区域使用可缩放的 font-size 处理即可
    • 容器设置 font-size: medium
    • 容器内文字字号间距全部使用相对单位,百分比或者 em 都可以

理解 font-family

  • 默认值由操作系统和浏览器共同决定
  • 支持两类属性值,一类是字体名,一类是字体族
    • 字体名包含空格,需要用双引号抱起来,不用区分大小写,如果有多个字体设定,从左往右一次寻找本地是否有对应的字体即可
    • 字体族就有很多类了,比如衬线字体、无衬线字体、等宽字体、系统 UI 字体、手写字体、奇幻字体
    • 衬线字体:笔画开始、结束的地方有额外装饰而且笔画的粗细会有所不同的字体,比如中文宋体,英文 Times New Roman、Georgia 等
    • 无衬线字体:没有额外的装饰,而且笔画的粗细差不多,比如中文雅黑,英文 Arial、Verdana、Tahoma、Helivetica、Calibri 等
    • 等宽字体:每个字符在同等 font-size 下占据的宽度是一样的

以前人们排正文喜欢使用衬线字体,但是如今,不知是审美疲劳还是人们更加追求简洁干净的缘故,更喜欢使用无衬线字体。在 CSS 中,字体是有对应的属性值的

body {
    font-family: serif; /* 衬线字体 */
    font-family: sans-serif; /* 无衬线字体 */
}

我们在移动端开发时候,虽然设备的默认中文字体不一样,但都是无衬线,都挺好看的,因此可以直接使用下面的 CSS 代码

body {
    font-family: sans-serif;
}

没有必要特别指定中文字体,说不定会画蛇添足

serif 和 sans-serif 还可以和具体的字体名称写在一起

body {
    font-family: "Microsoft Yahei", sans-serif;
}

需要注意 serif 和 sans-serif,一定要写在最后,因为大多数浏览器下,serif 和 sans-serif 后面的所有字体会被忽略

等宽字体在 Web 中有什么用呢

  • 等宽字体与代码呈现
  • 等宽字体与图形呈现
  • ch 单位与等宽字体布局:ch 相关的字符是 0,1ch 表示一个 0 字符的宽度
    • 手机号码可以设置成 11ch,同时字体等宽,这样用户一眼就能看出自己是否少输入了
    • 屏幕上代码一个一个出现的动效,如果是等宽字体,就可以使用 ch 单位来控制宽度,配合 overflow 和 animation 能在完全不实用 JS 的情况下实现

理解 font-weight

  • 关键字:normal、bold
  • 相对父级元素关键字:lighter,bolder
  • 字重控制:100~900 整百数
    • 400 等同与 normal
    • 700 等同于 bold
  • 显然 400 和 700 是字体粗细与否的重要临界点,意义在于 lighter 和 bolder 这两个具有相对特定的关键字就是基于 100,400,700,900 进行解析和渲染的

关键问题,开发时发现,font-weight 无论是设置 300、400、500 还是 600 文字粗细都没有任何变化,只有 700 才会加粗一下,就好像浏览器不支持这些数值。实际上这些数字关键字浏览器都是支持的,之所以没有看到任何粗细变化,是因为我们的系统里面缺乏对应粗细的字体

理解 font-style

  • normal
  • italic:当前字体的斜体字体,如果当前字体没有对应的斜体字体,则退而求其次,解析为 oblique
  • oblique:单纯地让文字倾斜

font 属性:文本相关样式的缩写

  • font-size 和 font-family 是必须的,不可以省略
  • font 会破坏部分属性的继承性,比如没有设置 line-height,就会将元素的 line-height 属性值重置为了 normal

font 属性除了缩写用法,还支持关键字属性值。设置 font 为操作系统该部件对应的 font,也就是说直接使用系统字体。简单了解下即可。

  • 让网页跟着系统走,对设计师而言,是比较冒险的做法,因为最终呈现的字体是不可控的。但转念一想,何尝不是一种情感化设计呢
  • 让网页字体跟系统走,还有更加长远的好处,用户可以及时享受到新系统新字体带来的愉悦视觉感受

@font face 规则

本质上就是一个定义字体或者字体集的变量,这个变量不仅仅是简单的自定义字体,还包括字体重命名、默认字体样式设置等。核心属性有

  • font-family:字体变量
  • src:引入系统字体或外链字体
  • font-style:设置对应字体样式该使用什么字体
  • font-weight:设置对应字体字重该使用什么字体
  • unicode-range:让特定的字符或者特定范围的字符使用指定的字体

字体格式

  • svg 格式是为了兼容 iOS 4.1 及其之前的版本,考虑到线框,大可舍弃
  • eot 格式是 IE 私有的。IE8~IE8 仅支持 eot 这一种字体格式
  • woff:web open font format,专门为 Web 开发而设计的字体,优先使用的字体,尺寸更小
  • woff2 是比 woff 尺寸更小的字体,Web 开发首选字体,只是目前仅 Chrome 和 Firefox 支持得比较好
  • ttf:系统安装字体比较多,Web 开发也能用,尺寸比较大

@font face 与字体图标技术:字体图标的尺寸大小和颜色控制非常方便,开发维护方面占用流量小很多,收益非常明显

字体图标技术的一些渲染现象

  • 原始字符和最终图形表现相差很大,字体文件加载缓慢的时候,可以明显看到字符变图形的过程,这种加载体验不太友好,字体内联在 CSS 文件中可以有效避免这一问题,但往往字体文件体积都比较大,这样处理得不偿失
  • 原始字符的 x-height 和最终的图形 x-height 往往是不一样的,会影响内联元素的垂直对齐,容易出现页面高度闪动的情况
  • 原始字符的 ch 宽度,也就是水平占据的宽度和最终的图形也是不一样的,因此容易出现内联元素水平方向晃动的问题

用工具生成图标字体时,其中间的媒介都是 SVG 图标,但是并不是所有的 SVG 图标都是可以的,最好满足下面 3 点

  • 纯路径,纯矢量,不要有 base64 内联图形
  • 使用填充而非描边,也尽量避免使用一些高级的路径填充覆盖技巧
  • 宽高尺寸最好都大于 200,因为字体生成的时候,坐标值会四舍五入,SVG 尺寸过小会导致坐标取值偏差较大,使的最终的图标不够精致

文本控制相关

文本控制

  • text-indent 控制文本缩进
    • 很大的 text-indent 负值在某些设备上有潜在的性能风险,体现在滚屏的时候会发生卡顿
    • 百分比值是相对于当前元素的包含块计算的,而不是当前元素
    • 仅对第一行内联盒子内容有效
    • 非替换元素以外的 display: inline 的内联元素设置 text-indent 值无效,inline-block/inline-table 则会生效
  • letter-spacing 字符间距
    • 继承性,默认值 normal 而不是 0
    • 支持负值,且值足够大的时候,会让字符形成重叠,甚至反向排列
    • 和 text-indent 一样,无论值多大或多小,第一行一定会保留至少一个字符,在默认左对齐的情况下,无论值如何设置,第一个字符的位置一定是纹丝不动的
  • word-spacing 单词间距
    • 仅作用于空格字符,而不是字面意义上的单词,增加空格的间隙宽度
  • word-break 和 word-wrap
    • word-break:normal | break-all(允许任意非 CJK 文本间换行) | keep-all(不允许 CJK 文本中的单词换行,只能在半角空格或连字符处换行,兼容性堪忧)
    • word-wrap:normal | break-word(一个单词中实在没有其他靠谱的换行点的时候换行)
  • white-space 处理元素内空白字符
    • 决定图文内容是否在一行显示(回车是否生效)
    • 是否允许大段连续空白(空格是否生效)
    • 合并空格会让多个空格变成一个;合并换行会把多个连续换行合并成一个,并当做一个普通空格处理;如果文本环绕,当一行文字内容超出容器宽度时,会自动从下一行开始显示
    • normal:合并空白字符和换行符
    • pre:空白字符不合并,并且内容只在有换行符的地方换行
    • nowrap:和 normal 一样会合并空白字符,但不允许文本环绕
    • pre-nowrap:空白字符不合并,且内容只在有换行符的地方换行,同时允许文本环绕
    • pre-line:合并空白自负,但只在有换行符的地方换行,允许文本环绕

CJK 指的是 Chinese/Japanese/Korean

text-align 与元素对齐。在 IE 上使用 text-align: justify 都无法让中文两端对齐。好在 IE 提供了一个私有的 CSS 属性 text-justify 可以实现中文两端对齐,于是通过使用下面的 CSS 代码组合就可以实现全部浏览器都兼容的中文两端对齐

.justify {
    text-align: justify;
    text-justify: inter-ideograph;
}

此外 text-align: justify 还可以实现容错性更强的两端对齐布局效果,直接看 CSS 吧

.justify {
    text-align: justify;
    /* font-size: 0; /* /* 解决空隙,但是 IE 认为其样式表现就好像空格根本不存在一样,无法两端对齐 */
    font-size: .1px;
    font-size: -webkit-calc(0px + 0px) /* fix IE */
}
/* 最后一行不会有两端对齐效果,确保超过一行*/
.justify:after {
    content: '';
    display: inline-block;
    width: 100%;
    vertical-align: bottom; /* 解决空隙 */
}

此外,对于超过两列的情况,最后一行可能表现为两端对齐,这不是我们需要的,因此需要辅助列表来占位。占位标签的个数和列表的列数保持一样就可以了。

text-decoration

  • underline:会和文字下边缘粘在一起,推荐使用 border 模拟
  • overline:上划线,没有想到使用场景
  • line-through:中划线,表示删除,比如原价删除等。但更推荐直接使用 del 标签

text-transform,要么全大写(uppercase),要么全小写(lowercase)

  • 身份证输入:最后的可能字符 X 要求必须大写
  • 验证码输入

了解 first-letter 和 first-line 伪元素

  • first-letter 生效前提
    • display 值有要求
    • 不是所有字符都能独立作为 ::first-letter 伪元素存在的
  • first-letter 注意事项
    • 不是所有 CSS 属性都可以生效
    • 权重总是多了一层
  • first-letter 实际应用:电商产品的 ¥ 符号,样式同样不一样,就可以不用单独包裹标签

关于单冒号和双冒号:IE9 及以上版本浏览器支持双冒号写法,但 IE8 浏览器只认识单冒号写法

元素的装饰和美化

少的可怜的颜色关键字

  • CSS1 中支持 16 个基本颜色关键字
  • CSS2 中仅仅新增了一个颜色 - 橙色 orange
  • CSS3 中一下子增加了 100 多个颜色关键字
  • CSS4 中又仅增加了一个颜色关键字 - rebeccapurple

特殊的 transparent 关键字

  • background-color: transparent 包括 IE6 浏览器都支持
  • border-color: transparent 从 IE7 开始支持
  • color: transparent 却从 IE9 才开始支持

currentColor 变量

  • 使用当前 color 计算值
  • IE9+ 才支持它
  • CSS 中很多属性默认就是 currentColor 的表现,我们一般无须画蛇添足地再声明这个关键字,如 border,text-shadow、box-shadow

background

  • background-image
    • IE8 支持 base64 图片,base64 图片渲染性能并不高,只适合尺寸比较小的图片,大尺寸图片慎用
  • background-position
    • 支持 1~4 个值,可以是具体数值,也可以是百分比值,还可以是 left、top、right、center 和 bottom 等关键字
    • 如果缺省偏移关键字,则会认为是 center
    • 支持百分比值,但是比较特殊 positionX = (容器的宽度 - 图片的宽度) percentX;positionY = (容器的高度 - 图片的高度) percentY
  • background-repeat
  • background-attachment 背景相对于当前文档视区定位,也就是页面再怎么滚动背景图片位置依旧纹丝不动
    • scroll | fixed
    • fixed 表示b
  • background-color
  • background-size(IE9+)
  • background-origin(IE9+)
  • background-clip(IE9+)

更多

元素显隐方案选择,用户界面样式和流向的改变

元素显隐

CSS 让元素不可见的方法有很多,比如剪裁、定位到屏幕外、明度变化等

  • display: none
  • visibility: hidden:和 transition 友好
  • clip: rect(0 0 0 0)
  • relative 与负 z-index
  • opacity: 0

用户界面样式

outline 属性

  • 和 border 类似,是一个和用户体验密切相关的属性,与 focus 状态以及键盘访问密切相关,因此万万不可全局设置 outline: 0 none;
  • 现代浏览器的 focus 体验已经做的很好了,对于普通链接或者按钮,点击的时候已经不会出现 outline 效果了,只有键盘 Tab 或者 JS focus() 主动触发才会有发光效果
  • 输入框元素因为要先 focus 才能输入内容,浏览器内置的 focus 效果可能和网页设计格格不入,因此需要重置,可以使用专门的类名
    .input {
      outline: 0; /* none */
    }
    .input:focus {
      border-color: Highlight;
    }
    
  • 如果使用了 label 代言了表单控件,则需要把 focus 态也代言了
  • 真正不占据空间的属性。你可能会说内联元素的垂直 padding 也不占据任何空间,但是祖先元素的 overflow 计算值不是 visible,同时 padding 足够大,滚动条就会出现。

丰富的光标属性 cursor:特别多,就不列出来了,需要的时候再查阅好了。还需要知道的是,我们是可以自定义光标的。

流向的改变

在 CSS 中流向是可以轻易进行颠覆和改变的,并不总是自左向右,自上而下。

改变水平方向 direction 属性早在 IE6 时代就被支持了。属性值有

  • ltr:默认值,left-to-right
  • trl:right-to-left

direction 场景

  • 轻松调转按钮顺序
  • 前面的文字省略

纵横规则 writing-mode



留言