仍然是最近几个月闲来无事,想着自从毕业那几年之外,已经好久没有沉下心来好好看看css了,趁着年底的这几个月,我又重新找了本《深入解析CSS》来回顾了一下CSS的一些知识点,并且在阅读的过程中,收集了一下书中的重点知识以及根据自身疑问找到的一些资料,用以后续可能的回顾。
学习目标
其实css本身来说,日常工作中,你能遇到的最常见的功能就是:
- 工作方面
- 布局
- 响应式
- 动画
- 常见css技巧
- 内功部分
- 模块化css(大型应用css)
现在的css,什么颜色、字体、边框啥的东西,说实话,都不会再有什么要求了,连所谓的什么选择器、样式权重啥的也都不重要了(或者说不被重视)。其实对于目前的css来说,有了AI查询之后,基本上很多疑问其实可以找到答案,那么现在还学习css的原因是什么呢?
- 为了效率,在自己开发编写页面时,可以快速、合理的布局,按照基本上比较好的方式编写页面结构和样式编写。
- 动画的理论,在编写动画或者过渡时,可以没有那么生涩,知道大致该怎么写,为啥要这么写(可以更好的配合AI)
- 在面对一些需求是,也能够有足够的知识储备,例如响应式
- 了解一些技巧,可以快速在常见、特定的需求时,快速找到比较合适的办法,而不是找资料、思考半天才动手,影响效率。
前言
- 精通 CSS,难在需要知道在何时做何事。或者说怎么才能写出最佳的写法,达到自己想要的效果。
- 精通CSS,需要学习CSS的所有功能。了解得越多,对CSS的感受就越自然。
练习得越多,就越能轻松地想到完美的布局和定位方法。读得越多,就越能从容地应对任何设计 - 知道css有什么功能,还要知道使用过后它会产生什么效果和行为(要求比较高)
- 学习css,以便在一般的问题出现时,能够知道大致是为什么,可以尝试如何解决
- MDN网站其内容也非常的丰富和全面,是一个非常好的参考资料:MDN CSS
你可以做任何想做的事情,只要事情按照你的预期发展就行。CSS没有任何硬性规定,但正因为全靠自己发挥,没有衡量标准告诉你做得好不好,所以你需要格外小心。
高级话题
这节会把重点放在设计考量上。一些小小的细节,可能会对网站的观感产生重大的影响。
背景、阴影和混合模式
- 线性渐变和径向渐变
- 盒阴影和文字阴影
- 调整背景图片的大小和位置(常用)
- 使用混合模式,让背景和内容相结合
渐变和阴影用的倒是不多。
CSS背景
- css支持使用多个图片背景,使用多个背景图片时,列表中排在前面的图片会渲染到排序靠后的图片上面。
渐变也是一种背景,它写在background-image样式中。
- 混合模式:background-blend-mode
- 如果我们使用两张背景图片,那么一般是希望第二张图片也可以透视显示。这时就可以使用混合模式(blend mode)
- 如果熟悉图片编辑软件,那你可能见过混合模式。混合模式用来控制叠放的图片怎样融合在一起
- CSS支持15种混合模式,每一种都使用不同的计算原理来控制生成最终的混合结果。对每一个像素来说,就是取一个图层上的像素颜色,与其他图层上对应像素的颜色拼合计算,生成一个新的像素颜色,最终生成一张混合图片。
- 这些混合模式又可以划分为五类:变暗、变亮、对比、复合和比较。
- 混合模式的作用
- 使用某种颜色或者渐变为图片着色;
- background-blend-mode不仅仅合并多个背景图片,还会合并background-color。所有这些叠放的图层,最终都会被混合模式拼合在一起,因此我们可以把背景颜色设置为想要的色相,混合到图片中去。
- 明度混合模式(还有其他几种类似的混合模式)最终的渲染结果,取决于哪个图层在其他图层之上,这是非常重要的。
背景色图层始终在最下层,其他背景图片叠放在背景色图层之上。 - 颜色混合模式与明度混合模式恰好相反,使用前景色的色相和饱和度与背景色的明度来生成最终的结果色
- 为图片添加纹理效果,比如划痕或者老胶片放映时的颗粒感等;
- 缓和、加深或者减小图片的对比度,使图片上的文字更具可读性;
- 在图片上覆盖了一条文字横幅,但是还想让图片完整显示。
- 使用某种颜色或者渐变为图片着色;
- 大部分背景相关的属性可以接受多个值,以逗号分隔。但如果只设置一个值,一般就会应用到所有的背景图片上。
背景语法
- background是一个简写属性(非顺序):background-clip、background-color、background-image、background-origin、background-position、background-repeat、background-size 和 background-attachment。
- background 属性被指定多个背景层时,使用逗号分隔每个背景层。
- background-color 只能在 background 的最后一个属性上定义,因为整个元素只有一种背景颜色。
- 最常用的背景写法:
background: url() <background-position> ?[ / <background-size>] <background-repeat>- 例如:
background: url("bg.png") center / cover no-repeat; - 例如:
background: url("bg.png") left top / 100% 100% no-repeat; - 其中background-size是可选的,如果要加上,则一定要在background-position之后,且需要添加
/字符。 - 很少用的几个,只有特殊需求时才会考虑用这几个
- background-clip 设置元素的背景(背景图片或颜色)是否延伸到边框、内边距盒子、内容盒子下面
- background-attachment CSS 属性决定背景图像的位置是在视口内固定,或者随着包含它的区块滚动。
- background-origin 规定了指定背景图片background-image 属性的原点位置的背景相对区域。
- 也就是left、top这些点,是相对于盒模型的内容、还是边框或者padding内边距的
- 注意:当使用 background-attachment 为 fixed 时,该属性将被忽略不起作用。
- 例如:
对比、颜色和间距
字体颜色的对比效果
- 通常字体颜色都是深灰色,而不是纯黑(#000)。使用灰色而非纯黑是常见做法。在背光式计算机屏幕上,纯白色背景(#fff)上的纯黑色文本会产生强烈的对比效果,很容易在阅读时造成视觉疲劳,特别是大段的文本。黑色背景上的白色文本也会有同样的问题。在这种情况下,要么用深灰色代替黑色,要么用浅灰色代替白色,或者都替换掉。
- 我们不想让文本产生过于强烈的对比效果,同样也不希望对比效果太差。浅灰色背景上的灰色文本同样难以阅读,甚至会使用户的视力受损。在阳光下看智能手机,也是一样。那么我们怎样才能实现适当的对比效果呢?
- 为了帮助我们判断,W3C的Web内容无障碍指南(Web Content Accessibility Guidelines, WCAG)提供了关于对比度最小值的建议(称为AA级),更严格一点,还有加强型对比度(称为AAA级)。
- 在开发者工具中,你可以在一个字体的颜色上,打开颜色的调色板,此时下方有一个展开箭头,此时会显示对比度颜色的等级,并且它可以打开对比度等级所在的颜色范围,这样选择范围内的颜色,就是符合对比度等级的。
行高和字距
- 在盒模型中,元素的内容盒子为内边距所环绕,然后是边框,最后是外边距。
但是对于段落和标题这样的元素,内容盒子并不是只有显示出来的文字区域,元素的行高决定了内容盒子最终的高度,这要超出字符的顶部和底部。- 通常行高会手动去设置为了1.4,所以文本占据的高度要比实际文本的字体要高一些
- 设计师习惯使用行距(leading)来表示每行文字之间的间距。在CSS中,文字间距由行高控制,不能直接转换成行距。
- line-height属性的初始值是关键字normal,大约等于1.2(确切的数值是在字体文件中编码的,取决于字体的em大小),但是在大部分情况下,这个值太小了。对于正文主体来说,介于1.4和1.6之间的值比较理想。
- 行高的设置,通常为一个无单位值,例如1.4这种,不要设置为一个具体的单位,例如px,因为它会继承。那么在不同的字体大小下,具体的px就不怎么合适了。除非你明确其应用范围,并特定需要设置它达到某种效果。
行距和字距转换为css单位
- 在设计领域,文本行之间的距离称为行距(leading,与bedding有点谐音),来源于印刷版每行文字之间添加的一条条的引导线(lead)。字符之间的距离称之为字距(tracking)。如果你和设计师一起工作,他们可能会在设计稿中指明行距和字距,但这些值看起来跟CSS属性line-height和letter-spacing一点儿都不像。
- 行距
- 行高一般使用“点”作单位来描述,比如18pt,代表的是一行文字的高度加上它与下一行文字之间的距离。这实际上与CSS的line-height一样,但是没有使用一个无单位的数字来表达。你必须首先把它转化为跟字体一样使用像素单位,然后再计算出无单位数字。
- 把点值乘以1.333,就可以把pt转化为px(因为每英寸是96px,并且每英寸等于72pt, 96/72=1.333),即18pt×1.333=24px。然后除以字号,得到无单位的行高值,即24px/16px=1.5。
- 字距
- 它通常会给定一个字数,比如100。因为这个数字表示1em的千分之一,所以除以1000就可以转化成em单位,即100/1000=0.1em。
排版
字体
- Web字体是拖慢网页加载时间最大的几个元凶之一,仅排在图片之后。因此应该谨慎一些,只挑选自己需要的字体。
- WOFF是指Web开放字体格式,这是一种专为网络使用而设计的压缩字体格式。所有的现代浏览器都支持WOFF,基本上也都支持WOFF2,除了IE(WOFF2格式有更好的压缩效果,因此文件更小)。
- 浏览器中经常会遇到这种情况,页面的内容和布局就要开始渲染了,字体却还在下载中。开始时,大多数的浏览器供应商为了尽可能快地渲染页面,使用了可用的系统字体。然后,一小段时间过去了,Web字体加载完成,页面会使用Web字体重新渲染一次。
- 比起系统字体,Web字体很可能会在屏幕上占据不一样的空间。第二次渲染时,页面布局变了,文字突然跳动了。
- 这就是所谓的FOUT,即无样式文本闪动(Flash of Unstyled Text)。
- 因为开发者们不喜欢这样,所以大部分浏览器供应商修改了浏览器的行为。他们不再渲染回退字体,改成渲染页面上除了文本以外的其他所有元素。确切地说,他们把文本渲染成不可见的,因此文字依然会占据页面的空间。通过这种方式,页面的容器元素得以实现,用户就可以看到页面正在加载。这就导致了一个新的问题,FOIT,即不可见文本闪动(Flash of Invisible Text)。也就是一开始字体没有加载时是空的,字体加载完后才渲染到页面上。
- 要解决问题,其实就是要避免发生FOUT和FOIT。然而在Web字体领域,这两个问题从未完全解决过。
- 较为合理的解决方案(js版本)
- 使用JavaScript可以监控字体加载事件,这样就可以更好地控制FOUT与FOIT的发生过程。
- 还可以使用js库来帮助处理,有一个叫Font Face Observer的库。这个库可以让你等待Web字体加载,然后做出相应的响应。
- 有个新的
CSS属性font-display,无须JavaScript的帮助就可以更好地控制字体加载。- 这条属性需要在@font-face规则内部使用,用来指定浏览器应该如何处理Web字体加载。
过渡
Web是个新鲜生动的媒介,可以做更多的事情,比如元素可以淡出、菜单可以滑入、颜色可以从一种变为另一种,实现这些效果最简单的方式是过渡(transitions)。如果正确使用,过渡可以增强页面的交互效果,而且因为我们的眼睛更容易被动态的东西吸引,所以当变化产生时可以更好地获得用户关注。
- 过渡是通过一系列
transition-*属性来实现的。如果某个元素设置了过渡,那么当它的属性值发生变化时,并不是直接变成新值,而是使用过渡效果。 - transition-property这个属性可以指定哪些属性使用过渡。关键字all意味着所有的属性变化都使用过渡。
- transition-duration属性代表过渡到最终值之前需要多长时间,设置为0.5s,代表0.5秒的持续时间。
- 元素属性任何时候发生变化都会触发过渡:可以是状态改变的时候,比如:hover;也可以是JavaScript导致变化的时候,比如添加或者移除类影响了元素的样式。
- 把过渡属性设置在了一个始终指向该元素的选择器,虽然其他属性发生着变化,但你肯定不想过渡属性本身发生变化。
- 也可以使用简写属性transition,该简写属性接受四个参数值,分别代表四个过渡属性transition-property、transition-duration、transition-timing-function和transition-delay。
- 持续时间,是一个用秒(例如0.3s)或者毫秒(300ms)表示的时间值。跟长度值不太一样,0不是一个有效的时间值。你必须为时间值添加一个单位(0s或者0ms),否则声明将无效,并被浏览器忽略。
- 如果需要为两个不同的属性分别设置不同的过渡,可以添加多个过渡规则,以逗号分隔
- 若判断出
无过渡起点,则会直接变成最终值,无法进行过渡。 - 过渡和动画中的定时函数(ease-in、ease-out这种)
- 定时函数是基于数学定义的贝塞尔曲线(Bézier curve)。浏览器使用贝塞尔曲线作为随
时间变化的函数,来计算某个属性的值。 - 开发者工具可以设置贝塞尔曲线和看实际效果
- 定时函数是基于数学定义的贝塞尔曲线(Bézier curve)。浏览器使用贝塞尔曲线作为随
- 另外一种定时函数:阶跃函数,即steps()函数。跟前面介绍的从一个值到另一个值的基于贝塞尔曲线的流畅过渡不同,这个函数是一系列非连续性的瞬时“阶跃”(steps)。
- 最好的例子就是时钟指针一跳一跳的效果。
- 大部分的过渡持续时间应该处于200~500ms。
- 对于鼠标悬停、淡入淡出和轻微缩放特效,应该使用较快的过渡速度。一般要控制在300ms以下,有时候甚至可能要低到100ms。对于那些包含较大移动或者复杂定时函数的过渡,比如弹跳特效(参见第15章),要使用较长的300~500ms的持续时间
- 大部分的接受长度值、数值、颜色值或者calc()函数值的属性可以添加动画效果;大部分的使用关键字或者其他非连续性值的属性(比如url()、display)不可以使用动画。
- visibility属性可以从页面上移除某个元素(是不会绘制该元素到页面上,无法交互),有点类似于display属性,分别设置visible和hidden即可。但跟display不同的是,visibility可以支持动画。
为它设置过渡不会使其逐渐消失,但transition-delay可以生效,而在display属性上是不生效的。 - 我们可以利用visibility的这个能力作为小窍门来实现动画。(用visibility来立即显示出元素,并且配合opacity来淡入出元素,并且在元素要消失时,延迟visibility的生效,让opacity淡出后再让元素隐藏)
- visibility属性可以从页面上移除某个元素(是不会绘制该元素到页面上,无法交互),有点类似于display属性,分别设置visible和hidden即可。但跟display不同的是,visibility可以支持动画。
- 淡入淡出特效也可以使用一些JavaScript代码来代替过渡延迟,但我觉得这样需要使用更多的代码,而且容易出错。然而有时候为了实现想要的效果,有必要使用JavaScript(接下来很快就会见到),但如果一个过渡或者动画只用CSS就可以实现,一般会选择CSS。
过渡的坑:未显示设置width宽度并对width进行过渡设置时,其结果不符合预期
- 对一个块级div设置全属性过滤,并且在hover时,设置其width为一个具体的更小的值。
- 但是其结果非预期中的width会过渡变小,而是立即变小。需要显式给div的样式设置一个width:100%;才能正常过渡,这是为什么?
- 块级元素的默认值不是占据父元素的宽度吗?理论上应该是有一个值的,为何还需要显式设置100%才可能使过渡生效?
结论:块级元素的「默认占满父容器宽度」 !== 它的 CSS width属性有一个100%的具体值。这是两个完全不同的概念,也是过渡失效的唯一核心原因。
- 块级元素 (div) 的默认宽度行为:width: auto(CSS 规范的默认值),表现为「自动填满父元素的可用宽度」,视觉上就是 100% 宽度;
- transition 过渡动画的生效条件:只能在「两个有具体、可计算的 CSS 属性值」之间生效;
- 问题本质:transition 无法识别 width: auto(无具体值) → width: 具体小值 的变化,
判定为「无过渡起点」,所以直接瞬间突变,不会平滑动画; - 显式写width:100%后生效的原因:给width属性赋了具体的、可计算的数值,此时过渡是 width:100% → width:具体小值,两个都是确定值,过渡正常触发。
- width的auto和width:100%本质上也不是一个东西,100%的width则表明给元素的宽度显式设置为父元素的100%宽度,此时,如果你再给其加上20px的外边距,那么这个元素整体占据的内容就会超过父元素的宽度。
- 而如果是auto,那么你给这个元素添加20外边距,那么这个元素整体占据的内容仍然不会超过父元素,此时这个元素的宽度就是 100% - 40px
块级元素的「默认宽度」到底是什么?
- 所有块级元素(div/p/h1等)的width属性,在你不手动设置的情况下,浏览器给的默认样式是:width: auto,这是 CSS 的官方规范定义,没有例外。
- 你看到的「块级 div 默认占满父元素宽度」,是 width: auto 这个属性值的
渲染表现结果,而不是width的值等于100%(auto并不是css可计算值)- width: auto:自适应宽度,规则是「自动拉伸,填满父元素的水平可用空间」,它是一个「动态计算的、无固定值的状态」,不是一个具体的数值 / 百分比;
- width: 100%:固定占比宽度,规则是「宽度严格等于父元素宽度的 100%」,它是一个「静态的、可计算的具体值」。
视觉上,绝大多数场景下两者都是「占满父容器」,但这只是表象,在 CSS 的属性计算层面,这是两个完全不同的值,这个差异对transition/animation这类需要「属性值变化」的 CSS 特性,是致命的。
CSS transition 过渡动画的【生效前提】
- CSS transition 生效的唯一核心条件:被过渡的 CSS 属性,
必须发生 「从一个【具体的、可计算的确定值】 到 另一个【具体的、可计算的确定值】」 的变化。 - 过渡对「auto」的判定逻辑:浏览器在解析过渡时,遇到属性值是auto,会直接判定这个属性「没有初始值」,它无法计算「从 auto 到具体值」的差值是多少、过渡的步长是多少,所以干脆跳过过渡逻辑,直接应用目标样式 → 表现为「立即变小」,没有动画。
这个问题不只是 width,只要 CSS 属性的默认值是auto,在做过渡 / 动画时都会遇到这个问题,例如width、height、margin中的auto等
优雅替代方案:父容器宽度不固定时,用max-width:100%替代width:100%做过渡,是最优解。
css变换
我们将学习transform属性,它可以用来改变页面元素的形状和位置,其中包括二维或者三维的旋转、缩放和倾斜。变换通常结合过渡或动画一起使用
注意:个人认为,通常我们实际在日常的开发中,基本上使用二维的一些平移、缩放、旋转就可以实现大部分的日常工作了。
变换说明
- 使用变换的时候要注意一件事情,虽然元素可能会被移动到页面上的新位置,但
它不会脱离文档流。你可以在屏幕范围内以各种方式平移元素,其初始位置不会被其他元素占用(同样是绘制阶段的元素修改,其布局阶段的位置和占据的区域不会改变)但是元素本身是确实会被缩小的(移动旋转之类的也是类似),也就是说你的点击的范围是缩小后的范围,基本上所有的这种不脱离文档流都会是这种行为) - transform会创建一个层叠上下文
- 变换不能作用在span或者a这样的行内元素上。若确实要变换此类元素,要么改变元素的display属性,替换掉inline(比如inline-block),要么把元素改为弹性子元素或者网格项目(为父元素应用display: flex或者display: grid)。
- 基点
- 变换是围绕基点(point of origin)发生的。基点是旋转的轴心,也是缩放或者倾斜开始的地方。这就意味着元素的基点是固定在某个位置上,元素的剩余部分围绕基点变换(但translate()是个例外,因为平移过程中元素整体移动)。
默认情况下,基点就是元素的中心,但可以通过transform-origin属性改变基点位置- 注意:
基点不会在执行变换后变化,即如果一个元素使用变换移动了,那么基点还是再元素原来位置的中心点(或者设置的那个点),此时如果再进行其他变化可能不会如你所想的那样(例如移动后,再旋转) - 也可以使用百分比、px、em或者其他单位的长度值来指定基点。不过通常使用top、right、bottom、left和center这些关键字,在大部分项目中就够用了。
- 多重变换
- 可以对transform属性指定多个值,用空格隔开。变换的每个值
从右向左按顺序执行(注意,不是从左向右) - MDN上的定义:变换函数按从左到右的
顺序相乘,也就是说复合变换按从右到左的顺序有效地应用。- 这应该和矩阵相关
- 可以对transform属性指定多个值,用空格隔开。变换的每个值
- 变化的好处
- 性能要比其他属性要更好
- 变换本身也比较简单,在不涉及矩阵的情况下。移动、缩放和旋转简单很多,一行就搞定了。
- 处理过渡或者动画的时候,尽量只改变transform和opacity属性。如果有需要,可以修改那些只导致重绘而不会重新布局的属性。只有在没有其他替代方案的时候,再去修改那些影响布局的属性,并且密切关注动画中是否存在性能问题。
变换和过渡
- 变换可以使用过渡来来实现动画效果,直接写:transition: transform 0.3s;这种即可
- 常见的平移、缩放、旋转都可以过渡
3D变换
- 为页面添加3D变换之前,我们需要先确定一件事情,即
透视距离(perspective)。变换后的元素一起构成了一个3D场景。- 接着浏览器会计算这个3D场景的2D图像,并渲染到屏幕上。我们可以把透视距离想象成“摄像机”和场景之间的距离,前后移动镜头就会改变整个场景最终显示到图像上的方式。
- 如果镜头比较近(即透视距离小),那么3D效果就会比较强。如果镜头比较远(即透视距离大),那么3D效果就会比较弱。
- 默认情况下,透视距离的渲染是假设观察者(或者镜头)位于元素中心的正前方。
- 可以通过两种方式指定透视距离:使用perspective()变换或者使用perspective属性
- 有时候我们希望多个元素共享同一套透视距离,就仿佛它们处于相同的3D空间中。实现这种效果,需要为它们的父元素设置perspective属性。
- perspective()函数可以为单个元素设置透视距离(写在transition中)
- rotate()就可以被称为rotateZ(),它就是绕着Z轴旋转的。
- 默认情况下元素的背面是可见的(例如用旋转的时候),但我们可以为元素设置backface-visibility:hidden来改变它。添加这条声明之后,元素只有在正面朝向观察者的时候才可见,朝向别处的时候不可见。
- 针对这项技术,一个可能的应用场景是把两个元素背靠背放在一起,就像卡片的两面。卡片的正面展示出来,背面隐藏。然后我们可以旋转它们的容器元素,使这两个元素都翻转过来,这样正面隐藏背面显现。卡片翻转特效的演示,参见文章Intro to CSS 3D Transforms。
- 如果你要使用嵌套元素构建复杂的3D场景,transform-style属性就变得非常重要。(了解一下即可)
过时的写法:变换使用Z轴开启GPU的3D硬件加速
以前有一个说法,需要使用的3D写法(例如translate3d)才能启用3d硬件加速,但是现代开发中已经不再需要了。
现代浏览器开启 GPU 加速的核心条件和好处:
- 触发了 CSS 的「合成层(Composite Layer)」创建,而触发合成层的 CSS 属性,跟 Z 轴没有必然绑定关系,有两类核心触发方式,且都不需要刻意写 Z 轴相关代码。
- 第一类(最常用,无副作用):纯 2D 变换也能触发 GPU 加速:translateX、scale、rotate等
- transform 是浏览器原生的「GPU 友好型属性」 不管是 2D 还是 3D,
只要写了transform(非none),就会触发 GPU 加速,这是transform的原生特性,和 Z 轴无关
- transform 是浏览器原生的「GPU 友好型属性」 不管是 2D 还是 3D,
- 3D 变换(包含 Z 轴相关),也触发 GPU 加速:translate3d、scale3d等
- 很多人误以为「只有写translateZ(0)才生效」,本质是早期部分老旧浏览器的兼容写法,现在所有现代浏览器(Chrome/Firefox/Edge/Safari)都早已修复这个问题。
- 第一类(最常用,无副作用):纯 2D 变换也能触发 GPU 加速:translateX、scale、rotate等
- 这个合成层(Composite Layer)可以将其视为浏览器将该元素单独抽离出了一层进行绘制,并且会交由GPU进行绘制,这就是所谓的GPU硬件加速
- 那主图层那样使用主CPU绘制的吗?是的,注意,这里是绘制阶段,通常绘制阶段是和主线程互斥的,所以,
绘制阶段其本身是CPU的,但是绘制独立的合成层的页面会交由GPU进行绘制,那么此时确实可以看做性能更高了。 - 且后续进行变换时,也和主图层无关,仅仅只需要利用GPU处理自己所在的那个合成层即可。
- 那主图层那样使用主CPU绘制的吗?是的,注意,这里是绘制阶段,通常绘制阶段是和主线程互斥的,所以,
- 如果只是对页面做一次性修改,那么通常不会感觉出这种优化可以带来明显的差异。但如果修改的是动画的一部分,屏幕需要在一秒内发生多达几十次的更新,这种情况下渲染速度就很重要了。大部分的屏幕每秒钟会刷新60次。
为什么会流传「加 Z 轴才能 GPU 加速」的误区?都是历史原因 + 旧版兼容问题,2025 年的今天完全无需遵守
- 早期浏览器的「3D 变换强制分层」特性
- 早期(2010-2016 年)的 Chrome/Safari 浏览器,对「纯 2D transform」的优化不够彻底,部分场景下虽然也触发 GPU 加速,但稳定性差;而一旦写了translate3d()/translateZ(0)这类3D 变换属性,浏览器会「强制开启独立合成层」,GPU 加速的优先级更高、更稳定。
- 把「z-index」和「translateZ」搞混了
- 很多人会把 CSS 的 z-index(控制元素堆叠顺序,纯 CSS 属性,和 GPU 无关) 和 transform: translateZ(0)(3D 变换,触发 GPU 加速) 搞混,误以为「给元素加 z-index 就能 GPU 加速」,这是完全错误的
为什么 transform 能触发 GPU 加速?
- 浏览器的渲染流水线分为 4 步:样式计算(渲染树) → 布局(重排) → 重绘 → 合成
- 普通 CSS 属性(如width/height/margin/top/left)修改时,会触发「布局 + 重绘 + 合成」
- 以前使用定位,来减少大规模的元素重绘和重排,现在追求合成动画,把重绘重排也都干掉。
- transform/opacity 这两个 CSS 属性,修改时不会触发布局和重绘,只需要执行最后一步「合成」(
也可以理解为变化时在合成阶段对合成层直接进行变化处理的),而「合成」这一步是GPU 的专属强项,CPU 直接把计算任务交给 GPU,渲染效率提升 10 倍以上。 - 核心逻辑:GPU 加速的触发,是因为属性本身的渲染特性,不是因为 Z 轴!
- 普通 CSS 属性(如width/height/margin/top/left)修改时,会触发「布局 + 重绘 + 合成」
合成动画的注意事项
- transform: none 不会触发 GPU 加速
- GPU 加速的本质是「创建独立合成层」,每个合成层都会占用 GPU 的显存资源,如果页面上大量元素都开启 GPU 加速,会导致显存占用过高,轻则页面卡顿、重则浏览器崩溃(移动端更明显)。
- 正确做法:只给「需要做动画 / 过渡」的元素加 transform,普通静态元素不需要加。
- 除了transform,浏览器对以下属性也会触发 GPU 硬件加速,和 Z 轴无关:
- opacity(透明度变化)
- filter(滤镜,如模糊、渐变,部分场景)
- will-change: transform/opacity(提前告诉浏览器「这个元素要做变换」,主动创建合成层)
动画
- 为了对页面变化有更加精确的控制,CSS提供了关键帧动画。
- 关键帧(keyframe)是指动画过程中某个特定时刻。我们定义一些关键帧,浏览器负责填充或者插入这些关键帧之间的帧图像
过渡vs动画
- 过渡是直接从一个地方变换到另一个地方,相比之下,我们可能希望某个元素的变化过程是迂回的路径。有时,我们可能需要元素在动画运动后再回到起始的地方。这些事情无法使用过渡来实现。
- 重复某一个过渡效果
- 状态回到原始状态(A -> B -> A)
- 过渡过程中,无法返回到中间的某一个状态,例如100px变为300px的过渡,它无法做到100 -> 200 -> 150 -> 300(这里的回到150就无法做到),仅能通过非常难处理的贝塞尔曲线才有一点可能,效果还很不好。
- 从原理上看,过渡其实和关键帧动画类似:我们定义第一帧(起始点)和最后一帧(结束点),浏览器计算所有中间值,使得元素可以在这些值之间平滑变换。
但使用关键帧动画,我们就不再局限于只定义两个点,而是想加多少加多少。浏览器负责填充一个个点与点之间的值,直到最后一个关键帧,最终生成一系列无缝衔接的过渡。- 过渡只能有一个起始点和终点,没有中间点可以控制
- 注意:
过渡和动画不要一起使用
动画编写
- CSS中的动画包括两部分:用来定义动画的@keyframes规则和为元素添加动画的animation属性
- @keyframes规则
- 在其规则里面可以使用0%或者100%以及任意中间值,
定义其关键帧节点,每个关键帧节点中都可以定义其属性(例如宽、高、变换、颜色等)- 关键帧节点也可以认为是元素在动画的这个节点时的元素状态,例如位置、背景等状态
- 每个关键帧之间,其css状态差异应该是使用类似过渡的动画帧插值来实现(浏览器控制)
- 每个相邻关键帧节点的元素状态,就是动画在这个时间节点时的元素状态过渡(背景间的过渡、位置间的过渡等)
- 每个关键帧的元素状态都不需要完全一样的属性,如果0和100有背景,但是50没有,那么在动画到50时,其背景就是0和100背景的中间那个值。你可以看做是一种默认行为
- 同样的,如果是auto这些,非css计算值的,是无法实现动画插值的,也就是无法用动画,这点和过渡类似
- 两个关键帧使用的值相同,所以我们可以只定义一次属性值,使用逗号分隔
- 在其规则里面可以使用0%或者100%以及任意中间值,
- 使用animation来为元素添加具体动画、时长、状态控制等
- animation-name(over-and-back)——代表动画名称,就像@keyframes规则定义的那样。
- animation-duration(1.5s)——代表动画持续时间,在本例中是1.5s。
- animation-timing-function(linear)——代表定时函数,用来描述动画如何加速和/或减速。可以是贝塞尔曲线或者关键字值,就像过渡使用的定时函数一样(ease-in、ease-out,等等)。
- animation-iteration-count(3)——代表动画重复的次数。初始值默认是1。
- 注意
- 如果你打算重复某个动画并希望整体衔接流畅,需要确保结束值和初始值相匹配。
- 注意动画持续过程中,原本设置的样式声明会被@keyframes中的规则覆盖。
如果出现样式层叠,那么动画中设置的规则比其他声明拥有更高的优先级。动画结束后,恢复原本设置的样式。 - 通常开发书写的样式优先级高于用户代理样式(浏览器),因为开发书写的样式有比较高的优先级来源,但是
动画中应用的声明有更高的优先级来源。为某个属性添加动画的时候,会覆盖样式表中其他地方应用的样式。这就确保了关键帧中所有的声明可以互相配合完成动画,而不用关注动画之外对这个元素可能应用了哪些样式。- 这就导致动画在开始的时候,0%关键帧上的属性值会瞬间被应用,如果加了延迟,那么气动画上的0%属性也会等到延迟时间到了后才会被应用,这可能会导致一些淡入的动画在一开始会完全显现,等到动画延迟到了后又立即消失,然后开始执行动画。
- 我们可以把动画样式后向填充设置,就像一直暂停在第一帧,直到动画开始播放。可以使用
animation-fill-mode属性来实现 - 使用animation-fill-mode可以在动画播放前或播放后应用动画样式。这也可以让动画在播放完成后,将最后一帧的样式应用到元素上
- 不过要注意优先级
什么时候用 CSS?什么时候用 JS?
动画和过渡相比于js触发的处理的动画的好处
- 浏览器底层处理过渡,插入动画帧,帧率对齐屏幕刷新率
- js本身需要占用线程执行,即使是requestanimationframe也是需要修改和处理dom节点
- 动画和过渡更加简单
- 如果js和过渡、动画都是处理的变换或者透明度这种,那么他们同样能够满足3d硬件加速的,只不过js处理多了一个js任务执行罢了。
- 考量:所有 CSS 动画的短板,总结为一句话:CSS 只能做「纯视觉、规则化、无逻辑依赖」的动效,无法做「复杂逻辑、动态计算、数据驱动」的动效。
- 无法实现「动态数据驱动」的动画:比如:根据接口返回的「进度值(30%/80%)」做进度条动画、根据鼠标坐标做跟随动画、根据用户滑动距离做视差滚动、小球碰撞反弹的物理动画
- 无法实现「复杂逻辑控制」的动画:CSS 动画的控制能力非常有限,只能做「简单的暂停 / 播放 / 循环」,无法实现「条件判断、多动画联动、时序控制」。
什么时候用 CSS?什么时候用 JS?
- 法则 1:能使用 CSS 动画 / 过渡实现的,绝对优先用 CSS (95% 的场景)
- 所有「纯视觉、无逻辑、规则化」的动效
- 法则 2:只有 CSS 做不到的时候,才用 JS 动画 (5% 的场景)
- 所有「需要动态数据、复杂逻辑、物理效果」的动效
- 法则 3:折中方案:【CSS 做动效主体 + JS 做逻辑控制】(最优实践)
- 这是前端动效的天花板写法,兼顾 CSS 的性能优势 和 JS 的逻辑优势,也是大厂的主流写法,比如:
- 用 CSS 写好动画(@keyframes + animation),通过 JS 控制元素的 class 来启停动画;
- 用 CSS 写过渡效果,通过 JS 修改元素的 transform/opacity 属性触发过渡;
- 用 CSS 做动画主体,通过 JS 监听 transitionend/animationend 事件实现「动画完成后的逻辑回调」。
- 通常就是animation + transform/opacity + js控制
疑问:过渡和动画本身会使用3d硬件加速吗?
疑问:过渡和动画本身会使用3d硬件加速吗?即使内部没有使用变化,只是使用了宽高、颜色等属性进行过渡和动画,就像css变换一样会生成单独的合成层吗?
答案是不会。
单纯的 transition / animation 过渡 / 动画本身,不会触发 GPU 硬件加速、也不会创建独立合成层 能不能触发 GPU 加速、会不会生成合成层,唯一的判定依据是「你动画 / 过渡的是什么 CSS 属性」,和 transition/animation 语法本身无关。
核心原则:「谁变化,谁决定」 动画的性能,由【被过渡的属性】决定
transition: all 0.3s / @keyframes 只是「动画的执行规则」,它只负责「让属性的值从 A 变到 B」,本身不具备「加速 / 分层」能力。- 浏览器把所有 CSS 属性分成了 3 类渲染行为,优先级 / 性能天差地别,动画用不同属性,结果完全不同
- 第一类:【合成层属性】→ 触发 GPU 硬件加速 + 创建独立合成层
- 只有 2 个属性:transform + opacity这是浏览器「亲儿子级」最优属性
- 核心特性:
- 无论是 transition: transform 还是 animation 动画处理 transform/opacity,必然触发 GPU 硬件加速,自动生成独立合成层;
- 动画时,不会触发「重排 (布局)」、不会触发「重绘」,只需要 GPU 做「合成」这一步,性能开销≈0,丝滑无卡顿;
- 这也是为什么所有性能优化文章都反复强调:做动画,优先用 transform/opacity;
- 补充:哪怕是 transform: translateX(0) 这种「视觉无变化」的写法,只要写了非 none 的 transform,动画也会走 GPU 加速。
- 第二类:【重绘属性】→ 无 GPU 加速、无独立合成层
- 典型代表:background-color/color/box-shadow/border-color/outline/text-shadow 等视觉样式类属性
- 用 transition/animation 动这类属性,完全不会触发 GPU 硬件加速,也不会创建合成层;
- 动画时,不会触发布局 (重排),但会触发「重绘」 重绘是 CPU 计算「像素的颜色变化」,然后把新的像素渲染到屏幕上,性能开销中等;
- 这类动画肉眼看大概率不卡顿(比如颜色渐变、阴影变化),但本质是 CPU 在干活,GPU 全程躺平,没有任何硬件加速加持;
- 重点:这类属性永远不会生成独立合成层,元素始终在「主合成层」里渲染。
- 第三类:【重排属性】→ 无 GPU 加速、无合成层 + 性能最差
- 典型代表:width/height/margin/padding/top/left/bottom/right/font-size/display 等盒模型 / 布局类属性
- 用 transition/animation 动这类属性,完全没有 GPU 加速,完全没有独立合成层,是性能最差的动画方式;
- 动画时,会触发「重排 (布局)」+「重绘」+「合成」 浏览器渲染流水线的全部三步,性能最差;
- 重排的开销极大:因为 width/height/top 这些属性决定了元素的位置和大小,改一个值,浏览器需要重新计算「整个页面的元素布局(夸张了,定位会好一点,脱离文档流)」,牵一发而动全身;
- 这类动画大概率会卡顿(尤其是移动端 / 复杂页面),比如:宽高从 100px 变 200px 的过渡,肉眼能看到掉帧,就是 CPU 算力跟不上导致的。
浏览器创建「独立合成层」是有严格条件的,不是随便一个动画就能创建,核心规则:
- 只有当元素的渲染可以被 GPU 独立处理、且不影响其他元素 时,浏览器才会为其创建独立合成层
- 而 transform/opacity 刚好满足这个条件
- transform:
是「元素的像素矩阵变换」,元素的布局、大小、位置在文档流里完全没变,只是 GPU 把这个元素的像素整体平移 / 缩放 / 旋转,和其他元素互不干扰; - opacity:是「像素透明度调整」,GPU 直接修改像素的透明度值,不需要重新计算布局和绘制。
- transform:
- 而 宽高 / 颜色 /top/left 这类属性,完全不满足这个条件
- 改宽高 → 元素的盒模型变了,会挤压 / 拉伸周围元素,必须 CPU 重新计算整个页面的布局,GPU 无法独立处理;
- 改颜色 → 元素的像素颜色需要 CPU 重新绘制,GPU 只是负责把 CPU 绘制好的像素显示出来,没有独立处理的空间;
- 改 top → 元素在文档流里的位置变了,浏览器需要重新计算元素的堆叠和布局,GPU 插不上手。
补充:那些「看似有用,实则无效」的加速技巧
- 加了 translateZ (0),宽高动画就会 GPU 加速?错
- transform: translateZ(0) 确实能给元素创建独立合成层 + GPU 加速,但这个加速只对 transform/opacity 生效。
- 如果你给元素加了这个属性,同时又用 transition: width 动宽高,结果是
- 元素确实在独立合成层里,但
宽高的动画依然是 CPU 重排 + 重绘,GPU 只是负责把 CPU 计算好的结果「合成」到屏幕上,没有任何性能提升,反而白白占用 GPU 显存,得不偿失。 所有 GPU 加速的技巧(translateZ (0)、will-change、transform),只对 transform/opacity 这两个属性有用,对其他所有属性都无效!
- CSS 动画 vs 过渡,在「GPU加速」上有区别吗?没有区别
transition(过渡)和 animation(关键帧动画)只是「动画的两种书写形式」,本质都是「让属性值渐变」,它们的性能表现、是否触发 GPU 加速、是否创建合成层,完全由「被动画的属性」决定,和用哪种动画语法无关。
选择器
可以列举一些最常用的
基本的
- 基础选择器
- 标签选择器:div、p
- 类选择器:.class
- 通配符(
*)选择器
- 组合选择器:组合器将多个基础选择器连接起来组成一个复杂选择器。
- 子组合器(>)——匹配的目标元素是其他元素的
直接后代。例如:.parent > .child。 - 相邻兄弟组合器(+)——匹配的目标元素紧跟在其他元素后面。例如:p + h2。
- 他就猫头鹰选择器比较用的多,给元素加边距
- 通用兄弟组合器(~)——匹配所有跟随在指定元素之后的兄弟元素。注意,它不会选中目标元素之前的兄弟元素。例如:li.active ~ li。
- 复合选择器:多个基础选择器可以连起来(不使用空格或者其他组合器)组成一个复合(compound)选择器(例如:h1.page-header)。复合选择器选中的元素将匹配其全部基础选择器。
- 子组合器(>)——匹配的目标元素是其他元素的
伪类选择器(不是伪类元素)
伪类选择器用于选中处于某个特定状态的元素。这种状态可能是由于用户交互,也可能是由于元素相对于其父级或兄弟元素的位置。伪类选择器始终以一个冒号(:)开始。优先级等于一个类选择器(0,1,0)。
- :first-child 匹配的元素是其父元素的第一个子元素。
- :last-child 匹配的元素是其父元素的最后一个子元素。
- :only-child 匹配的元素是其父元素的唯一一个子元素(没有兄弟元素)。
- :nth-child(an+b) 匹配的元素在兄弟元素中间有特定的位置。公式an+b里面的a和b是整数,该公式指定要选中哪个元素。
- 要了解一个公式的工作原理,请从0开始代入n的所有整数值。第0个元素不是一个真正的元素,n可以选择负数
- :nth-child(n):所有元素
- :nth-child(n + 2):从第2个元素开始的
- :nth-child(2n):从0开始,偶数
- :nth-child(2n + 1):从1开始,基数
- :nth-child(n):所有元素
- :nth-child(-n + 4):前4个元素。
- :nth-last-child(an+b)类似于:nth-child(),但不是从第一个元素往后数,而是从最后一个元素往前数。
- :first-of-type 类似于:first-child,但不是根据在全部子元素中的位置查找元素,而是根据拥有相同标签名的子元素中的数字顺序查找第一个元素。例如div:first-of-type 则找第一个是div的子元素
- :first-of-type系列
:not(<selector>)匹配的元素不匹配括号内的选择器。括号内的选择器必须是基础选择器,它只能指定元素本身,无法用于排除祖先元素,同时不允许包含另一个排除选择器。- :focus 匹配通过鼠标点击、触摸屏幕或者按Tab键导航而获得焦点的元素。
- :hover 匹配鼠标指针正悬停在其上方的元素。
- :root 匹配文档根元素。对HTML来说,这是html元素,但是CSS还可以应用到XML或者类似于XML的文档上,比如SVG。在这些情况下,该选择器的选择范围更广
还有一些表单域相关的伪类选择器。其中一些是在选择器Level4版本的规范中提出或者修订的,因此在IE10以及其他一些浏览器中不受支持。请在Can I Use网站上查看兼容情况。
伪元素选择器
伪元素类似于伪类,但是它不匹配特定状态的元素,而是匹配在文档中没有直接对应HTML元素的特定部分。
- 这些选择器以双冒号(::)开头,尽管大多数浏览器也支持单冒号的语法以便向后兼容。伪元素选择器的优先级与类型选择器(0,0,1)相等。
- ::before——创建一个伪元素,使其成为匹配元素的第一个子元素。该元素默认是行内元素,可用于插入文字、图片或其他形状。必须指定content属性才能让元素出现,例如:.menu::before。
- ::after——创建一个伪元素,使其成为匹配元素的最后一个子元素。该元素默认是行内元素,可用于插入文字、图片或其他形状。必须指定content属性才能让元素出现,例如:.menu::after。
属性选择器
属性选择器用于根据HTML属性匹配元素。其优先级与一个类选择器(0,1,0)相等。
[attr]——匹配的元素拥有指定属性attr,无论属性值是什么,例如:input[disabled]。[attr="value"]——匹配的元素拥有指定属性attr,且属性值等于指定的字符串值,例如:input[type="radio"]。
大型应用程序中的CSS
掌握浏览器如何渲染CSS很重要,了解如何在项目中编写和组织CSS也很重要(这部分仅做了解即可)
模块化css
模块化CSS(Modular CSS)是指把页面分割成不同的组成部分,这些组成部分可以在多种上下文中重复使用,并且互相之间没有依赖关系。最终目的是,当我们修改其中一部分CSS时,不会对其他部分产生意料之外的影响。
- 注意,这里不是指react中的css module的样式写法,而是更为宽泛的概念(参考vue单文件组件中scoped带作用域的样式,其可以被称为模块化css
- 模块化的样式则允许开发人员添加一些限制。我们把样式表的每个组成部分称为模块(module),每个模块独立负责自己的样式,不会影响其他模块内的样式。也就是说,在CSS里引入了软件封装的原则。
- 如果没有模块化css的项目,那么时间一长,以项目中的按钮为例,除了可能出现的大量重复的代码之外,那么很可能会出现类似的元素却有不同的表现。
- 重复是为了获得一致的体验,但是随着时间的推移,不同的按钮之间还是发生了一些不一样的改变。因此,有的按钮内边距稍有不同,有的按钮红得更鲜艳
- 解决办法就是把按钮重构成一个可复用的模块,不受页面位置的限制。创建模块不但可以精简代码(减少重复),还可以保证视觉一致性。
- 不要使用依赖语境的选择器
- 这里其实是指,将选择器和不属于当前功能范围的元素组合起来,例如修改button样式,但是你在编写选择器时,偏偏把父级的选择器也带进来,这样也就是将这个选择器的css样式规则和这个特定的父元素给绑定了。
- 也就是
千万不要使用基于页面位置的后代选择器来修改模块。 - 不过在现代化的组件化前端代码中,基本上这个问题影响很小了,因为你的样式基本上作用范围只有当前的组件css样式,天生就是模块化的。
- 即使出现要修改组件样式,通常为了兼容性,也会给组件添加可控制的属性来控制组件样式。
- 尽量避免在模块选择器中使用通用标签名(你写不写模块化css的代码都应该要尽量避免)
- 因为这些通用标签,例如div和span这种,其太过宽泛,很容易后续修改时新增一个这种元素,这时候你就不得不重新改动之前的css写法以及回想当时的写法的含义和可能的影响,思考该如何修改了。
- 每个模块应该只做一件事情。组件化也是这种思想
- 为模块命名是个很伤脑筋的事情。命名是开发中非常需要修炼的一个技能。
- 命名应该按照其具体的含义,而不是其行为或者外观啥的,这些只是这个模块的外在表现,而不是内在含义
创建模块化css的方式
- 为一些通用的样式编写唯一类名(例如字体大小、常用布局),这样进需要给html添加不同的类名,就可以应用相应的css样式,这也算一种css模块,不过仅限于简单的样式(Tailwind CSS)
- 如果要自己写纯的模块化css代码(即通过css类名来定义模块),那么其命名可以参考BEM命名规范,它比较贴合模块css的写法(模块、模块内元素、状态)
- 如果样式变得复杂,或者需要特定的html结构,那么在现代开发中,直接为相应的UI元素编写为一个组件则是更为通用的方法。其中同时包含了html和css样式
- 或者,现代的css由于有了预处理器以及编译阶段,可以模块化的更为彻底
基础样式
- 每个样式表的开头都要写一些
给整个页面使用的通用规则,这些规则通常被称为基础样式,其他的样式是构建在这些基础样式之上的。基础样式本身并不是模块化的,但它会为后面编写模块化样式打好基础。 - 基础样式应该是通用的,只添加那些影响页面上大部分或者全部内容的样式。选择器不应该使用类名或者ID来匹配元素,应只用标签类型或者偶尔用用伪类选择器。核心思想是这些基础样式提供了一些默认的渲染效果,但是之后可以很方便地根据需要覆盖基础样式。
- 这里推荐一个叫作normalize.css的库,这个小样式表可以协助消除不同的客户端浏览器渲染上的不一致。
- 不过现代的打包工具或者脚手架,这一块都自带了的。虽然没有那么全,不过应该也是够用的
工具类
有时候,我们需要用一个类来对元素做一件简单明确的事,比如让文字居中、让元素左浮动,或者清除浮动。这样的类被称为工具类(utility class)。
- 从某种意义上讲,工具类有点像小号的模块。工具类应该专注于某种功能,一般只声明一次。我通常把这些工具类放在样式表的底部,模块代码的下面。
- 工具类是唯一应该使用important注释的地方。事实上,工具类应该优先使用它。说实话,不用的话,可能会被覆盖掉。
- 工具类的作用立竿见影。在页面上做点小事儿的时候不需要创建一个完整的模块,这种情况下可以用一个工具类来实现。但是不要滥用工具类。
模式库
把模块清单整合成一组文档,在大型项目中已经成为通用做法。这组文档被称为模式库(pattern library)或者样式指南(style guide)。模式库不是网站或者应用程序的一部分,它是单独的一组HTML页面,用来展示每个CSS模块。
也就是将你的css模块代码通过例如注释或者其他方式生成一个使用文档,用于团队协助。不过现代的css,由于组件化的存在,基本上可以不需要这个所谓的模式库的,了解即可。
一旦开始建立自己的模式库,你可能会发觉,仿佛正在建一个自己的框架。没错,就是这样!这也正是为什么这些框架都是成功的,因为它们每一个都是模式库。框架里面的CSS样式,都是为了可以在多种场景下可复用而特意编写的。
不要盲目地使用框架,要学习思考框架背后的设计思路。想象一下,如果你的模式库要设计成通用适配的,让不知道是谁的第三方使用,要怎么做。这可以让你写的样式可复用性更好,如果后期需要修改,可以减少对页面的破坏。
通常来讲,CSS是一门“只增不减”的语言。开发者害怕去编辑或者删除那些已有的样式,因为他们没办法完全预知修改造成的结果。这时他们就会添加更多的代码到样式表的底部,来覆盖之前的样式规则,或者增加选择器优先级,最终导致样式表变成了一堆难以维护的乱码。以模块化的方式来组织CSS代码,再维护一套与之相应的模式库,就可以避免陷入这样尴尬的境地。你总是知道某个模块的样式位于何处。每个模块只用来做一件事。
预处理器
对现代CSS工作流来讲,使用预处理器是必不可少的一部分。预处理器不仅有助于提高代码书写效率,而且有助于维护基础代码。
我们需要明确一件事情,对浏览器而言,因为最终输出是常规CSS,所以预处理器不会向语言添加任何新特性。但对作为开发者的我们来讲,预处理器确实提供了许多方便。
sass
如果你要在编写css时,抽离一些通用的css、逻辑代码,或者工具样式,那么可以花时间了解一下sass中的一些功能。预处理器不会修改CSS的基本原理。我在整本书中讲到的内容,依然全部适用。
Sass支持两种语法:Sass和SCSS。它们的语言特性一样,但Sass语法去掉了所有的大括号和分号,严格使用缩进来表示代码结构。所以我们选择的sass语法是scss,其文件和lang也是scss,一般不选sass语法
好处
- 行内计算:定义一个sass变量后,如果其是数值,那么一般可以使用加减乘除来对其进行数学运算
- 嵌套选择器,可以更加明确样式优先级,但是也会提高样式优先级
- 默认情况下,外层的选择器会自动添加到编译代码的每个选择器前面,拼接的位置还会插入一个空格。要修改默认操作,可以使用
&符号代表外层选择器想要插入的位置。 - 也可以在声明块内嵌套媒体查询,这样可以用来避免重复书写相同的选择器,这样的话,如果你要修改选择器,就不必再去媒体查询里面修改对应的选择器了。(可以研究一下,对于编写响应式代码会有一些用处)
- 默认情况下,外层的选择器会自动添加到编译代码的每个选择器前面,拼接的位置还会插入一个空格。要修改默认操作,可以使用
- 局部文件,样式的代码组织
- 局部文件可以允许你把样式分割成多个独立的文件,Sass会把这些文件拼接在一起生成一个文件。使用局部文件可以按照自己的想法随意组织文件,但最终只提供给浏览器一个文件,这样可以减少网络请求的数量。
运行Sass的时候,局部文件会被编译,然后插入到@import规则指定的地方。
- 混入(mixin)是一小段CSS代码块,可以在样式表任意地方复用。如果有一段特定的字体样式需要在多个地方使用,或者类似于清除浮动这样常用的重复规则。
- 他甚至类似函数,可以传递参数,但是不要太复杂的混入,不然,理解起来比较麻烦,而且没人用
- 混入使用@mixin规则来定义,使用@include规则来调用。
- 扩展:和mixin类似,但编译方式有所不同。对于扩展,sass不会多次复制相同的声明,而是把选择器组合在一起,这样它们就会包含同样的规则。
- 循环:针对某个值使用循环,可以生成一系列细小的变化。
- 在对一些重复编写的css代码时,循环可以有很大的帮助
postcss
PostCSS是另一种类型的预处理器。它编译源文件并输出一个处理过的CSS文件,这一点上和Sass或者Less一样,但PostCSS是完全依靠插件工作的。如果没有安装插件,输出文件就是没有任何变化的源文件副本。
- 你能用PostCSS实现什么功能,完全取决于你使用哪些插件
- 可以使用多个插件,提供和Sass一样的功能;也可以只用一两个插件,同时使用Sass和PostCSS运行代码。
- PostCSS最开始是指发布处理器,因为它通常在预处理器之后运行。
- PostCSS中最重要的插件可能就是Autoprefixer了。这个插件可以将相关的所有浏览器前缀都添加到CSS中。
- cssnext是另一款非常流行的PostCSS插件(也可以称之为一组插件)。这款插件希望模拟那些还没有受所有浏览器支持的最新的CSS语法(有一些还没有在CSS规范中最终确认),为实现这些CSS特性使用了很多方法,比如polyfill等。
- 它也许可以为一些新的css特性编写兼容写法,不确定,就像babel一样编写降级写法。
- cssnano是基于PostCSS的压缩工具。压缩工具(minifier)可以从代码中剥离所有无关的空格,使代码体积尽可能变小,但同时依然保持相同的语法含义。