从语言和功能两方面来构建css的知识架构。
在语言方面,从大到小的去学习css的各种语法结构,比如@rule、选择器、单位等等。
功能部分,主要学习布局、绘制、交互。布局主要学习正常流、弹性布局。绘制主要从图形和文字两方面学习。交互主要学习动画和其他交互。
语言
css 的标准众多,从一条路线出发,语法。任何css的特性都必须通过语法结构表现出来。CSS 的顶层样式表由两种规则组成的规则列表构成,一种被称为 at-rule,也就是at 规则
,另一种是 qualified rule,也就是普通规则
。
at-rule 由一个 @ 关键字和后续的一个区块组成,如果没有区块,则以分号结束。这些 at-rule 在开发中使用机会远远小于普通的规则,所以它的大部分内容,你可能会感觉很陌生。这些 at 规则正是掌握 CSS 的一些高级特性所必须的内容。qualified rule 则是指普通的 CSS 规则,也就是我们所熟识的,由选择器和声明区块指定构成的规则
。声明列表又由属性和值组成,值有普通类型的值和函数类型的值。
声明由属性和值组成,是一个属性:值的序列。属性由中划线-下划线-字母组成的标识符,不允许连续的两个中划线开头,那样会被认为是 css 变量。值可能值字符串、标识符。可以是以下这些值:
- css 关键字,initial / unset / inherit。
- 字符串
@rule
@charset : https://www.w3.org/TR/css-syntax-3/
@import : https://www.w3.org/TR/css-cascade-4/
@media : https://www.w3.org/TR/css3-conditional/
@page : https://www.w3.org/TR/css-page-3/
@counter-style : https://www.w3.org/TR/css-counter-styles-3
@keyframes : https://www.w3.org/TR/css-animations-1/
@fontface : https://www.w3.org/TR/css-fonts-3/
@supports : https://www.w3.org/TR/css3-conditional/
@namespace : https://www.w3.org/TR/css-namespaces-3/
charset 用于提示 CSS 文件使用的字符编码方式,它如果被使用,必须出现在最前面。这个规则只在给出语法解析阶段前使用,并不影响页面上的展示效果。
import 用于引入一个 CSS 文件,除了 @charset 规则不会被引入,@import 可以引入另一个文件的全部内容。
media 就是大名鼎鼎的 media query 使用的规则了,它能够对设备的类型进行一些判断。在 media 的区块内,是普通规则列表。
page 用于分页媒体访问网页时的表现设置,页面是一种特殊的盒模型结构,除了页面本身,还可以设置它周围的盒。
counter-style 产生一种数据,用于定义列表项的表现。
keyframes 产生一种数据,用于定义动画关键帧。
fontface 用于定义一种字体,icon font 技术就是利用这个特性来实现的。
support 检查环境的特性,它与 media 比较类似。
namespace 用于跟 XML 命名空间配合的一个规则,表示内部的 CSS 选择器全都带上特定命名空间。
定义的命名空间可以将通配、标签、属性选择器限制在指定命名空间里的元素上,还可以通过命名空间前缀的方式。常用于 svg / mathml 这些元素的 xml。@namesapce规则要定义在所有的@charset/@import之后,其他样式定义前。
viewport用于设置视口的一些特性,不过兼容性目前不是很好,多数时候被 HTML 的 meta 代替。
选择器
qualified rule 主要是由选择器和声明区块构成。声明区块又由属性和值构成。选择器是由 CSS 最先引入的一个机制。选择器表示CSS规则的应用范围,根据特征选择DOM元素。选择器所选择的DOM元素,叫做选择器对象。把选择器的结构分类
,由简单到复杂可以分成“简单选择器”、“复合选择器”、“复杂选择器”、“选择器列表”。
简单选择器:针对某一特征判断是否选中元素。
复合选择器:连续写在一起的简单选择器,针对元素自身特征选择单个元素。
复杂选择器:由“(空格)、 >、 ~、+、||”等符号连接的复合选择器,根据父元素或者前序元素检查单个元素。
选择器列表:由逗号分隔的复杂选择器,表示“或”的关系。
简单选择器
标签选择器/全体选择器
必须要考虑 html 或 xml 元素的命名空间问题。
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>JS Bin</title>
<!--使用命名空间规则来区分 svg 和 html 中的 a 标签 -->
<style>
@namespace svg url(http://www.w3.org/2000/svg);
@namespace html url(http://www.w3.org/1999/xhtml);
svg|a {
stroke:blue;
stroke-width:1;
}
html|a {
font-size:40px
}
</style>
</head>
<body>
<svg width="100" height="28" viewBox="0 0 100 28" version="1.1" xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink">
<desc>Example link01 - a link on an ellipse
</desc>
<a xlink:href="http://www.w3.org">
<text y="100%">name</text>
</a>
</svg>
<br />
<a href="javascript:void 0;">name</a>
</body>
</html>
id 选择器与 class 选择器
这两类选择器都是针对标签特定属性的选择器。比属性选择器更早诞生,理论上可以一定程度上替代它们。但是要注意,class 选择器识别的是:用空格分隔的 class 语法。也就是说标签的 class 属性可以通过空格的方式使用多个类选择器样式
。
属性选择器
根据标签的属性来选择标签,有4种形态。[att] 判断属性是否存在来选择标签,[att=val]精确匹配,检查一个元素属性的值是否是 val。[att~=val]多种匹配,检查一个元素的值是否是若干值之一,这里的 val 不是一个单一的值了,可以是用空格分隔的一个序列。[att|=val]开头匹配,检查一个元素的值是否是以 val 开头,它跟精确匹配的区别是属性只要以 val 开头即可,后面内容不管。
伪类选择器
这类选择器由 CSS 规定,以冒号开头。伪类选择器有普通型和函数型
两种,主要有树结构关系、链接与行为、逻辑,等伪类选择器。
树结构关系伪类
:root 伪类表示树的根元素。
:empty 伪类表示没有子节点的元素,这里有个例外就是子节点为空白文本节点的情况。
:nth-child 和 :nth-last-child 这是两个函数型的伪类,CSS 的 An+B 语法设计的是比较复杂的,我们这里仅仅介绍基本用法。:nth-last-child 的区别仅仅是从后往前数。
:first-child 和 :last-child 分别表示第一个和最后一个元素。
:only-child 按字面意思理解即可,选中唯一一个子元素。
链接与行为伪类选择器
:any-link 表示任意的链接,包括 a、area 和 link 标签都可能匹配到这个伪类。
:link 表示未访问过的链接。
:visited 表示已经访问过的链接。
:hover 表示鼠标悬停在上的元素。
:active 表示用户正在激活这个元素,如用户按下按钮,鼠标还未抬起时,这个按钮就处于激活状态。
:focus 表示焦点落在这个元素之上。
:target 用于选中浏览器 URL 的 hash 部分所指示的元素。
逻辑伪类选择器
:not 伪类,函数型伪类。
<style>
div.outer p {
color: blue;
}
div:not(.outer) p { // not 逻辑伪类选择器排除了 outer类属性选择器的 div标签下的 p标签。
color: gray
}
</style>
<div class="outer">
<!--这部分字体就是蓝色 -->
<p>
1111111
</p>
<div class="inner">
<!--这部分字体就是灰色 -->
<p>
222222
</p>
</div>
</div>
有一些提议的逻辑伪类 :is / :where / :has 违背了选择器匹配 DOM 树不回溯
的原则。最终的命运如何还不太确定。
伪类选择器作为选择器能力的补充,在实际使用中最好尽量使用id和class标识标签,约束伪类的使用。最好只在不得不使用伪类的场景使用伪类,这对于 CSS 代码的性能和可读性都有好处。
@todo 待学习的还有 querySelector 让 js 可以处理选择器。css匹配规则。
选择器间组合
在 CSS 规则中,选择器部分是一个选择器列表。选择器列表
是用逗号分隔的复杂选择器序列;复杂选择器
则是用空格、大于号、波浪线等符号连接的复合选择器;复合选择器
则是连写的简单选择器组合。
选择器列表的语法,选择器的连接方式可以理解为像四则运算一样有优先级。第一优先级,无连接符号(类选择器连写,选择同时包含类选择器的元素),第二优先级,“空格”“~”“+”“>”“||”,第三,优先级“,”。
.c,.a>.b.d {
/*......*/
}
- .c
- .a > .b.d
- .a
- .b.d
- .b
- .d
这样的一个结构。复合选择器表示简单选择器中“且”的关系,例如,例子中的“ .b.d ”,表示选中的元素必须同时具有 b 和 d 两个 class。
复杂选择器是针对节点关系的选择,它规定了五种连接符号。
“空格”:后代,表示选中所有符合条件的后代节点, 例如“ .a .b ”表示选中所有具有 class 为 a 的后代节点中 class 为 b 的节点。
“>” :子代,表示选中符合条件的子节点,例如“ .a>.b ”表示:选中所有“class 为 a 的节点的子节点中,class 为 b 的节点”。
“~” : 后继,表示选中所有符合条件的后继节点,后继节点
即跟当前节点具有同一个父元素,并出现在它之后的节点,例如“ .a~.b ”表示选中所有具有 class 为 a 的后继中,class 为 b 的节点。
“+”:直接后继,表示选中符合条件的相邻后继节点,直接后继节点即 node.nextSlibing。例如 “.a+.b ”表示选中所有具有 class 为 a 的下一个 class 为 b 的节点。nextSlibling 在 vue 的中经常用到,node.nextSlibling 返回node的后继节点,也会返回幽灵节点
。
“||”:列选择器,表示选中对应列中符合条件的单元格。
最常用的是“空格”和“>”,常用于组件化场景,组件化开发中,很难完全避免 class 重名的情况如果为组件的最外层容器元素设置一个特别的 class 名,生成 CSS 规则时,则全部使用后代或者子代选择器,这样可以有效避免 CSS 规则的命名污染问题。
选择器优先级
复杂选择器的优先级用一个三元组(a,b,c)来表示。id 选择器的数目记为 a,伪类选择器和 class 选择器的数目记为 b,伪元素选择器和标签选择器的数目记为 c,全体选择器不影响优先级。
specificity = base * base * a + base * b + c
// base 是一个足够大的正整数,最早是256进制,然后扩大到65536,现代浏览器多采用了更大的数量。
行内属性的优先级永远高于 CSS 规则。
内联样式 > ID 选择器 > 类选择器 = 属性选择器 = 伪类选择器 > 标签选择器 = 伪元素选择器。
同一优先级的选择器遵循“后面的覆盖前面的”原则。
选择器的优先级是针对复杂选择器的优先级,选择器列表不会合并计算优先级。
<div id="my" class="x y z">text<div>
<style>
.x, .z {
background-color:lightblue;
}
.y {
background-color:lightgreen;
}
</style>
.x, .z 会分别计算 .x、.z 的优先级,不会合并起来起算,根据计算规则,.x、.z、.y 的优先级权重都是1个base,再根据同一优先级,后面覆盖前面的原则。最终应用的是 .y 的样式。
选择器的使用上,如果产生复杂的优先级计算,代码的可读性一定是有问题的。所以实践中,建议你“根据 id 选单个元素”、“class 和 class 的组合选成组元素”、“tag 选择器确定页面风格”这样的简单原则来使用选择器,不要搞出过于复杂的选择器。
(1 封私信 / 80 条消息) 样式优先级如何计算 - 搜索结果 - 知乎 (zhihu.com)
伪元素
之所以没有把它放在简单选择器中,是因为伪元素本身不单单是一种选择规则,它还是一种机制
。伪元素的语法跟伪类相似,但是实际产生的效果
却是把不存在的元素硬选出来。目前兼容性达到可用的伪元素有以下几种。::first-line / ::first-letter / ::before / ::after。
::first-line 和 ::first-letter
比较类似的伪元素,其中一个表示元素的第一行,一个表示元素的第一个字母。
注意这里的第一行指的是排版后显示的第一行,跟 HTML 代码中的换行无关。CSS 标准规定了 first-line 必须出现在最内层的块级元素之内。
<div>
<!-- 最终结果第一行是蓝色 -->
<p id=a>First paragraph</p>
<p>Second paragraph</p>
</div>
div>p#a {
color:green;
}
div::first-line {
color:blue;
}
因为 p 是块级元素,所以伪元素出现在块级元素之内,所以内层的 color 覆盖了外层的 color 属性。如果我们把 p 换成 span,结果就是相反的,伪元素选择器只会应用在 div 块元素上,不会应用到内部的行级元素上。
::first-letter 的行为又有所不同,它的位置在所有标签之内,我们把前面的代码换成::first-letter。首字母变成了蓝色,这说明伪元素出现在 span 之内。
CSS 标准只要求 ::first-line 和 ::first-letter 实现有限的几个 CSS 属性,都是文本相关,这些属性是下面这些。word-spacing,表示单词间间距。letter-spacing,表示字符之间间距。text-transform,规定元素中字符的大小写显示。
::before 和 ::after
这两个伪元素跟前面两个不同的是,它不是把已有的内容套上一个元素,而是真正的无中生有
,造出一个元素。::before 表示在元素内容之前插入一个虚拟的元素,::after 则表示在元素内容之后插入。这两个伪元素所在的 CSS 规则必须指定
content 属性才会生效,我们看下例子:
<style>
p.special::before {
display: block;
content: "pseudo! ";
}
</style>
<p class="special">I'm real element</p>
还支持 content 为 counter。
::before 和 ::after 中支持
所有的 CSS 属性。实际开发中,这两个伪元素非常有用,有了这两个伪元素,一些修饰性元素,可以使用纯粹的 CSS 代码添加进去,这能够很好地保持 HTML 代码中的语义,既完成了显示效果,又不会让 DOM 中出现很多无语义的空元素。
单位
em/rem/px/vh/vw
em/rem/vw/vh/vm等是CSS3新计量单位。可以用来实现响应式布局,更好地适应不同分辨率的终端或移动设备。vw/vh/em/rem是相对长度单位,PX是绝对长度单位。
vw/vh根据窗口大小来对元素进行布局,vw表示将窗口宽度等分成100份,占据窗口宽度的份数,vh表示针对的是窗口高度。
px以像素为单位对元素进行布局。
1个em的大小是父级元素或元素自身font-size的大小,或者浏览器的默认字体大小font-size: 16px,不是固定的。1个rem的大小是固定的,等于 html 标签元素的字体尺寸大小。
百分比
详解CSS中的百分比的应用 - 知乎 (zhihu.com)
功能
从布局、绘制、交互这三个大的体系出发,在学习大体系知识点的同时巩固css的基本概念。
css 默认的元素类型
只有两种,块元素、行内元素,而行内块元素需要使用 display 来设置。html 元素的表现都是盒子,而盒子的显示方式
有3种,块元素、行内元素、行内块元素。可以通过 display 属性来设置元素的显示方式,block / inline / inline-block / none。
块元素默认占一行,宽度与父元素一致。可以设置width / height / margin / padding。块级元素设置 hegith / padding / margin 会撑大父元素的文档流,在父元素没有设置固定高度的前提下。
常见的块元素有div / p / ul / ol / li / h1-h6。一般布局中用的父元素都是块元素。
@todo 如果父元素设置了固定高度,会撑大吗?
@todo 设置 margin / padding 改变了元素的宽高,怎么就撑大父元素的文档流了呢?
行内元素不独占一行,不能通过width 来设置元素宽度,行内元素的宽度由内容宽度和 padding / margin决定。不能通过margin / padding / height 设置元素高度,不会撑高父元素。span / b / i / strong / script。部分标签可以设置 width / height,例如img / input / select / textarea。
行内元素虽然不能设置高度,但是可以通过 line-height 设置行高。设置 padding 虽然不能撑高父元素,改变行高,但是仍然有效果,比如背景色效果。
行内块元素不会独占一行,如果行内块元素在设置完 width / padding / margin 后,父元素剩余宽度还能容下第二个元素,那么第二个元素会与第一个元素在同一行排版,否则换行显示。设置width / height / padding / margin都有效果。
CSS元素类型 - msay - 博客园 (cnblogs.com)
HTML块级元素及行内元素 (biancheng.net)
盒模型
将每一个HTML元素看作一个矩形的盒子。每个盒子由4部分构成 content、padding、border、margin。盒子模型有两种,标准盒模型和IE怪异盒子模型。标准盒模型元素的 width / heigth 仅仅是内容区域的宽高。怪异盒模型元素的 width / height 是 内容区域 / padding / border 之和。
margin 是元素自身周围需要的空间,不计入元素的大小尺寸,影响的是盒子外部空间,元素自身占据的范围是从内容到边框为止。例如给元素上一个背景色,背景色实际上是覆盖的是 content / padding / border(,元素的background-color设置的是内容盒内边距,border-color设置边框的背景色)。
box-sizing属性
选择元素高度、宽度的计算公式。在 css 盒模型定义中,元素设置的 width / height 只会应用到这个元素的内容区,如果这个元素有 padding / border,那么绘制在屏幕上的时候会将盒子宽高加上设置的边框盒内边距值。这样就意味着在调整宽高的时候,要考虑元素的内边距和边框。
- content-box,标准盒模型,
默认
都为标准盒模型,width / height 值仅用于设置内容区域宽高,不包括边框、内边距。 - border-box,怪异盒子模型,width / heigth 值包括了内容宽高,以及内边距和边框。
- inherit,元素总高宽从父元素继承。
布局
早期的 css 设计不能很好的支持软件排版需求,导致了很多种民间制造的黑科技出现。也就是说 css 的排版方式有很多种。正常流是一种绕不开的排版方式,也就是必须要掌握的。
从两个角度来学习 css 的正常流,首先从感性认知的角度,实际上,正常流本身是很简单、符合直觉的东西。其次再从严谨的css标准角度去理解正常流,规定正常流排版的算法。
正常流
正常流的行为
排版这项工作,在毕昇发明活字印刷之前是不存在的,更早时候的操作叫做“雕版”。如果想要印刷书籍,就需要手工雕刻整本书作为印版。
活字印刷出现后,排版这个词被引入,作为活字印刷15道工序之一。排版的过程就是由工人将一个一个字捡出,在排入版框中。实际上,这个过程就是一个流式处理的过程。
从古代活字印刷开始,到现代的出版行业,再到今天的web,排版过程其实没有多少本质上的变化。只不过,现在css中排版需要处理的内容,不再是简单的大小相同的木字或铅字,而是有着不同字体和字号的富文本,以及大小不同的盒。这个过程中就有一个正常流的存在。正常流的排版行为
就是依次排列,排不了就换行。
更多的规则
通过设置 float 规则,可以使一些盒占据正常流需要的空间,可以将 float 理解为“文字环绕”。
设置 vertical-align 规则,可以规定如何在垂直方向对齐盒
。vertical-align 规则看起来复杂,但是实际上基线、文字顶 / 底、行顶 / 底 都是正常书写文字的时候需要用到的概念。下图说明在设置不同的 vertical-align 时,盒与文字是如何混合排版的。
上下相邻的块元素的 margin 会产生折叠,可以这样理解,将 margin 理解为一个元素规定了自身周围至少需要的空间。
从上到下有6条线,顶线、文字顶线、中线、基线、文字底线、底线。尤其记得基线不是最下面的线,最下面的是底线。
行高是指上下文本行的基线间的垂直距离。行距是指一行底线到下一行顶线的垂直距离。
CSS行高——line-height - 谦行 - 博客园 (cnblogs.com)
正常流的原理
在 css 标准中,规定了一个如何排布每一个文字或盒的算法
,这个算法依赖一个排版的当前状态,css 将这个当前状态为格式化上下文
。这样的话排版过程中文字与盒在页面中的位置就和格式化上下文有关。
@todo 这个算法是如何执行的,需要并控制了哪些东西呢?
需要排版的盒划分为块级盒、行内级盒,分别对应有块级格式化上下文、行内级格式化上下文。与正常流一样,如果单纯看格式化上下文,那么其实很简单直观。块级格式化上下文就是从上到下依次排列,行内级格式化上下文就是在一行内从左到右顺序排列元素。
正常流中的一个盒或文字排版的时候需要分成3种情况处理。遇到块级盒
,排入块级格式化上下文。遇到行内级盒
或文字
,首先尝试排入行内级格式化上下文,如果排不下,那么创建一个行盒,先将行盒排版,行盒会创建一个行内级格式化上下文。行盒是块级,对应第一种情况。遇到float盒
,将浮动盒的顶部与当前行内级上下文边缘对齐,然后根据浮动盒的方向把盒的对应边缘对到块级格式化上下文的边缘,之后再重排当前行盒。
@todo css有几种格式化上下文呢?css 有4种格式化上下文,bfc / ifc / ffc 弹性盒格式化上下文 / gfc 网格格式化上下文。flex 容器内布局规则就是用的 ffc。gfc 用的少,css3 引入的 grids 布局模型流行度低,虽然比 flex 强大。 CSS 格式化上下文(BFC,IFC,FFC和GFC)基本介绍 - 掘金 (juejin.cn)
@todo 第二种情况,行盒是块级,应该创建块级格式化上下文,为什么又创建了行内级格式化上下文?
以上排版一个盒或文字的情况处理是在一个块级格式化上下文中的排版规则
,实际上,页面中的布局并没有那么简单,一些元素会在内部创建新的块级格式化上下文。浮动元素,绝对定位元素,非块级但仍包含块级元素的容器(inline-blocks / table-cells/ table-captions),块级的能包含块级元素的容器且 overflow 不为 visible。
最后一条,winter 的理解是自身为块级,且 overflow 为 visible 的块级元素容器,它的块级格式化上下文和外部的块级格式化上下文发生了融合。也就是说,如果不考虑盒模型相关的属性,这样的元素从排版的角度就好像不存在。
@todo overflow visible 属性需要学习掌握,再理解这段话的意思。
BFC
Block Fomatting Context,块级格式化上下文,规定了块级盒子内的布局规则,内部的布局与外部的区域互不影响。
盒子是CSS布局
的基本单位,BFC规定块级盒子内部作为一个独立的区域,规定了块级盒子内部的定位方式、子元素间的关系,根据块级盒子的类型使用不同规则盒子内部进行布局、渲染。相应的行级盒子有行级格式上下文 IFC,inline formattin context。
盒模型和BFC_plia的博客-CSDN博客_bfc模型
(1条消息) 从块级元素和行内元素的分析到bfc的布局理解_埃尔斯(aiers)的博客-CSDN博客
BFC容器内布局规则
BFC容器中的盒元素会在垂直方向上一个接一个放置,块级盒子会在垂直方向上发生外边距折叠
;BFC容器内的元素margin-left靠着BFC容器的边界
,浮动元素也是靠在BFC容器的左边界上;BFC容器
不会和浮动元素重叠;内部浮动元素不会造成外部元素高度塌陷
,计算BFC
的高度时会将内部浮动元素的高度算进去。
触发BFC
根元素、float!=none、position 为 absolute/fixed、display 为 inline-block/ table-cell/table-caption/flex/inline-flex、overflow 不为 visible。
overflow默认值visible,其他值hidden/scroll/auto。
BFC应用
消除外边距折叠
问题。给上下相邻的两个块级元素其中一个外套一个BFC容器
,就可以解决。还可以清除内部浮动
,BFC容器内的浮动元素会撑满BFC容器,这样在计算BFC的高度时会将浮动元素的高度计算进去,解决了内部浮动导致的父元素高度塌陷问题
。
解决外边距折叠问题
同属一个bfc的两个盒子外边距会折叠,可以将两个盒子中任一个元素放在bfc中。
<!DOCTYPE html>
<html>
<head>
<style>
.father {
width: 350px;
height: 550px;
border: 1px solid red;
overflow: auto;
}
.son1,
.son2,
.son3 {
width: 50px;
height: 50px;
margin: 50px;
background-color: pink;
}
.son1 {
background-color: purple;
}
.outer {
overflow: hidden;
}
.son2 {
background-color: black;
}
.son3 {
background-color: blue;
}
</style>
<body>
<div class="father">
<div class="son1"></div>
<div class="outer">
<div class="son2"></div>
</div>
<div class="son3"></div>
</div>
</body>
</html>
清除元素内部浮动
普通的父元素中子元素设置了float属性之后,子元素不再占据父元素的空间,此时父元素的高度就为0,造成了父元素高度塌陷
。当设置父元素为bfc容器后,就清除了子元素浮动带来的影响,常见的就是给父元素添加overflow:hidden属性。清除浮动的意思不是清除设置的浮动属性,而是清除设置了浮动属性之后给别的元素带来的影响。
.father {
width: 150px;
border: 1px solid red;
}
.son1, .son2 {
width: 50px;
height: 50px;
background-color: pink;
}
.son2 {
background-color: purple;
}
<div class="father">
<div class="son1"></div>
<div class="son2"></div>
</div>
制作右侧盒子自适应宽度的问题(左侧盒子宽度固定,右侧宽度不固定)
当在父元素中只设定一个盒子浮动,另一个不浮动时,会造成第二个盒子在第一个盒子的下方,被覆盖掉一部分(但文字不会被覆盖)。 给第2个元素设定bfc。当增加第一个块的宽度时,第二个块的宽度会自动缩小,实现宽度自适应。
<!DOCTYPE html>
<html>
<head>
<style>
.father {
width: 200px;
border: 1px solid red;
}
.son1{
width: 100px;
height: 50px;
float: left;
background-color: pink;
}
.son2 {
background-color: purple;
height: 100px;
}
</style>
<body>
<div class="father">
<div class="son1"></div>
<div class="son2">
fsdg很大方大幅度啊打发打算打发发达打发大幅度阿凡达噶第三方打发大发顺丰的发打发大感染
</div>
</div>
</body>
</html>
正常流使用技巧
从两种经典布局问题入手,等分布局、自适应宽。
等分布局
使用百分比宽度。还有一种方法是使用 float,但是使用 float 元素只能做到顶对齐,不如 inline-block 灵活。@todo有待实践分析。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<style>
.outer {
font-size: 0;
width: 800px;
}
.inner {
width: 33.33%;
height: 100px;
display: inline-block;
outline: solid 1px blue;
}
.inner:last-child {
margin-right: -5px;
}
</style>
</head>
<body>
<!--
因为inner div 之间的换行和空格会被 html 当作文本节点,会和 inline 混排
导致这3个div并没有并排在一行。
-->
<div>
<div class="inner"></div>
<div class="inner"></div>
<div class="inner"></div>
</div>
<!-- 可以写在一行,但是代码可读性差。那么可以将 outer 字号设为 0-->
<div class="outer">
<div class="inner"></div>
<div class="inner"></div>
<div class="inner"></div>
</div>
<!-- 浏览器兼容问题。某些浏览器因为像素计算精度问题,还会出现换行。给 outer 添加特定宽度。-->
<!-- 如果添加的宽度是 100%,在某些旧浏览器上可能还是会换行,给最后一个div加上负的右margin-->
</body>
</html>
自适应宽
在 ie6 时代,自适应宽,一个元素固定宽度,另一个元素填满父容器剩余宽度,是个经典的布局问题。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<style>
.outer {
font-size: 0;
}
.fixed {
display: inline-block;
width: 200px;
vertical-align: top;
}
.auto {
display: inline-block;
margin-left: -205px;
padding-left: 200px;
box-sizing: border-box;
width: 100%;
vertical-align: top;
}
.fixed, .auto {
height: 100px;
outline: solid 1px blue;
}
</style>
</head>
<body>
<!-- 左侧已经固定宽度,现在需要让 auto 填充剩余宽度。下面代码会出现换行-->
<div>
<div class="fixed"></div>
<div class="auto"></div>
</div>
<!-- 要将两个块元素变为行内块元素,再利用负 margin-->
<div>
<div class="fixed">左侧边栏</div>
<div class="auto">内容</div>
</div>
<!-- 单纯使用上面的设置还是会有问题,参考等分布局,需要处理幽灵节点-->
<!-- 内容标签设置负的左外边距为 200px,还不行,有一个 5px 的差距,为什么呢?-->
</body>
</html>
float 实现自定义宽。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<style>
div {
height: 500px;
}
.aside {
width: 300px;
float: right;
background: yellow;
}
.main {
background: aqua;
margin-right: 300px;
}
.parent {
position: relative;
}
.left {
float: left;
width: 200px;
background-color: green;
}
.right {
float: right;
width: 200px;
background-color: red;
}
.mid {
margin-left: 200px;
margin-right: 200px; // 如果不设置左右外边距的话,内容区域会被浮动元素覆盖。
background-color: blue;
}
</style>
</head>
<body>
<!-- 两栏布局 -->
<div>
<div class="aside">侧边栏</div>
<div class="main">内容</div>
</div>
<!-- 三栏布局 -->
<div class="parent">
<div class="left">left</div>
<div class="right">right</div>
<!-- 需要放置在后面,应为没有对mid设置宽度,利用了块级元素的流体特性,如果放在
前面的放会撑满一行的宽度,导致浮动元素换行。
-->
<div class="mid">mid</div>
</div>
</body>
</html>
CSS布局:Float布局过程与老生常谈的三栏布局 - 一叶斋主人 - 博客园 (cnblogs.com)
弹性布局
flex布局
flex 是 css3 新属性,因为基于传统的盒模型进行布局,依赖于display/position/float等属性,想实现居中都不容易,flex 用于布局,可以轻松实现容器内元素的居中。并且可以自适应元素之间的相对距离,响应式实现页面布局。
容器属性
display: flex,将该元素设为flex容器。flex 子项会块状化,子项原来的 display 值会发生变化,变成块级元素。匿名内联元素也会块状化。匿名内联元素指的就是没有嵌套标签的裸露的文本元素。子项浮动会失效。子项支持 z-index 属性。子项的 margin 不会合并。子项的尺寸是被格式化后的。子项的尺寸是有具体的计算值的。可以使用 margin: auto
进行自动分配。
flex-direction,控制 flex 子项整体的布局方向。row / row-reverse / column / column-reverse。
flex-wrap, 控制flex子项整体是单行显示还是换行显示。nowrap / wrap / wrap-reverse。
flex-flow,是上两个属性的缩写。
水平对齐属性,justify-content,控制着整体的水平对齐方式。normal / flex-start / flex-end / flex-center / space-between / space-around / space-evenly。
垂直对齐属性,align-items,决定了每一个子项在交叉轴上的对齐方式。
垂直对齐属性,align-content,将所有子项作为一个整体进行垂直对齐设置。
项目属性
容器内的元素可以使用order/flex-grow/flex-shink/flex-basis/align-self
。order数值越小,项目越靠前。
flex-grow 规定了容器剩余空间多余的时候的分配规则,默认为0,多余空间不分配。
flex-shrink 规定了容器剩余空间不足时侯的分配规则,默认都为1,空间不足时要收缩,设为0时不缩小,负值无效。
flex-basis 表示 flex 子项分配到的基础尺寸,默认为auto,即项目的本来大小,子项的最终尺寸表现为最大内容宽度,子项内的文字不会换行。flex-basis: 0%,表现为内容的最小宽度,也就是会换行。
flex是 flex-grow / flex-shrink / flex-basis 的简写。当缺省声明 flex 属性的时候,其他值不一定是默认值。flex: 1
等于flex: 1 1 0%
,flex: 1 2
等于flex: 1 2 0%
,flex: 200px
等于flex: 1 1 200px
。不是使用的默认值。
align-self,表示每一个子项自身的垂直对齐方式,这是与 align-items 唯一的区别。
flex 可以轻松实现两栏、三栏布局。
父容器设置flex容器,左孩子设置flex:0 0 200px
,不放大也不缩小,占据固定的位置,右孩子设置flex:1 1
,有多的剩余空间就放大,剩余空间不足就缩小。
.container { display: flex; }
.left {
flex: 0 0 200px;
order: 1;
background-color: yellow;
}
.content {
flex: 1 1;
order: 2;
background-color: blue;
}
<div class="container">
<div class="left"></div>
<div class="content"></div>
</div>
父容器flex。左孩子flex:0 0 200px; order: 1
。中间部分flex:1 1; order: 2
。右孩子flex: 0 0 300px; order: 3
。
.container { display: flex; }
.left {
flex: 0 0 200px;
order: 1;
background-color: blue;
}
.content {
flex: 1 1;
order: 2;
background-color: yellow;
}
.right {
flex: 0 0 100px;
order: 3;
background-color: pink;
}
<div class="container">
<div calss="left"></div>
<div calss="content"></div>
<div calss="right"></div>
</div>
浮动布局
先布局左列、右列、然后是中间内容。包含元素设置相对定位。左列、右列固定宽度,占据左边和右边。中间内容不设置 width,自动填充剩余宽度,设置左右外边距。
两栏
.left {
float: left;
width: 200px;
background-color: yellow;
}
.content {
margin-left: 200px;/* 不设置宽度,左外边距设置固定值 */
background-color: blue;
}
<div>
<div class="left"></div>
<div class="content"></div>
</div>
三栏
.parent { position: relative; }
.left {
float: left;
width: 200px;
background-color: blue;
}
.right {
float: right;
width: 300px;
background-color: yellow;
}
.content {
margin-left: 200px;
margin-right: 300px;
background-color: pink;
}
<div class="parent">
<div class="left"></div>
<div class="right"></div>
<div class="content"></div>
</div>
grid网格布局
CSS3
新特性,主要用来进行布局,是一个二维的网格布局方式,可以对行和列进行处理。设置元素的display
属性为grid/line-grid
,将元素设置为网格容器。网格容器元素中的子元素叫做网格项目。通过设置容器属性和项目属性对网格进行调整。容器属性中,grid-template-columns/grid-template-rows
设置行宽行高;grid-row-gap/grid-colunm-gap/grid-gap
属性用来设置行间距和列间距;grid-auto-flow
设置网格容器中项目的排序顺序;justify-content/align-content-place-content
设置容器内容的水平垂直位置。项目属性中,justify-selft/align-self/place-self
设置单元格内容的水平垂直位置。
grid
可以用于居中布局、两列布局、三列布局。浏览器总体兼容性还可以,手机端不行。
响应式布局方案
元素的响应式可以将元素的宽高设置vw/vh
。图片的响应式可以使用display: inline-block;max-width: 100%;height: auto
来实现图片随容器的大小进行缩放,或者是background-image
来将图片设为元素的背景,让元素响应式就行。还可以通过display:flex|grid
对容器内元素进行响应式布局。还有些常见的响应式布局方案有CSS3 Meida Query
加rem/vw/vh
;或者UI
库提供的栅格系统。
圣杯布局
策略很简单。div#container有设置成两边固定宽度,中间自适应的。接下来的技巧就是把左侧列放到container的左侧padding上,右侧列放到container 的右侧padding上,中间让center列自动填充container就好了。
两栏
子元素用float:left
,左孩子width:100%
,右孩子设置固定宽度,并margin-left:负的固定宽度
。html/body/div
元素设置为100%
。
.container { padding: 0 0 0 300px; }
.content {
float: left;
width: 100%;
}
.left {
float: left;
width: 300px;
margin-left: -100%;
position: relative;
left: -300px;
background-color: yellow;
}
<div class="container">
<div class="content"></div>
<div class="left"></div>
</div>
三栏
父容器使用padding
设置right/left
的内边距。中间栏设置向左浮动,宽度100%
。左边设置固定宽度,float
向左,margin-left
向左负100%
,相对定位向左负固定宽度。右边栏设置固定宽度,向左浮动,左内边距负固定宽度,相对定位,向右负固定宽度。
.container2 {
padding: 0 400px 0 300px;
height: 100%;
}
.content2 {
float: left;
width: 100%;/* 自适应宽度 */
background-color: pink;
}
.left2 {
width: 300px;
float: left;
margin-left: -100%;/*不换行*/
position: relative;
left: -300px;/* 填充容器的内边距 */
background-color: blue;
}
.right2 {
width: 400px;
float: left;
margin-left: -400px; /*不换行*/
position: relative;
right: -400px;/* 填充容器的内边距 */
background-color: green;
}
<div class="container2">
<div class="content2">中间内容</div>
<div class="left2">左边</div>
<div class="right2">右边</div>
</div>
双飞翼三栏布局
中间元素自适应宽度,然后设置左列、右列。利用浮动,让块级元素不换行。左列、右列宽度固定,填充中间元素的内边距。
中左右设置左浮动,中设置100%宽度,中再包裹一个div作为内容部分,内容部分设置左右固定外边距。左栏固定宽度,左外边距负100%。右栏固定宽度,左外边距负固定宽度。
.mid {
float: left;
width: 100%;
background-color: blue;
}
.mid .content {
margin-left: 300px;
margin-right: 300px;
}
.left {
float: left;
width: 300px;
margin-left: -100%;/* 不换行,移动到中间内容的做内边距,填充中间元素的内边距 */
background-color: yellow;
}
.right {
float: left;
width: 300px;
margin-left: -300px;/* 填充中间元素的内边距 */
background-color: pink;
}
<div>
<div class="mid">
<div class="content"></div>
</div>
<div class="left"></div>
<div class="right"></div>
</div>
字节跳动最爱考的前端面试题:CSS 基础 - 掘金 (juejin.cn)
定位机制
正常文档流、float浮动、position定位
。
文档流
文档流是一种规则,在解析HTML时,元素会按照从上到下,从左到右的顺序占据页面的位置。而有的元素使用了float/position
定位属性,会脱离文档流,也就不会占据文档流中的位置。
float
浮动元素脱离文档流,会使不支持宽高的元素支持宽高,浮动元素没有设置宽度的话会由内容撑开,浮动元素遇到父级元素边框和相同浮动元素时停止。
position
规定了元素在页面中的定位类型。使用 top、bottom、left 和 right 属性定位的。但是,除非首先设置了 position 属性,否则这些属性将不起作用。根据不同的position
值,它们的工作方式也不同。
div.static {
position: static;
border: 3px solid #73AD21;
}
position: static;
HTML 元素默认情况下的定位方式为 static(静态)。静态定位的元素不受 top、bottom、left 和 right 属性的影响。position: static; 的元素不会以任何特殊方式定位;它始终根据页面的正常流进行定位:
div.static {
position: static;
border: 3px solid #73AD21;
}
position: relative;
元素相对于在文档中正常的位置进行定位,半脱离文档流
。设置相对定位的元素的 top、right、bottom 和 left 属性将导致其偏离其正常位置进行调整,元素在文档流中依然占据着位置。其余内容可能会超出浏览器显示区域。
div.relative {
position: relative;
left: 30px;
border: 3px solid #73AD21;
}
position: fixed;
元素是相对于视口定位的,这意味着即使滚动页面,它也始终位于页面中同一位置。 脱离了文档流,不占据页面位置。
div.fixed {
position: fixed;
bottom: 0;
right: 0;
width: 300px;
border: 3px solid #73AD21;
}
position: absolute;
元素相对于最近的已定位(除了static)元素进行定位。最近的元素都没有定位的话,就将使用文档主体(body),与fixed不同,会随页面滚动一起移动。
div.relative {
position: relative;
width: 400px;
height: 200px;
border: 3px solid #73AD21;
}
div.absolute {
position: absolute;
top: 80px;
right: 0;
width: 200px;
height: 100px;
border: 3px solid #73AD21;
}
position: sticky;
元素根据用户的滚动位置进行定位。粘性元素根据滚动位置在相对(relative)和固定(fixed)之间切换。起先它会被相对定位,直到在视口中遇到给定的偏移位置为止,然后将其“粘贴”在适当的位置(比如 position:fixed)。
注意:Internet Explorer、Edge 15 以及更早的版本不支持粘性定位。 Safari 需要 -webkit- 前缀(请参见下面的实例)。您还必须至少指定 top、right、bottom 或 left 之一,以便粘性定位起作用。
在到达其滚动位置时,sticky 元素将停留在页面顶部(top: 0)。
div.sticky {
position: -webkit-sticky; /* Safari */
position: sticky;
top: 0;
background-color: green;
border: 2px solid #4CAF50;
}
left / top / right / bottom
将元素移动到到他包含元素的指定距离处。比如 left 将元素移动到他包含元素向右 xxx 像素的位置。百分比根据包含元素来计算。
重叠元素
在对元素进行定位时,它们可以与其他元素重叠。
z-index 属性指定元素的堆栈顺序(哪个元素应放置在其他元素的前面或后面),值越大,显示顺序就越靠前。元素可以设置正或负的堆叠顺序:
img {
position: absolute;
left: 0px;
top: 0px;
z-index: -1;
}
具有较高堆叠顺序的元素始终位于具有较低堆叠顺序的元素之前。
注意:如果两个定位的元素重叠而未指定 z-index,则位于 HTML 代码中最后的元素将显示在顶部。
层叠上下文
先说说层叠顺序
,这和层叠上下文
是不同的概念。在HTML中,同层的元素可能会重合,层叠顺序就是为了说明同层的两元素重合时谁覆盖谁的问题。同层元素为块级元素、行内块元素、行内元素,都是靠后的元素覆盖前面的元素。
【css】一篇通俗易懂的CSS层叠顺序与层叠上下文讲解 - 知乎 (zhihu.com)
而层叠上下文
指的是将页面看成三维的空间,Z轴
指向用户,将页面元素在z轴
划分为一个个平面空间,描述的是平面空间之间的覆盖关系。而层叠顺序
是在同一个层叠上下文
中普通元素谁覆盖谁的说明。html标签
会产生根层叠上下文,还有position属性
等很多属性(display: flex | line-flex、opacity小于1、transform不为none、filter不为none
)也会产生层叠上下文,但是都包含在根层叠上下文中。块级盒、浮动盒、行内盒、z-index为正为负为0,都会产生层叠上下文。
什么是z-index,有什么用?
z-index
可以用来改变层叠上下文元素间的覆盖关系。只能在用了position
属性的元素中才会生效。
绘制
颜色和形状
color,文字颜色。background-color,背景颜色。rgb 颜色,用 0 - 255 的数字表示一种颜色,正好占据一个字节,一个rgb颜色就占据三个字节。在 RGB 表示法中,三色数值最大表示白色,三色数值为 0 表示黑色。
CSS 中的很多属性还会产生形状,比如我们常见的属性:border、box-shadow、border-radius。这些产生形状的属性非常有趣,我们也能看到很多利用它们来产生的 CSS 黑魔法。然而,最好将仅仅把它们用于基本的用途,把 border 用于边框、把阴影用于阴影,把圆角用于圆角,所有其它的场景,都有一个更好的替代品:datauri+svg。
文字
交互
transforms 属性可以调用移动、旋转、缩放、倾斜方法,实现一些效果。
translate 方法,将元素从当前位置移开,移开的距离就是参数的大小。百分比是根据包含元素计算的。
动画
和动画相关的属性有两个,animation 和 transition。
animation 的基本用法,实际上 animation 分成六个部分:
animation-name,动画名称,这是一个 keyframes 类型的值。
animation-duration,动画的时长。
animation-timing-function,动画的时间曲线。
animation-delay,动画开始前的延迟。
animation-iteration-count,动画的播放次数。
animation-direction,动画的方向。
@keyframes mykf {
0% { top: 0; }
50% { top: 30px; }
75% { top: 10px; }
100% { top: 0; }
}
div {
animation: mykf 5s infite;
}
在 @keyframes 规则里定义动画执行的关键帧来控制动画的变化。关键帧是用 animation-timing-function 作为时间曲线的。
transition,有四个部分:
transition-property,要变换的属性。
transition-duration,变换的时长。
transition-timing-function,时间曲线。
transition-delay,延迟。
实际上,有时候我们会把 transition 和 animation 组合,抛弃 animation 的 timing-function,以编排不同段用不同的曲线。在 keyframes 中定义了 transition 属性,以达到各段曲线都不同的效果。
@keyframes mykf {
0% { top: 0; transition: top ease }
50% { top : 30px; transition: top ease-in; }
75% { top: 10px; transition: top ease-out; }
100% { top: 0; transition: top linear; }
}
贝塞尔曲线
贝塞尔曲线是一种插值曲线,它描述了两个点之间差值来形成连续的曲线形状的规则。一个量从一个值到变化到另一个值,如果我们希望它按照一定时间平滑地过渡,就必须要对它进行插值。
贝塞尔曲线是一种被工业生产验证了很多年的曲线,它最大的特点就是“平滑”。时间曲线平滑,意味着较少突兀的变化,这是一般动画设计所追求的。
中间有堆公式认证。浏览器中一般都采用了数值算法,其中公认做有效的是牛顿积分。
理论上,贝塞尔曲线可以通过分段的方式拟合任意曲线,但是有一些特殊的曲线,是可以用贝塞尔曲线完美拟合的,比如抛物线。
todo 纯粹用 JavaScript 来实现一个 transition 函数,用它来跟 CSS 的 transition 来做一下对比,看看有哪些区别。
其他交互
水平垂直居中
position+margin: auto
如果子元素没有设置宽高,会撑满父元素。
<style>
.father {
width: 500px;
height: 300px;
border: 1px solid black;
position: relative;
}
.son {
width: 100px;
height: 40px;
background: green;
position: absolute;
top: 0; left: 0; right: 0; bottom: 0;
margin: auto;
}
</style>
postition + margin: 负值
这种方式在父元素宽高发生变化时,仍能保持居中。但是必须知道子元素自身的宽高。
<style>
.father {
position: relative;
width: 500px;
height: 300px;
border: 1px solid black;
}
.son {
position: absolute;
top: 50%;
left: 50%;
margin-left: -50px;
margin-top: -20px;
width: 100px;
height: 40px;
background: green;
}
</style>
postition + transfrom
比position + margin:负值好的一点是不需要知道子元素的宽高。
<style>
.father {
position: relative;
width: 500px;
height: 200px;
background: red;
}
.son {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
width: 100px;
height: 100px;
background: green;
}
</style>
水平居中
行内元素
父元素设置text-align:center
。flex
布局,设置justify-content:center
。
块级元素
需要水平居中的块元素使用margin:0 auto
。
父元素为absolute
,元素使用relative
加上left: 50%;marign: -width/2
。
垂直居中
行内元素
元素设置line-height:height
。
多行文本可以设置display:table-cell;vertical-align:middle
。
块级元素
父元素设置flex
,justify-content/aligin-items
为center
。
子元素设置margin:auto
。
父元素设置为relative
,子元素可以设置position:absolute /left/top:50% /margin-left/margin-top:-width/2
。
隐藏元素
最常见的方式是visibility: hidden、display: none、opacity: 0
。visibility: hidden
,隐藏元素,但是dom元素仍存在于文档流中,触发重绘,不会被子元素继承。display: none
,从文档流中删除DOM元素,使其不占据页面位置,会触发回流,可以被继承。opacity: 0
,将元素透明度设置为0
,使元素隐藏起来,但是仍占据页面位置,可以被继承,可以通过点击触发事件。
设置height/width/margin/padding/border
为0
,元素不可见,DOM也不存在。position: absolute
,将元素移出可视区域。
display属性了解吗?display
可以设置元素在文档流中的显示类型以及元素内容的布局规则。可以设置属性值为block
,表现为块级盒子,inline
表现为行级盒子。可以设置属性值为flex/grid/table
等,将元素变为容器,对内部应用相应的布局规则。还可以设置为none
,将元素从页面中移除出去,使得内容不可见。
文本溢出的省略样式的实现
单行文本溢出省略
使用text-overflow: ellipsis 文本溢出时显示省略标记/white-space: nowrap 设置文字在一行显示,不换行/overflow: hidden 超出宽度时隐藏内容
属性。
多行文本溢出省略
CSS3新特性
CSS如何提升性能?
从css文件加载方面来说,可以减少文件的体积。选择器也是有讲究的,一般id选择器效率最高,然后是类选择器,元素选择器,并且尽量不要写过于具体的选择器,这样可以减少构建CSSOM树的递归深度。合理选择属性,避免频繁造成页面的回流。
(1 封私信 / 22 条消息) CSS 优化、提高性能的方法有哪些? - 知乎 (zhihu.com)
被问过
css 变量
使用 — 声明,var 函数中使用。
css 动画
css 布局
flex 两栏、三栏布局
css 样式隔离方案(百度二面、飞书商业变现一面)
bem,采用不同前缀的命名规则,容易出纰漏,对 css 结构设计解耦能力要求比较高。
css modules,依赖css-loader编译生成不冲突的选择器类名。
css in js,css和js 一起编码生成不冲突的选择器名,运行时开销,缺失完整的 css 能力。
预处理器,。
shadow dom,浏览器原生 css 沙箱支持,场景特定。
vue scoped,添加属性选择器,唯一属性名,只适用于vue框架。
bem
css 样式应用是全局性的,没有作用域可言。
bem css 命名规范提倡css样式解耦,避免使用后代选择器这样的嵌套。但是命名过长。bem css命名规范,就是用组件名+标签名+修饰器名来命名css选择器,也就是这样一种命名格式,block-name_element-name—modifier-name,page-btn_btn—submit,表示page-btn这个组件下button标签类名submit的选择器。文件系统要求同一目录下的文件名唯一,这就保证了组件之间不会冲突。
block name,每个块名必须是唯一的,用于明确指出它所描述的是哪个块。可重复使用,也支持嵌套。
在使用块时,块不应影响其环境,也就是不应设置块的外部几何形状或位置
。需要注意的是块应该是独立的,不能限定上下文。当在页面中添加,删除,或者是移动某个块时,不需要对块进行修改。
<div class="top">
<form class="search-form">搜索</form>
</div>
<div class="bottom">底部</div>
<style>
/*错误写法*/
.top .search-form{...} /*表示只有在top块内的search-form块才会应用此CSS样式*/
.bottom{}
/*正确写法*/
.top {}
.bottom {}
.search-form {}
</style>
有3个块,分别是top、search-form、bottom,块之间可以嵌套。在实际应用中,需要保证每个块都是独立的。
element name,元素是块的组成部分,是依赖上下文的。元素在所属的块中指定位置时,才能表现出应有的功能。
元素之间可以彼此嵌套,一个元素总是一个模块的一部分,而不是另一个元素的一部分,这意味着元素的名称不能被定义为 block elem1 elem2 这样的层次结构。
<div class="top">
<!--top块中的search-for块-->
<form class="search-form">
<!--在search-form块中的input元素-->
<input class="search-form__input">
<!--在search-form块中的button元素-->
<button class="search-form__button">搜索按钮</button>
</form>
</div>
<style>
.search-form .search-form__input{...}
.search-form .search-form__button{...}
</style>
modifier,修饰器可以和块、元素一起工作。通常是外观或行为有些许改变,这时可以使用修饰符来处理。
注意,修饰符不能单独使用,而且必须包含对应的块名或元素名。这样要求的目的有3个,第一,每一个块都会建立独立的命名空间,可以有效的减少命名冲突
。第二,可以显示说明该修饰符所对应的块或元素,防止混淆
。第三,不包含块名或元素名,使用修饰符就必须使用组合选择器,应该避免增加样式权重
使其难以覆盖。
并不是每个地方都应该使用BEM命名方式,只有当需要明确关联性的模块关系时,才需要使用 BEM 格式。
用混合拆分样式
在BEM中,位置和布局样式通过父级块来进行设置。这就需要通过混合组合块与元素,组合多个实体(块、元素、修饰符都被称作 BEM实体)的表现与样式,同时不耦合代码。
<!-- top 块 -->
<div class="top">
<!-- search-form块混合top块的search-form元素 -->
<form class="search-form top__search-form">搜索</form>
</div>
这样就通过混合的方式把位置样式从块中剥离了,可以在.top__search-form
中设置表单的位置或浮动等样式,保持了 search-form
块的样式独立,对其完整样式代码进行了解耦。
因此在使用 BEM 命名时需要格外注意遵循它的工作方式:
- 不在块里设置位置、布局相关的样式,
只设置基本样式
。 - 通过
混合的方式
,在作为父级块的元素时设置布局样式。 适时拆分
元素为独立的块,解耦样式并形成新的命名空间。
css modules
css modules 不是官方标准,也不是浏览器特性,只是在构建中用 css-loader 对CSS选择器重新构造唯一的名字来限定作用域的一种方式(类似于命名空间)。
css-loader 开启 modules,只需要这样,loader: "style-loader!css-loader?modules&localIdentName=[name]__[local]-[hash:base64:5]"
。css modules 只会给想要局部化的选择器重构名字,:global(.title){...}
,用 :global 前缀包含的 title 类选择器不会重构名字。
这样的方式结合了 js 的模块化能力。目前 css 模块化有两种主要思路,第一种是用 js 来管理样式依赖,比如 css modules,另一种是用 js、json 来写样式,比如 react-style、jsxstyle。
可以完全解决 css 全局污染问题,学习成本也低。只对类名选择器重构名字,不会改变其他类型的选择器。但是想用样式的时候需要引入样式文件,比如 style.css,并且使用固定的语法来使用样式style.title
。没有变量,需要结合预处理器。
todo 不能用变量语法?预处理器是啥?
css in js
核心思想就是将 css 写进组件的模板里面,用 js 写样式,放在模板元素的 style 属性里。这样做的好处是 css 样式与组件绑定在了一起,很容易实现组件间的样式隔离。
然而这一种方式,违背了关注点分离原则。
现在实现了css-in-js的库有很多,从实现方法上区分大体分为两种:唯一css选择器,代表库:styled-components,内联样式(Unique Selector VS Inline Styles)。
styled-components 通过字符串模板的方式定义选择器样式。radium 则是通过对象定义的方式定义 style 属性值。
预处理器
CSS 预处理器是一个css扩展语法的程序,绝大多数CSS预处理器会增加一些原生CSS不具备的特性。代码混合,嵌套选择器,继承选择器。这些特性让CSS的结构更加具有可读性且易于维护。
todo 还能样式隔离,我确实没用过
shadow dom
是一组 js api,shadow dom 的样式仅作用于 shadow 元素下。
浏览器原生支持。缺点是浏览器兼容性不够好,只对一定范围内的 dom 结构起作用。
Vue scoped
在 style 标签里添加 scoped 属性。通过 vue-style-loader 在构建时进行转换,给 style 标签里的选择器加上属性选择器,选择器应用到的标签也添加上相应的属性名。这样就实现了组件间样式隔离。
父组件如果想修改子组件的样式,需要用到深度选择器>>>
,如果用了预处理器,还用不了深度选择器,得用/deep/
或::v-deep
。
https://juejin.cn/post/7064246166396862477