仍然是最近几个月闲来无事,想着自从毕业那几年之外,已经好久没有沉下心来好好看看css了,趁着年底的这几个月,我又重新找了本《深入解析CSS》来回顾了一下CSS的一些知识点,并且在阅读的过程中,收集了一下书中的重点知识以及根据自身疑问找到的一些资料,用以后续可能的回顾。
学习目标
其实css本身来说,日常工作中,你能遇到的最常见的功能就是:
- 工作方面
- 布局
- 响应式
- 动画
- 常见css技巧
- 内功部分
- 模块化css(大型应用css)
现在的css,什么颜色、字体、边框啥的东西,说实话,都不会再有什么要求了,连所谓的什么选择器、样式权重啥的也都不重要了(或者说不被重视)。其实对于目前的css来说,有了AI查询之后,基本上很多疑问其实可以找到答案,那么现在还学习css的原因是什么呢?
- 为了效率,在自己开发编写页面时,可以快速、合理的布局,按照基本上比较好的方式编写页面结构和样式编写。
- 动画的理论,在编写动画或者过渡时,可以没有那么生涩,知道大致该怎么写,为啥要这么写(可以更好的配合AI)
- 在面对一些需求是,也能够有足够的知识储备,例如响应式
- 了解一些技巧,可以快速在常见、特定的需求时,快速找到比较合适的办法,而不是找资料、思考半天才动手,影响效率。
前言
- 精通 CSS,难在需要知道在何时做何事。或者说怎么才能写出最佳的写法,达到自己想要的效果。
- 精通CSS,需要学习CSS的所有功能。了解得越多,对CSS的感受就越自然。
练习得越多,就越能轻松地想到完美的布局和定位方法。读得越多,就越能从容地应对任何设计 - 知道css有什么功能,还要知道使用过后它会产生什么效果和行为(要求比较高)
- 学习css,以便在一般的问题出现时,能够知道大致是为什么,可以尝试如何解决
- MDN网站其内容也非常的丰富和全面,是一个非常好的参考资料:MDN CSS
你可以做任何想做的事情,只要事情按照你的预期发展就行。CSS没有任何硬性规定,但正因为全靠自己发挥,没有衡量标准告诉你做得好不好,所以你需要格外小心。
基础部分
层叠、优先级和继承
- 学习传统编程中遇到问题时,你通常知道该搜索什么(比如,“如何找到一个数组里类型为x的元素”)。在CSS中,却很难将问题提炼成一句话。即使可以,答案一般也是“这得看情况”。
- 好的解决办法通常取决于具体场景,以及你希望以多大粒度
处理各种边缘情况。 - css中的c是层叠的意思:层叠样式表
层叠
- CSS本质上就是声明规则,即在各种条件下,我们希望产生特定的效果。如果某个元素有这个类,则应用这些样式。如果X元素是Y元素的子节点,则应用那些样式。浏览器会根据这些规则,判断每个规则应该用在哪里,并使用它们去渲染页面。
- CSS开发很重要的一点就是以可预测的方式书写规则
- 包含冲突声明的规则集可能会连续出现,也可能分布在样式表的不同地方。无论如何,对于你的HTML来说,它们都选中了相同的元素。
- 假设三个规则(其实就是用3种方式给同一个元素设置同一个属性的不同属性值)集尝试给标题设置不同的字体,哪一个会生效呢?浏览器为了解决这个问题会遵循一系列规则,因此最终的效果可以预测。
层叠指的就是这一系列规则。它决定了如何解决冲突,是CSS语言的基础。- 你可以简单看做一个元素在应用css样式时,哪些样式会被应用上去,如果冲突时,该如何选择哪一个具体的样式(选择器优先级权重)
- 层叠指的就是这一系列规则。它决定了如何解决冲突,是CSS语言的基础。
- 当声明冲突时,层叠会依据三种条件解决冲突(从高到低)
- (1) 样式表的来源:样式是从哪里来的,包括你的样式和浏览器默认样式等。你的样式会覆盖浏览器默认样式
- 浏览器自身会为元素添加一些默认样式表
- (2) 选择器优先级:哪些选择器比另一些选择器更重要。
- 行内样式大于选择器样式
- (3) 源码顺序:样式在样式表里的声明顺序。
- (1) 样式表的来源:样式是从哪里来的,包括你的样式和浏览器默认样式等。你的样式会覆盖浏览器默认样式
- 这些规则让浏览器以可预测的方式解决CSS样式规则的冲突。
- 标记了!important的声明会被当作更高优先级的来源
- 层叠独立地解决了网页中
每个元素的样式属性的冲突,只有显示编写的样式才会覆盖默认样式 - 两条经验法则
- 在选择器中不要使用ID。就算只用一个ID,也会大幅提升优先级。
- 不要使用!important。它比ID更难覆盖,一旦用了它,想要覆盖原先的声明,就需要再加上一个!important,而且依然要处理优先级的问题。
继承
- 浏览器遵循三个步骤,即来源、优先级、源码顺序,来解析网页上每个元素的每个属性。如果一个声明在层叠中“胜出”,它就被称作一个层叠值。
- 如果一个元素上始终没有指定一个属性,这个属性就没有层叠值。其实简单点就是有没有给它显式的加某个样式
如果一个元素的某个属性没有层叠值,则可能会继承某个祖先元素的值。- 但不是所有的属性都能被继承。默认情况下,只有特定的一些属性能被继承,通常是我们希望被继承的那些。
- 将属性加到body上会在整个网页上生效。而将属性加到特定元素上,则只会被它的后代元素继承。如果是能够继承的属性会顺序传递给后代元素,直到它被层叠值覆盖。
特殊值
- 有两个特殊值可以赋给任意属性,用于控制层叠:
inherit和initial。 - 使用inherit关键字
- 有时,我们想用继承代替一个层叠值。这时候可以用inherit关键字。可以用它来覆盖另一个值,这样该元素就会继承其父元素的值。
- 还可以使用inherit关键字
强制继承一个通常不会被继承的属性,比如边框和内边距。通常在实践中很少这么做,但是在介绍盒模型时,会看到一个实际用例(即用root设置根元素的盒模型box-sizing: border-box;,然后用*和::before、::after给所有元素和伪类使用box-sizing: inherit;来继承根元素的盒模型设置。)有没有用不说,感觉没啥用
- 使用initial关键字
- 有时,你需要撤销作用于某个元素的样式。这可以用initial关键字来实现。
- 如果将initial值赋给某个属性,那么就会有效地将其重置为默认值,这种操作相当于硬复位了该值。
- 这里的默认值,可能是浏览器给予的默认样式的值,但是可能在不同的浏览器中,效果也会不同,所以貌似也很少用。
- 注意:声明display: initial等价于display: inline。不管应用于哪种类型的元素,它都不会等于display: block(即使默认的div的display的属性的初始值是block)这是因为
initial重置为属性的初始值,而不是元素的初始值。inline才是display属性的初始值。也就是inline不会根据不同的元素而值不同。
简写属性
- 简写属性是用于同时给多个属性赋值的属性。
- 大多数简写属性可以省略一些值,只指定我们关注的值。但是要知道,
这样做仍然会设置省略的值,即它们会被隐式地设置为初始值。这会默默覆盖在其他地方定义的样式。- 也就是只要设置了简写属性,那么即使你没有设置其中的部分属性,那么它仍然会存在一个初始值,等同你显示设置了这些属性为初始值,此时会覆盖其他其他地方的样式。
- 也就是说简写值在明确值后面,那么你即使没有显式设置那个明确的值,也会覆盖掉上一个明确的值
- 理解值的顺序
- 有很多属性的值很模糊。在这种情况下,值的顺序很关键。理解这些简写属性的顺序很重要。
- 上、右、下、左
- 当遇到像margin、padding这样的属性,还有为元素的四条边分别指定值的边框属性时,开发者容易弄错这些简写属性的顺序。这些属性的值是按顺时针方向,从上边开始的。
- 水平、垂直
- 还有一些属性只支持最多指定两个值,这些属性包括background-position、box-shadow、text-shadow(虽然严格来讲它们并不是简写属性)。这些属性值的顺序跟padding这种四值属性的顺序刚好相反。
两个值通常代表了一个笛卡儿网格。笛卡儿网格的测量值一般是按照x, y(水平,垂直)的顺序来的。第一个值是x,第二个值是y
- 如果属性需要指定从一个点出发的两个方向的值,就想想“笛卡儿网格”。如果属性需要指定一个元素四个方向的值,就想想“时钟”。
开发者工具
- 继承:inherited,如果在开发者工具中,看到
inherited from xxx表明这些样式是继承自哪些元素的。 - 浏览器的样式检查器显示了所检查元素的每个选择器,它们
根据优先级排列(从上至下)。在选择器下方是继承属性。元素所有的层叠和继承一目了然。
相对单位
- 人们最熟悉同时也最简单的应该是像素单位(px)。它是绝对单位,即5px放在哪里都一样大。而其他单位,如em和rem,就不是绝对单位,而是相对单位。相对单位的值会根据外部因素发生变化。
- 开发人员,即便是经验丰富的CSS开发人员,通常也不愿意使用相对单位,包括经常提到的em。em值变化的方式使其难以预测,不如像素简单明了。
- CSS为网页带来了后期绑定(late-binding)的样式:直到内容和样式都完成了,二者才会结合起来。
相对单位的好处
- 在很长时间里,网页设计者通过聚焦到“像素级完美”的设计来降低这种复杂性。他们会创建一个紧凑的容器,通常是居中的一栏,大约800px宽。然后再像之前的本地应用程序或者印刷出版物那样,在这些限制里面进行设计。(因为web浏览器的宽高可以修改)
- 等到智能手机出现后,开发人员再也无法假装每个用户访问网站的体验都能一样。不管我们喜欢与否,都得抛弃以前那种固定宽度的栏目设计,开始考虑响应式设计。
- 响应式——在CSS中指的是样式能够根据浏览器窗口的大小有不同的“响应”。这要求有意地考虑任何尺寸的手机、平板设备,或者桌面屏幕。
- 相对单位就是CSS用来解决这种抽象的一种工具。我们
可以基于窗口大小来等比例地缩放字号,而不是固定为14px,或者将网页上的任何元素的大小都相对于基础字号来设置,然后只用改一行代码就能缩放整个网页。 - 相对值是等比缩放的:也就是如果其基准变了,那么其依赖该基准的所有相对值,都是
同样的等比缩放。
em
注意:相对单位的em已经基本上不用了。移动端用视窗,而pc端则用媒体查询。因为目前的响应式设置不是简单的缩放就满足了,而是基于不同尺寸的设备应用不同的排版
- em是最常见的相对长度单位,适合基于特定的字号进行排版。在CSS中,1em等于当前元素的字号,其准确值取决于作用的元素。
- 例如当前元素的字号是16px(字号会被继承),那么1em的大小实际被转换为px就是16px,这就是em转换的规则。
- 浏览器会根据相对单位的值计算
出绝对值,称作计算值(computed value)。 - 根元素上的em是相对于浏览器字体默认值的,默认值通常是16px
- 当设置padding、height、width、border-radius等属性时,使用em会很方便。这是因为当元素继承了不同的字号,或者用户改变了字体设置时,这些属性会跟着元素均匀地缩放。
- 当前元素的字号决定了em。但是,如果声明font-size: 1.2em,会发生什么呢?一个字号当然不能等于自己的1.2倍。实际上,这个font-size是根据继承的字号来计算的。
- em的复杂之处在于
同时用它指定一个元素的字号和其他属性。这时,浏览器必须先计算字号(使用继承值),然后使用这个计算值去算出其余的属性值(使用当前元素的字号)两类属性可以拥有一样的声明值,但是计算出的绝对值不一样。 - 最大的一个原因在于你不知道当前元素的字体大小是多少,他可能是基于继承的,也可能是被其他样式规则覆盖的,或者继承的是一个用em写的字体大小,你还要额外计算处具体的绝对值。其心智负担非常重。
- 所以你使用em时,要设置font-size时,要额外注意非常多的东西。
- 但是其本质的规则并不复杂,你只需要知道一点,当前元素的font-size是多少,为什么是这么个值就行。
rem
- 根节点(指html)是所有其他元素的祖先节点。根节点有一个伪类选择器(:root),可以用来选中它自己。这等价于类型选择器html
- rem不是相对于当前元素,而是相对于根元素的单位。不管在文档的什么位置使用rem,都是基于根节点的字号大小来计算出绝对值
- 视口——浏览器窗口里网页可见部分的边框区域。它不包括浏览器的地址栏、工具栏、状态栏
视口相对单位
- rem的缩放本质,其实也是基于视口宽度大小来更新html的font-size大小(通常是等比的)
- 而视口单位等同于帮我们将font-size的更新给省略了,而是让我们直接基于视口宽度来作为基准设置我们的相对单位。
- 50vw等于视口宽度的一半,25vh等于视口高度的25%。vmin取决于宽和高中较小的一方,这可以保证元素在屏幕方向变化时适应屏幕。在横屏时,vmin取决于高度;在竖屏时,则取决于宽度。
实际书写时的单位转换
- 相对值有个问题在于,在你实际书写时,其设计稿本身不是绝对值,而是相对值的,那么如果你在书写时将绝对值转换为相对值进行书写时,那么就需要自己去计算出绝对值到相对值的转换,这个会有比较大的心智负担。
- 通常来说,我们可以直接书写绝对值(根据设计稿)然后再通过打包的形式,根据一个规则将代码中的绝对值转换为相对值。
- 例如我的设计稿是按照375的宽度去编写的(或者750),那么我们在将代码中的px绝对值单位转换为相对值单位(例如rem)时,就可以使用类似这种算法:
x / 37.5,你要确定你设定的基准1rem等于多少px像素,假设你定义的基准是:1rem等于37.5px,那么假设你要设置100px,那么实际就需要写为(100 / 37.5)rem,那么当你的页面宽度为375px大小时,其html的font-size为37.5px,该元素的实际大小为100px。当页面宽度缩放时,html的font-size也会等比减少,此时rem本身的尺寸也会等比减少。边框时同理。- 要注意,通常来说,字体有一个最小值,但是这里的html的font-size本身不会被应用于字体显示,而是一个尺寸的基础比例。
- 不过对于真正的字体设置,你需要考虑是用rem还是绝对值,因为通常字体可能有一个最小值(是10还是12来着)也可能没有。不过这种场景下,阅读本身也没有意义了,此时,大概率的情况是会限制html的font-size的最小值。即使产生滚动条,也好比无法阅读。
- Chrome 从118 开始,为所有语言解除 这一限制
- 不过现代开发中,编辑器通常有一些插件,会在你实际编写100px时,自动转换为rem,或直接基于构建打包去转换即可。
单位使用考量
- 在响应式网页中,需要习惯“模糊”值。1.2em到底是多少像素并不重要,重点是它比继承的字号要稍微大一点。或者相对于xxx是怎么样的。
- 通常对于单位来说,不要混合多个相对单位,通常只需要一个相对单位(确定你的项目是基于什么样的单位做的响应式的),然后再配合上一下px单位
calc()函数内可以对两个及其以上的值进行基本运算。当要结合不同单位的值时,calc()特别实用。- 无单位的数值和行高
- 有些属性允许无单位的值(即一个不指定单位的数)。支持这种值的属性包括line-height、z-index、font-weight(700等于bold,400等于normal,等等)。任何长度单位(如px、em、rem)都可以用无单位的值0,因为这些情况下单位不影响计算值,即0px、0%、0em均相等。
- 一个无单位的0只能用于长度值和百分比,比如内边距、边框和宽度等,而不能用于角度值,比如度,或者时间相关的值,比如秒。
- 当一个元素的值定义为长度(px、em、rem,等等)时,子元素
会继承它的计算值。当使用em等单位定义行高时,它们的值是计算值,并将计算值传递到了任何继承子元素上。如果子元素有不同的字号,并且继承了line-height属性,就会造成意想不到的结果,比如文字重叠。- 使用无单位的数值时,继承的是声明值,即在每个继承子元素上
会重新算它的计算值。
- 使用无单位的数值时,继承的是声明值,即在每个继承子元素上
css变量
变量的好处
变量的主要的作用是可以提取通用的值,以便在不同的地方引用(代码复用),这样可以在后续的修改时统一处理。在此基础上,由于可以在非常简单的基于变量控制不同地方的样式,对于需要动态切换的需求,例如主题,变量可以比较简单就实现:统一定义,统一修改
什么是css变量
- 新规范里的CSS变量有本质上的区别,它比任何一款预处理器的变量功能都多。
- 在样式表某处为自定义属性定义一个值,作为“单一数据源”,然后在其他地方复用它。这种方式特别适合反复出现的值,比如颜色值。
- 如果var()函数算出来的是一个非法值,对应的属性就会设置为其初始值。
- 自定义属性的声明能够层叠和继承:可以在多个选择器中定义相同的变量,这个变量在网页的不同地方有不同的值。这是其非常有意义的一点。
- 继承可以使其在后代的样式中引用
- 层叠,则是可以根据需求,在不同的地方覆盖其变量,设置为新的值
- 通常会使用root选择器的规则集中定义变量。这很重要,如此一来这些值就可以提供给根元素(整个网页)下的任何元素。或者配合属性选择器和date-xxx来实现切换。或者内部自行覆盖这些变量。
- 使用js修改css变量
- 因为
css变量本质也是一个css样式定义,和color这种一样,他可以给根节点的行内样式设置一个css变量,来覆盖和修改css变量的值。这是最简单的,虽然你也可以通过dom去修改,但是现代框架开发中几乎没有必要。
- 因为
盒模型
CSS中最让人头疼的两个问题:垂直居中和等高列(指并排的div吧)。真的吗?
- 水平居中:一条横线的中间
- 垂直居中:一条竖线的中间
盒模型和文档流是网页布局的基础知识。
盒模型说明
- 标准盒模型:width + padding + maring + border
- 怪异盒模型(IE盒模型):width + margin
- 盒子有内部显示(inner display type)和外部显示(outer display type)两种类型。
- 外部显示类:对外的布局行为
- 表示盒子的是否换行、width 和 height 属性是否起作用(行内盒子就不起作用)、内边距、外边距和边框会将其他元素从当前盒子周围“推开”等等行为
- 内部显示类型:它决定了盒子内元素的布局方式。
- block是默认行为:默认情况下,在没有任何其他指令的情况下,方框内的元素也会以标准流的方式布局,并表现为区块或行内盒子。
- flex就会改变内部元素的布局方式,将其修改为弹性盒模型。其内部的元素布局方式就不再遵循标准流的布局方式了。
- 总是全局设置
border-box: border-box,以便得到预期的元素大小。 - 你也许会担心通用选择器
(*)的性能问题。在IE6中,这个选择器超级慢,因此开发人员会避免使用它。现在不必担心了,因为现代浏览器都能很好地处理。
全局样式设置例子(部分):
1 | *, |
外边距
参考:MDN css盒模型
负外边距
- 不同于内边距和边框宽度,外边距可以设置为负值。负外边距有一些特殊用途,比如让元素重叠或者拉伸到比容器还宽
- 负外边距的具体行为取决于设置在元素的哪边:
- 如果设置左边或顶部的负外边距,元素就会相应地向左或向上移动,导致元素与它前面的元素重叠
- 如果设置右边或者底部的负外边距,并不会移动元素,而是将它后面的元素拉过来。给元素底部加上负外边距并不等同于给它下面的元素顶部加上负外边距。
- 元素拉伸:如果不给一个块级元素指定宽度,它会自然地填充容器的宽度。但如果在右边加上负外边距,则会把它拉出容器。如果在左边再加上相等的负外边距,元素的两边都会扩展到容器外面
- 负外边距并不常用,但是在某些场景下很实用,尤其是当创建列布局的时候。不过应当避免频繁使用,不然网页的样式就会失控。
外边距折叠
- 当顶部和/或底部的外边距相邻时,就会重叠,产生单个外边距。这种现象被称作折叠。
- 第一种:相邻兄弟之间的外边距折叠
- 当前后叠放两个段落时(其上下默认有1em的外边距),它们的外边距不会相加产生一个2em的间距,而会折叠,只产生1em的间隔。
- 折叠外边距的大小等于相邻外边距中的最大值。
- 所有相邻的顶部和底部外边距都会折叠到一起。如果在页面中添加一个空的、无样式的div(没有高度、边框和内边距),它自己的顶部和底部外边距就会折叠。
- 其实你可以看做在正常情况下(默认的正常文档流布局),所有元素的上下外边距都会尝试进行折叠,只不过通常的div外边距是0,被忽略了而已。
- 第二种:容器外部折叠(嵌套元素之间的上下外边距折叠)
- 即使两个元素不是相邻的兄弟节点也会产生外边距折叠。(即被嵌套元素的顶部外边距和其他元素的底部外边距进行折叠)
- 多个嵌套元素的上外边距会依次折叠,折叠出最大的值
- 只有上下外边距会产生折叠,左右外边距不会折叠
- 折叠外边距就像“个人空间”。如果在公交车站站着两个人,他们每个人都认为较为舒适的个人空间应为3英尺,那么他们就会乐意间隔3英尺,而不必间隔6英尺才让双方满意。
- 这也就是说可以给任何元素加上外边距,而不必担心它们前后的元素是什么。
- 关闭外边距的折叠方式
- 对容器使用overflow: auto(或者非visible的值),防止内部元素的外边距跟容器外部的外边距折叠。这种方式副作用最小。
- 在两个外边距之间加上边框或者内边距,防止它们折叠
- 当使用Flexbox布局时,弹性
布局内的元素之间不会发生外边距折叠。网格布局同理。 - 如果容器为浮动元素、内联块、绝对定位或固定定位时,外边距不会在它外面折叠。
- 当元素显示为table-cell时不具备外边距属性,因此它们不会折叠。此外还有table-row和大部分其他表格显示类型,但不包括table、table-inline、table-caption。
- 这些方法中有很多会改变元素的布局行为,除非它们能产生想要的布局,否则不要轻易使用。
- 负外边距也会折叠,选择离0远的值
元素高度问题
- 处理元素高度的方式跟处理宽度不一样。通常
最好避免给元素指定明确的高度,而是自行撑开(尤其是在文档流中)- 除非别无选择,否则不要明确设置元素的高度。先寻找一个替代方案。设置高度一定会导致更复杂的情况
- 普通文档流
- 指的是网页元素的默认布局行为。行内元素跟随文字的方向
从左到右排列,当到达容器边缘时会换行。块级元素会占据完整的一行,前后都有换行。
- 指的是网页元素的默认布局行为。行内元素跟随文字的方向
普通文档流是为限定的宽度和无限的高度设计的。内容会填满视口的宽度,然后在必要的时候折行。因此,容器的高度由内容天然地决定,而不是容器自己决定。- 当明确设置一个元素的高度时,内容可能会溢出容器。当内容在限定区域放不下,渲染到父元素外面时,就会发生这种现象。
- 百分比高度问题
- 用百分比指定高度存在问题。百分比参考的是元素容器块的大小,但是容器的高度通常是由子元素的高度决定的。这样会造成死循环,浏览器处理不了,因此它会忽略这个声明。
要想让百分比高度生效,必须给父元素明确定义一个高度。
- 用百分比指定高度存在问题。百分比参考的是元素容器块的大小,但是容器的高度通常是由子元素的高度决定的。这样会造成死循环,浏览器处理不了,因此它会忽略这个声明。
- min-height和max-height。你可以用这两个属性指定最小或最大值,而不是明确定义高度,这样元素就可以在这些界限内自动决定高度
布局方式
浮动
了解即可,基本上不会在使用浮动布局了。不过要实现将图片移动到网页一侧,并且让文字围绕图片的效果,浮动仍然是唯一的方法。
- 浮动能将一个元素(通常是一张图片)拉到其容器的一侧,这样
文档流就能够包围它。这种布局在报纸和杂志中很常见,因此CSS增加了浮动来实现这种效果。 - 浮动元素会
被移出正常文档流,并被拉到容器边缘。文档流会重新排列,但是它会包围浮动元素此刻所占据的空间。 - 这才是浮动的设计初衷,但是可惜,通常我们并不这么使用。
BFC
块级格式化上下文(block formatting context, BFC)。BFC是网页的一块区域(也是基于元素的),元素基于这块区域布局。
- 它决定了
块级元素如何渲染、如何与其他元素交互,同时也是解决布局问题(如浮动塌陷、边距重叠)的关键手段。 - BFC 是 Web 页面中独立的渲染区域,该区域
按照块级盒子的布局规则进行排版(例如flex或者grid就拥有自己的布局规则),且区域内的布局不会影响到外部元素,外部元素的布局也不会影响到区域内的元素。 - 简单来说,BFC 就像一个 “隔离的独立容器”,容器内部的元素和外部的元素相互隔绝。
- 以此可以避免margin边距合并、float的一些特性。
- BFC 会具有像最外层文档那样的行为——
它会在主布局中形成一个微型布局。BFC的重点在于独立性和隔离性,而不在于内部用什么布局方式(flex或者grid)
BFC的元素的特点。
- 包含了内部所有元素的上下外边距。它们不会跟BFC外面的元素产生外边距折叠。
- 包含了内部所有的浮动元素。
- 不会跟BFC外面的浮动元素重叠。
- 网页的根元素也创建了一个顶级的BFC。
简而言之,BFC里的内容不会跟外部的元素重叠或者相互影响。
给元素添加以下的任意属性值都会创建BFC(在该元素上):
- float: left或right,不为none即可。
- overflow:hidden、auto或scroll,不为visible即可。
- display:inline-block、flex、inline-flex、grid或inline-grid、table-cell、table-caption。拥有这些属性的元素称为块级容器(block container)。
- position:absolute或position: fixed。
- 在包含区块中使用 display: flow-root(或 display: flow-root list-item;组合)将创建一个新的 BFC,而
不会产生任何其他潜在的问题副作用。
BFC的使用场景
- 最常用:解决margin重叠,通常当你不想要某个margin重叠时(兄弟元素、嵌套元素)
- 不常用:解决浮动中的一些问题
- 例如浮动的高度塌陷问题
BFC 与 IFC/GFC/FFC 的区别
- 弹性/网格容器(display:flex/grid/inline-flex/inline-grid)建立新的弹性/网格格式化(FFC/GFC)上下文,除布局之外,它与区块格式化上下文类似。
- IFC:行内格式化上下文是一个网页的渲染结果的一部分。其中,各行内框(inline box)一个接一个地排列,其排列顺序根据书写模式(writing-mode)的设置来决定
- 对于水平书写模式,各个框从左边开始水平地排列
- 对于垂直书写模式,各个框从顶部开始水平地排列
- 盒模型不完全适用于参与行内格式化上下文。
块级盒子和BFC
当元素设置 display: block; 时,它本身并不会直接触发某种特定的格式化上下文(如 BFC/IFC),而是该元素会作为块级盒子(block-level box) 参与到包含它的父容器的格式化上下文中。具体的上下文类型由父容器的布局特性和元素自身的其他属性共同决定
- 块级元素 不等于 BFC,块级元素只是参与布局的 “角色”,而格式化上下文是布局的 “规则环境”
- display: block 本身不是 BFC 的触发条件,但如果给display: block的元素添加BFC 触发属性(如overflow: hidden、float: left、position: absolute等),则该元素会成为一个独立的 BFC 容器。
- 若display: block的元素被包含在display: flex(FFC)或display: grid(GFC)的容器中,那么该元素的display: block会被 Flex/Grid 布局覆盖,元素会作为flex 项 /grid 项参与 FFC/GFC 的布局规则,而非普通的块级布局。
- 根元素
<html>默认是display: block,且天然是整个文档的根 BFC 容器,所有页面内的块级元素最终都参与这个根 BFC 的布局。 - display: block 定义了元素的显示类型为块级元素,它具有块级盒子的核心特征:
- 独占一行(默认宽度撑满父容器);
- 支持设置 width、height、margin、padding 等盒模型属性;
- 是 CSS 中最基础的块级布局单元,
<div>、<p>、<h1>等默认都是 display: block。
总结:整个html是一个块级格式化上下文,其内元素默认按照默认布局文档流布局方式(水平)。对于默认的块级元素和行内元素,默认根据文档流自行对他们进行布局(行内元素从左到右、块级元素独占一行),普通块级元只是布局角色,没有特殊的布局规则(或者说仍然是默认的文档流布局规则)。当其中内部元素出现特殊的布局,即创建一个新的块级格式化上下文时,其内部按照自身的布局规则进行布局之外,还和外部其他元素进行布局上的隔离,互不影响。
这就是格式化上下文的作用
网格系统(不是grid布局)
或者说栅格布局:网格系统提供了一系列的类名,可添加到标记中,将网页的一部分构造成行和列。它应该只给容器设置宽度和定位,不给网页提供视觉样式,比如颜色和边框。需要在每个容器内部添加新的元素来实现想要的视觉样式。
- 大部分流行的CSS框架包含了自己的网格系统。它们的实现细节各不相同,但是设计思想相同:在一个行容器里放置一个或多个列容器。列容器的类决定每列的宽度。
- 要构建一个网格系统,首先要定义它的行为
- 通常网格系统的每行被划分为特定数量的列,一般是12个(或者24个),但也可以是其他数。每行子元素的宽度可能等于1~12个列的宽度
- 通常使用不同的类来标识列元素所占据的宽度。
- 列间隔
- 给每个网格列添加左右内边距,创造间隔。
如果不使用grid或者flex来实现一个网格系统,那么其中的列间隔该如何实现比较方便,或者能实现到什么程度?
- 使用浮动
- 使用padding来实现间距,而不是使用margin,因为有box-sizing: border-box;来让padding不影响宽度计算。
- padding左右的边距导致无法对齐网格行的边缘问题
- 使用负边距给行元素,来拉伸行元素的左右
- 或者使用特殊选择器,来清除第一个和最后一个列的左右边距。
flexbox布局
- Flexbox的可预测性更好,还能提供更精细的控制
- 实际上,Flexbox比border-radius属性的支持范围更广
- 还可以用display: inline-flex。它创建了一个弹性容器,行为类似于inline-block元素
- 在实际开发时,很少用到display: inline-flex。
- 现代css中貌似给flex增加了一个弹性格式化上下文
flex原则
- 弹性盒子
- 弹性子元素默认是在同一行按照从左到右的顺序并排排列。
- 弹性容器像块元素一样填满可用宽度(flex,不是inline-block),但是弹性子元素不一定填满其弹性容器的宽度。
弹性子元素高度相等,该高度由它们的内容决定。(或者说一行)- 之前提到的display值,比如inline、inline-block等,只会影响到应用了该样式的元素,而Flexbox则不一样。一个弹性容器能控制内部元素的布局。
- flex的轴
- Flexbox布局是以
主轴和副轴为基础来定义的 - 默认的flex的主轴是水平的,副轴是垂直的。(元素依次从左到右,一行满了后在垂直方向排)
- 通常轴的方向是以起点和终点来描述的。
- Flexbox布局是以
- flex子元素控制
弹性子元素的大小
- 你可以用width和height属性设置它们大小,但是比起margin、width、height这些常见属性,Flexbox还提供了更多更强大的选项。
- flex选项(弹性选项,就是控制大小)
- flex属性控制弹性子元素在
主轴方向上的大小 - flex属性是三个不同大小属性的简写:flex-grow、flex-shrink和flex-basis。
- flex:1:它提供了flex-grow的值,剩下的两个属性是默认值(
分别是1和0%),等同于flex: 1 1 0%; - flex:none:元素会根据自身宽高来设置尺寸。它是
完全非弹性的:既不会缩短,也不会伸长来适应 flex 容器。相当于将属性设置为”flex: 0 0 auto”。 - flex:auto:元素会根据自身的宽度与高度来确定尺寸,但是会伸长并吸收 flex 容器中额外的自由空间,也会缩短自身来适应 flex 容器。这相当于将属性设置为 “flex: 1 1 auto”。如果你需要优先保证该元素的内容,可以考虑替代flex:1
- flex: 0%; 等于: “flex: 1 1 0%;”,只要其单语法值如果是flex-basis的有效值,其就等于
flex: 1 1 <flex-basis>;
- flex: 0%; 等于: “flex: 1 1 0%;”,只要其单语法值如果是flex-basis的有效值,其就等于
- flex-basis是核心,其他两个属性都基于flex-basis。初始值auto
- flex-basis定义了元素大小的基准值,即一个初始的“主尺寸”。
- flex-basis属性可以设置为任意的width值,包括px、em、百分比。
它的初始值是auto,此时浏览器会检查元素是否设置了width属性值。如果有,则使用width的值作为flex-basis的值;如果没有,则用元素内容自身的大小。如果flex-basis的值不是auto, width属性会被忽略。- 这里解释了元素的宽度width和flex:1在配合时的细节,因为width被用作了flex-basis,而flex:1则是flex: 1 1 0%;
- 每个弹性子元素的
初始主尺寸,即flex-basis的值确定后,它们可能需要在主轴方向扩大或者缩小来适应(或者填充)弹性容器的大小。这时候就需要flex-grow和flex-shrink来决定缩放的规则。
- flex-grow属性:存在剩余空间时的扩张机制。初始值为0
- 每个弹性子元素的flex-basis值计算出来后,它们(加上子元素之间的外边距)加起来会占据一定的宽度。加起来的宽度不一定正好填满弹性容器的宽度,可能会有留白
- 出来的留白(或剩余宽度)会按照flex-grow(增长因子)的值分配给每个弹性子元素,flex-grow的值为非负整数。
- 如果一个弹性子元素的flex-grow值为0,那么它的宽度不会超过flex-basis的值;
如果某个弹性子元素的增长因子非0,那么这些元素会增长到所有的剩余空间被分配完,也就意味着弹性子元素会填满容器的宽度flex-grow的值越大,元素的“权重”越高,也就会占据更大的剩余宽度。拥有相同的flex-grow值的子元素会分配相同比例的剩余宽度
- flex-shrink:超出容器宽度后的缩放权重。初始值为1
- flex-shrink属性与flex-grow遵循相似的原则。计算出弹性子元素的初始主尺寸后,它们的累加值可能会超出弹性容器的可用宽度。如果不用flex-shrink,就会导致溢出
- 每个子元素的flex-shrink值代表了它是否应该收缩以防止溢出。
- 如果某个子元素为flex-shrink: 0,则不会收缩;
- 如果值大于0,则会收缩至不再溢出。按照flex-shrink值的比例,
值越大的元素收缩得越多。注意,这里是收缩的越多。
- flex属性控制弹性子元素在
推荐使用简写属性flex,而不是分别声明flex-grow、flex-shrink、flex-basis。- 与大部分简写属性不一样,
如果在flex中忽略某个子属性,那么子属性的值并不会被置为初始值。相反,如果某个子属性被省略,那么flex简写属性会给出有用的默认值:flex-grow为1、flex-shrink为1、flex-basis为0%。这些默认值正是大多数情况下所需要的值。
- 与大部分简写属性不一样,
弹性盒子方向
- Flexbox的另一个重要功能是能够切换主副轴方向,用弹性容器的flex-direction属性控制。默认值是row,常用的还有column、row-reverse、column-reverse
- 改变弹性方向就改变了主轴方向,
副轴因为要与主轴垂直,所以方向也随之改变
- 改变弹性方向就改变了主轴方向,
- column方向貌似和普通的文档流布局是一样的效果,不过使用它有两个好处
- 填充剩余内容
- 其中的子元素很方便的垂直居中
- 水平弹性盒子的大部分概念同样适用于垂直的弹性盒子(column或column-reverse),但是有一点不同:
- 在CSS中处理高度的方式与处理宽度的方式在本质上不一样。弹性容器会占据100%的可用宽度,而高度则由自身的内容来决定。即使改变主轴方向,也不会影响这一本质。
- 弹性容器的高度由弹性子元素决定,它们会正好填满容器。所以
在垂直的弹性盒子里,子元素的flex-grow和flex-shrink不会起作用,除非有“外力”强行改变弹性容器的高度。这样拉伸和收缩就会有用武之地了- 例如在嵌套flex中,从外层弹性盒子计算出来的高度
- 例如容器设置了显式的高度
其他容器属性
- flex-wrap属性(半常用)
- 它允许弹性子元素换到新的一行或多行显示。它可以设置为nowrap(初始值)、wrap或者wrap-reverse。
- 启用换行后,子元素不再根据flex-shrink值收缩,
任何超过弹性容器的子元素都会换行显示。flex-grow仍然会有效 - 如果弹性方向是column或column-reverse,那么flex-wrap会允许弹性子元素换到新的一列显示,
不过这只在限制了容器高度的情况下才会发生,否则容器会扩展高度以包含全部弹性子元素。 - 注意:flex:1;和wrap一起使用时,还是不会换行,因为flex:1的主尺寸是0,所以不会撑满容器。
- 注意:
flex中的布局大部分针对的是行,如果某个元素换行了,那么这个元素不会对上一行的元素有影响。
- justify-content属性(常用)
- 当子元素未填满容器时,justify-content属性控制子元素沿主轴方向的间距。它的值包括几个关键字:flex-start、flex-end、center、space-between以及space-around。
- 默认值flex-start让子元素从主轴的开始位置顺序排列
- space-between将第一个弹性子元素放在主轴开始的地方,最后一个子元素放在主轴结束的地方,剩下的子元素间隔均匀地放在这两者之间的区域。值space-around类似,只不过给第一个子元素的前面和最后一个子元素的后面也加上了相同的间距。
间距是在元素的外边距之后进行计算的,而且flex-grow的值要考虑进来。也就是说,如果任意子元素的flex-grow的值不为0,或者任意子元素在主轴方向的外边距值为auto, justify-content就失效了。- center的话则让子元素居中
- align-items属性(半常用)
- 控制子元素在副轴方向的对齐方式。
align-items的初始值为stretch,在水平排列的情况下让所有子元素填充容器的高度(也就是所谓的等高列),在垂直排列的情况下让子元素填充容器的宽度,因此它能实现等高列。- 其他的值让弹性子元素可以保留自身的大小,而不是填充容器的大小。
- align-content属性(不常用)
- 如果开启了换行(用flex-wrap), align-content属性就可以控制弹性容器内沿副轴方向每行之间的间距。(类似justify-content的值和效果,不过控制的是多行之间的间距)
- flex-flow属性(不常用)
- 它是flex-direction和flex-wrap的简写。
子元素属性
- order属性
- 正常情况下,弹性子元素按照在HTML源码中出现的顺序排列。它们沿着主轴方向,从主轴的起点开始排列。使用order属性能改变子元素排列的顺序。还可以将其指定为任意正负整数。如果多个弹性子元素有一样的值,它们就会按照源码顺序出现。
- 默认都为0
- 谨慎使用order。让屏幕上的视觉布局顺序和源码顺序差别太大会影响网站的可访问性。在大多数浏览器里使用Tab键浏览元素的顺序与源码保持一致,如果视觉上差别太大就会令人困惑。视力受损的用户使用的大部分屏幕阅读器也是根据源码的顺序来的。
- align-self属性(不常用)不过可以配合align-items来减少一些flex嵌套
- 该属性控制弹性子元素沿着容器副轴方向的对齐方式。它跟弹性容器的align-items属性效果相同,但是它能单独给弹性子元素设定不同的对齐方式。auto为初始值,会以容器的align-items值为准。
flex布局技巧
- 浏览器的devTools工具,其实可以用一种可视化的形式查看flex布局的细节。
- 查看图片:

- 查看图片:
- Flexbox允许使用margin: auto来填充弹性子元素之间的剩余可用空间。
- 例子:将最后一个元素,移动到flex容器的最右边。给最后一个元素使用margin-left: auto后,flex就会让最后这个元素的外边距填充所有可用空间。
- 如果希望将倒数第一和倒数第二个元素都推到右侧,则可以把auto外边距加到倒数第二个元上。
- 这样,出现简单的左右布局时,可以减少内部嵌套一个flex布局和flex:1的写法。
- 你应该依靠正常的文档流,只在必要的时候才使用Flexbox。这么说并不是让你不用它,而是希望你不要拿着锤子满世界找钉子。
- 注意:Flexbox的一个有趣之处在于如何基于弹性子元素的数量和其中的内容量(及大小)来计算容器的大小。因为如果网页很大,或者加载很慢时可能会产生奇怪的行为。
- 当浏览器加载内容时,它渐进渲染到了屏幕,即使此时网页的剩余内容还在加载。假设有一个使用弹性盒子(flex-direction: row)实现的三列布局。如果其中两列的内容加载了,浏览器可能会在加载完第三列之前就渲染这两列。然后等到剩余内容加载完,浏览器会重新计算每个弹性子元素的大小,重新渲染网页。用户会短暂地看到两列布局,然后列的大小改变(可能改变特别大),并出现第三列。
- 只有一行多列的布局才会产生这个问题。如果主页面布局采用的是一列多行(flex-direction: column),就不会出现以上问题。
- flexbugs网站,用来记录浏览器在felx布局时所遇到的bug(不过现代浏览器基本上都已经修复)
grid网格布局
网格布局在生产环境是可用的,当你需要一个二维布局时(元素需要同时考虑行和列上的布局),可以考虑网格布局。
基本
- CSS网格可以定义由行和列组成的二维布局,然后将元素放置到网格中。有些元素可能只占据网格的一个单元,另一些元素则可能占据多行或多列。
- 网格的大小既可以精确定义,也可以根据自身内容自动计算。你既可以将元素精确地放置到网格某个位置,也可以让其在网格内自动定位,填充划分好的区域。
- 为了防止flex发展的历史重演,浏览器厂商采用了全新的方式处理网格布局。它们不再用浏览器前缀方式,用户必须明确地开启这项特性才能使用。
- 2017年3月,各大厂商启用了网格布局特性。3周的时间内,Firefox、Chrome、Opera以及Safari全都发布了新版本,启用网格布局。2017年6月,微软的Edge紧跟步伐。短短3个月,浏览器支持的用户量从0%一跃到近70%。这在CSS世界里可谓史无前例。
- 网格规范的Level1版本已经稳定,所有现代浏览器都遵守该规范。这意味着现在网格布局已具备在生产环境使用的条件
- 子网格:2023年最新的浏览器已经支持,用于网格内部子元素的内部依然和外层网格对齐。
grid和flex的差异
- 我们说 flexbox 是一种一维的布局,是因为一个 flexbox 一次只能处理一个维度上的元素布局,一行或者一列。作为对比的是另外一个二维布局 CSS Grid Layout(网格布局),可以同时处理行和列上的布局。
- 感觉flex如果要实现grid能实现的复杂布局,需要不停的嵌套,因为flex不能支持跨行或者跨列的布局方式。
- 例如:flex如果要实现某一个元素占据两行时(row的方向),你只能对其进行嵌套布局
- 也就是说,flex更像是当你确定好了布局方向后,那么这个方向的布局基本上能够覆盖掉大部分的复杂情况,也就是主轴。flex对于同一个行的元素的控制很强大,但是如果flex出现了多行,那么对于多行元素之间的布局控制就非常有限了(就换行flex-wrap时的每行间隔可以控制一下)
- 也就是flex能够对于单一方向的布局有着非常强大的能力,但是如果要同时考虑行和列两个维度方向上的布局,则有些力不从心了。
- flex针对的是一个方向上的布局控制,而grid是一种二维、两个方向上的布局控制。
- flex:确定行方向,控制元素在列方向的布局。
- grid:划分行列,控制元素在行和列的布局。
- Flexbox在一个方向上对齐元素,而网格在两个方向上对齐元素
Flexbox是以内容为切入点由内向外工作的,而网格是以布局为切入点从外向内工作的。- grid:网格中,首先要描述布局,然后将元素放在布局结构中去。虽然
每个网格元素的内容都能影响其网格轨道的大小,但是这同时也会影响整个轨道的大小,进而影响这个轨道里的其他网格元素的大小。 - flex:Flexbox让你在一行或一列中安排一系列元素,但是它们的大小不需要明确指定,每个元素占据的大小根据自身的内容决定。
- grid:网格中,首先要描述布局,然后将元素放在布局结构中去。虽然
grid适合对页面的大区域进行整体的布局,而flex则适合对具体内容进行细致的布局。
grid网格布局和网格系统
- 其实grid有点像网格系统,因为网格系统本身也是将一行分为多少列,然后子元素占据多少列的宽度等等
- 只不过网格系统受限于css本身,导致其和flex一样只能控制一行上的布局,是一维的。
- 不过和网格系统一样,grid其实也是将一行分为多少个列,但是他还能支持控制其整个区域可以分为多少行,从而可以同时对行和列上的元素进行布局。
- 也就是所谓的grid是将一个区域内容,划分为多少个格子,而区域中的元素,占据多少个格子(行和列方向上),其位置偏移多少个格子。
网格剖析
- 网格容器:用于进行网格布局的元素,其通常设置为display:grid;
- 网格元素:网格容器的
子元素- 网格中的元素,都会占据至少一个网格单元
- 网格线(grid line):网格线构成了网格的框架。一条网格线可以水平或垂直,也可以位于一行或一列的任意一侧。如果指定了grid-gap的话,它就位于网格线上。(最左边的和最右边的那条也算)
- 网格轨道(grid track):一个网格轨道是两条相邻网格线之间的空间。网格有水平轨道(行)和垂直轨道(列)。
- 网格单元(grid cell):网格上的单个空间,水平和垂直的网格轨道交叉重叠的部分。
- 网格区域(grid area):网格上的矩形区域,由一个到多个网格单元组成。该区域位于两条垂直网格线和两条水平网格线之间。
布局语法
- 跟Flexbox类似,网格布局也是作用于两级的DOM结构,即grid容器和grid元素。
- 使用display:grid;启用网格布局。grid是一个块级格式化上下文,其宽度默认是占据父元素宽度(可以用inline-grid设置行内块grid布局)
- grid-template-columns和grid-template-rows。这两个属性定义了网格有多少网格轨道(网格中有多少行和列)。
- 网格单元(或者说网格轨道)的大小
每个网格元素的内容都能影响其网格轨道的大小,但是这同时也会影响整个轨道的大小,进而影响这个轨道里的其他网格元素的大小。- 即每个网格轨道的大小,例如宽度,它的宽度是这个轨道中的最宽的那个元素的宽度。
- 一个元素占据网格单元或者区域时,其高度也会被拉伸为网格单元和区域的高度(和flex的高度一样),虽然我们通常不同设置行高,而是让其自行撑开
- grid-gap属性定义了每个网格单元之间的间距。也可以用两个值分别指定垂直和水平方向的间距(比如grid-gap: 0.5em 1em)。
- 现在新规范使用gap来替代grid-gap。grp是一个无关grid布局的属性,他可以被用于grid和flex
- gap应用于grid的兼容性好一些,gap在flex布局中生效的兼容性要差一些(通常使用其row-gap设置行间距,column-gap在flex中用的比较少,除非一行或者一列等分不换行,设置列间距用的)。
- gap是column-gap和row-gap的简写
- 新版本的浏览器会将grid-gap视为gap属性的别名,为了兼容性
- 单位:fr
- 新单位fr,代表每一列(或每一行)的分数单位(fraction unit)。这个单位跟Flexbox中flex-grow因子的表现一样。都是按照其值的比例,而非具体的长度。
- grip中不一定非得用分数单位,可以使用其他的单位,比如px、em或百分数。也可以混搭这几种单位,例如,grid-template-columns: 300px 1fr定义了一个固定宽度为300px的列,后面跟着一个会填满剩余可用空间的列。
- 注意,如果是类似等分的:1fr 1fr 1fr,他并非严格意义的三等分
- 1fr 的本质是:分配「
Grid 容器的可用空间」的份数,它是「弹性的、可被撑开的」,不是「写死的固定 px 宽度」。Grid 布局中,1fr 列轨道有一个默认特性:当列内子元素的「最小内容宽度」超过了 1fr 分配的宽度时,会自动拉伸该列轨道的宽度,满足子元素的最小宽度需求。 - 你可以理解为和flex的弹性一样,它是先计算内容的宽度再确定1fr的宽度
- 这在遇到容器子元素使用了一行文本溢出时比较常见text-overflow: ellipsis; 会导致溢出失效并撑开网格轨道宽度。
- 解决方法
可以使用minmax(0, 1fr)来代替1fr,这是 Grid 官方文档推荐的「解决 1fr 列被撑开」的最优方案- minmax(0, 1fr) 的核心作用是:给 Grid 的列轨道设置「最小宽度为 0」,彻底禁用 Grid 的「列轨道被内容撑开」的原生特性,强制列轨道的宽度「永远等于 1fr 分配的等分宽度」,无论列内的内容有多长、多大,列宽度都不会变。
- 1fr 的本质是:分配「
- 列宽的auto关键字(用于内容自适应宽度)
- Grid 的列宽设置中,auto 是一个自适应列宽关键字
- 列宽由「内容本身」决定,内容多则列宽宽,内容少则列宽窄,永远保证内容能完整显示,不会被裁切
- 列宽至少要能装下单元格内的所有内容(文字、图片、子元素),不会出现内容溢出 / 换行(除非内容主动设置换行);
- 其次分配「剩余空间」:如果 Grid 容器有多余的可用宽度,auto 列不会主动抢占剩余空间,只会维持「内容最小宽度」,把剩余空间让给 1fr/ 百分比这类弹性列。即:
auto 是「非弹性列宽」,不会主动瓜分 Grid 容器的剩余空间
- 注意,列宽是看整体的该列中每行的最大宽度和最小宽度的
- 三连auto的写法,会和三连1fr效果一样(如果内容都比较少的话)
- repeat()函数:grid-template-rows: repeat(4, auto);
- 它在声明多个网格轨道的时候提供了简写方式。
- 它定义了四个水平网格轨道,高度为auto,这等价于grid-template-rows: auto auto auto auto。轨道大小设置为auto,轨道会根据自身内容扩展。
- 用repeat()符号还可以定义不同的重复模式,比如repeat(3, 2fr 1fr)会重复三遍这个模式,从而定义六个网格轨道,重复的结果是2fr 1fr 2fr 1fr 2fr 1fr。
- 还可以将repeat()作为一个更长的模式的一部分。比如grid-template-columns: 1fr repeat(3, 3fr) 1fr定义了一个1fr的列,接着是三个3fr的列,最后还有一个1fr的列(可以用1fr 3fr 3fr 3fr 1fr表示)。
- repeat()在定义时,还可以给网格线命名:
repeat(4, [row] auto),它在repeat()里声明了一条命名的水平网格线,于是每条水平网格线被命名为row(除了最后一条,因为4个网格轨道需要5个名字给网格线) - 使用:grid-row: row 3 / span 1;重复定义同一个名称完全合法。这个例子将元素放在从row 3(第三个叫row的网格线)开始的地方,并跨越一个网格轨道。
- 更复杂的repeat:
repeat(3, [col] 1fr 1fr))这表明在每个网格轨道之间定义一个命名的col网格线。
- grid-column和grid-row属性确定子元素占据的区域:grid-column: 1 / 3;表明该元素占据了横跨列的网格线编号1到3的位置。
- 这些属性实际上是简写属性:grid-column是grid-column-start和grid-column-end的简写;grid-row是grid-row-start和grid-row-end的简写。
- grid-area是对:grid-row-start、grid-column-start、grid-row-end 和 grid-column-end 的简写
- 中间的斜线只在简写属性里用于区分两个值,斜线前后的空格不作要求。
- 这些属性实际上是简写属性:grid-column是grid-column-start和grid-column-end的简写;grid-row是grid-row-start和grid-row-end的简写。
- 可以用一个特别的
关键字span来指定grid-row和grid-column的值:grid-row: span 1;- 这个span关键字告诉浏览器元素需要占据一个网格轨道。因为这里没有指出具体是哪一行,所以会根据网格元素的
布局算法(placement algorithm)自动将其放到合适的位置。 - 可以使用
auto关键字:表示对网格的布置行为不做干涉,即自动布置,自动的 span 或者默认 span 值为 1。
- 这个span关键字告诉浏览器元素需要占据一个网格轨道。因为这里没有指出具体是哪一行,所以会根据网格元素的
- 布局网格元素还有另外两个替代语法(除了网格线编号和span外):命名的网格线和命名的网格区域。
- 命名的网格线
- 有时候记录所有网格线的编号实在太麻烦了,尤其是在处理很多网格轨道时。为了能简单点,可以给网格线命名,并在布局时使用网格线的名称而不是编号
grid-template-column: [start] 2fr [center] 1fr [end]:这条声明定义了两列的网格,三条垂直的网格线分别叫作start、center和end。之后定义网格元素在网格中的位置时,可以不用编号而是用这些名称来声明:grid-column: start / end;[center]中可以定义多个名字:[center center]-start和-end后缀作为关键字(例如left-start和left-end),定义了两者之间的区域。如果给元素设置grid-column: left,它就会跨越从left-start到left-end的区域。
- 命名网格区域(这个怎么说呢,感觉会增加编写的复杂度的样子,虽然写出来之后,可以一目了然)
- 但是,
据说这一个最受欢迎的方式 - 不用计算或者命名网格线,直接用命名的网格区域将元素定位到网格中。实现这一方法需要借助网格容器的grid-template属性和网格元素的grid-area属性。
- grid-template-areas属性使用了一种ASCII art的语法,可以直接在CSS中画一个可视化的网格形象。在这个里面给每一个网格单元分配一个具体的命名,同名的名字会组成网格区域。
- 每个命名的网格区域必须组成一个矩形。不能创造更复杂的形状,比如L或者U型
- 还可以用句点(.)作为名称,这样便能空出一个网格单元
- 使用命名网格区域时,
会自动创建隐式命名网格线。
- 但是,
- minmax()函数:它不仅仅只能在repeat函数中使用
- 有时候我们不想给一个网格轨道设置固定尺寸,但是又希望限制它的最小值和最大值。这时候需要用到minmax()函数。
- 它指定两个值:最小尺寸和最大尺寸。浏览器会确保网格轨道的大小介于这两者之间。(如果最大尺寸小于最小尺寸,最大尺寸就会被忽略。)通过指定minmax(200px, 1fr),浏览器确保了所有的轨道至少宽200px。
- auto-fill关键字
- repeat()函数里的auto-fill关键字是一个特殊值。设置了之后,
只要网格放得下,浏览器就会尽可能多地生成轨道(它并不管你放了几个元素),并且不会跟指定大小(minmax()值)的限制产生冲突。感觉常见的grid-template-columns设置时可能用的上。 auto-fill需要 “明确的列宽参考值”(固定值 / 最小值)才能计算列数,要让auto-fill生效,必须给列设置固定宽度(如 200px)或最小宽度(结合minmax())- minmax () + auto-fill(自适应列宽):grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
- 这是
最常用的 “响应式自动填充” 方案,结合minmax()设置 “列的最小宽度”,剩余空间用1fr分配 - minmax(200px, 1fr):每列最小宽度 200px(给 auto-fill 提供计算参考),最大宽度 1fr(当容器有剩余宽度时,列会自动拉伸填满);
- auto-fill:根据容器宽度和列的最小宽度(200px),计算能容纳的最大列数,剩余空间由1fr分配给每列,实现 “自适应列宽 + 自动换行”。
- 因为所有轨道的大小上限都为1fr(最大值),所以所有的网格轨道都等宽。
- 这是
- repeat(auto-fill, 1fr) 这样只会产生一列
- 原因:1fr 表示 “等分容器的可用宽度”,当使用auto-fill时,浏览器会先尝试计算 “能放多少个 1fr 的列”—— 但1fr本身是相对单位(依赖容器总宽度),而非固定 / 最小宽度(如 200px),浏览器无法确定 “一个 1fr 的列到底多宽”,因此只能生成1 列,让这 1 列占据 100% 的容器宽度。
- 他也会计算gap
- repeat()函数里的auto-fill关键字是一个特殊值。设置了之后,
- auto-fit关键字
- 如果网格元素不够填满所有网格轨道,auto-fill就会导致一些空的网格轨道。如果不希望出现空的网格轨道,可以使用auto-fit关键字代替auto-fill。它会让非空的网格轨道扩展,填满可用空间。
- 它和auto-fill行为几乎一样,但是如果其元素无法填满所有网格单元,则会根据元素确定网格轨道
- 例如repeat(auto-fill, minmax(200px, 1fr)),他会根据宽度和gap,来确定网格有多少网格轨道(以最小的200px来计算),假设宽为700,那么它会生成3个网格轨道,然后如果只有2个元素,那么最后一个网格单元就会空着。
- 如果使用auto-fit,那么在2个元素时,就会只生成2个网格轨道,也就是将3个网格轨道拉伸成2个网格轨道来满足2个元素,如果只有1一个元素,那么就会只有一个网格轨道。
- 如果超出了其计算的最大网格轨道数,那么auto-fill和auto-fit则表现的是一样的了。
- 具体选择auto-fill还是auto-fit取决于你是想要确保网格轨道的大小,还是希望整个网格容器都被填满。一般倾向于auto-fit。
- 有时候,你使用repeat(auto-fill, minmax(200px, 1fr) )设置了列,但是有时候你发现有3列,但是第三列的宽度不足200,这是为什么?
- 其实是因为可能存在了一个占据3列区域的元素,但是其网格容器宽度默认只能放下2列200px的元素,创建不了3列,此时就自动创建隐式的一列,而自动创建的隐式一列其宽度仅仅只是元素内容的宽度。
- 你可以使用grid-auto-columns: 1fr;来将隐式创建的网格轨道宽度设置为1fr。
- 或者设置为minmax(200px, 1fr),但是如果网格容器宽度不足,那么会撑开
- 反正最好别这样搞
命名的网格区域:以下代码定义了四个网格区域(top、left、right、bottom),中间围绕着一个空的网格单元。
1 | .class { |
网格线编号
- 网格轨道定义好之后,要将每个网格元素放到特定的位置上。浏览器给网格里的每个网格线都赋予了编号
- 网格线编号从1开始,左上角的点,所在的网格线为1。网格线1和网格线2可以定位到左上角第一个网格单元。
- 网格线编号也可以从右下角开始,其右下角的点为-1,并且以-1、-2、-3开始计算。
- 用网格线的编号指定网格元素的位置
- 可以在
grid-column和grid-row属性中用网格线的编号指定网格元素的位置。 - 如果想要一个网格元素在垂直方向上跨越1号网格线到3号网格线,就需要给元素设置grid-column: 1 / 3。或者设置grid-row: 3 / 5让元素在水平方向上跨越3号网格线到5号网格线。
- 这两个属性一起就能指定一个元素应该放置的网格区域。
- 可以在
显式和隐式网格
- 某些场景下,你可能不清楚该把元素放在网格的哪个位置上。当处理大量的网格元素时,挨个指定元素的位置未免太不方便。
- 当元素是从数据库获取时,元素的个数可能是未知的。在这些情况下,以一种宽松的方式定义网格可能更合理,剩下的交给布局算法来放置网格元素。
- 这时需要用到隐式网格(implicit grid)。
- 使用grid-template-*属性定义网格轨道时,创建的是显式网格(explicit grid),但是有些
网格元素仍然可以放在显式轨道外面,此时会自动创建隐式轨道以扩展网格,从而包含这些元素。 - 也就是说,你可能在语法上只是显式设置了网格有多少行和多少列,但是如果子元素默认一个个的网格单元去放都放不下时,就会创建新的网格行列(通常是创建行)除非你显示设置某个元素跨越不存在的列,那么也会创建隐式的列
隐式网格轨道默认大小为auto,也就是它们会扩展到能容纳网格元素内容。可以给网格容器设置grid-auto-columns和grid-auto-rows,为隐式网格轨道指定一个大小(比如,grid-auto-columns: 1fr)。- 在指定网格线的时候,
隐式网格轨道不会改变负数的含义。负的网格线编号仍然是从显式网格的右下开始的
- 使用grid-template-*属性定义网格轨道时,创建的是显式网格(explicit grid),但是有些
- 在一些布局中(照片墙或者瀑布流),可以只设置列的网格轨道,但是网格行是隐式创建的。这样网页不必关心照片的数量,它能适应任意数量的网格元素。只要照片需要换行显示,就会隐式创建新的一行。
布局算法
- grid的布局算法,它用来确定子元素在未显式确定网格区域位置时,元素的摆放情况
- 你可以利用通用的布局算法来减少你的grid样式编写。
- 显式指定网格区域和布局算法自动放置可以混合使用。
- 默认元素的位置,看起来就是一个网格单元(默认占据一行、一列),且从左到右,从上到下的摆放。
- 如果你使用了命名网格区域时,那么其网格元素会按照网格区域进行摆放,此时就是一个元素占据一个网格区域了,而不是一个网格单元了。
- 当不指定网格上元素的位置时,元素会按照其布局算法自动放置。
- grid-auto-flow用来控制布局算法,默认值是row
- 默认情况下,布局算法会按元素在标记中的顺序将其逐列逐行摆放。默认不会重叠放置
- 当一个元素无法在某一行容纳(也就是说该元素占据了太多网格轨道)时,算法会将它移动到下一行,寻找足够大的空间容纳它。
- 如果是隐式轨道时,那么其行为应该是依次增加一个轨道尝试放置,直到找到某一个网格区域能够放下这个元素指定的区域大小
- 例如:总共4 x 2,已经有一个2 x 2和一个1 x 1的了,分别放到左边和并排的左上角。此时如果再来一个2 x 2的,那么在4 x 2中已经没有可以放下2 x 2的空间了。
- 那么此时布局算法尝试增加一行,变成 4 x 3,那么刚好右下角的2 x 2就空出来了,就可以放下了。
- 如果值为column,它就会将元素优先放在网格列中,只有当一列填满了,才会移动到下一行。
- 这个应该很少用吧,因为怎么说呢,只有固定了列时,才可能尝试用这个值来先填满列
- 值如果为dense(可能常用)
它让算法紧凑地填满网格里的空白,尽管这会改变某些网格元素的顺序。加上这个关键字,小元素就会“回填”大元素造成的空白区域。- grid-auto-flow: dense,等价于grid-auto-flow: row dense。(前面的写法里隐含了row,因为初始值就是row。)
- 还可以写成grid-auto-flow: column dense;
- 需要注意的是,紧凑的auto-flow方式会导致元素出现的顺序跟HTML里不一致。当使用键盘(Tab键)或者使用以源码顺序而非以显示顺序为准的屏幕阅读器来浏览网页时,用户可能会感到困惑
网格和flex的对齐
- 网格布局模块规范里的对齐属性有一些跟Flexbox相同,还有一些是新属性。
- CSS给网格布局提供了三个调整属性:justify-content、justify-items、justify-self。这些属性控制了网格元素在水平方向上的位置。
- 还有三个对齐属性:align-content、align-items、align-self。这些属性控制网格元素在垂直方向上的位置。
- 注意,它们这个对齐不是grid特有的,而是这些对齐属性,针对grid和flex都可以生效
- 网格轨道大小为 auto(且只有为 auto)时,才可以被属性 align-content 和 justify-content 拉伸。
grid的一些疑问
- 如果一个元素在设置网格区域的位置时,覆盖或者重叠了,那么两个元素会直接重叠,其层级默认按照元素的书写顺序(也可以使用z-index控制)
- 其可能意味着,网格的划分和命名之类的,其本质上只是确定网格的大小和区域划分,元素具体放哪其网格布局的哪个区域不会关心(显式的设置位置,而非布局算法)
grid布局技巧
- 可以参考这个网格案例网站:Grid by Example 里面囊括了大量的网格示例,是由W3C成员及开发人员Rachel Andrew收集的。
- 当然,也可以通过AI来帮你快速进行网格布局
- 当设计要求元素在两个维度上都对齐时,使用网格。当只关心一维的元素排列时,使用Flexbox。网格可以与Flexbox配合实现完整的布局系统。
- 在实践中,这通常(并非总是)意味着网格更适合用于整体的网页布局,而Flexbox更适合对网格区域内的特定元素布局。
浏览器的开发者工具,可以查看网格布局行列和网格单元,并且会给你画出网格线和网格线编号(在dom中点击grid布局元素右边的按个grid标签)- 或者Elements面板下面,有一个layout面板,可以详细看grid或者flex布局的细节,并且可以设置显示轨道名称和尺寸等等
- 网格布局,第一点就是先尝试确定要布局的区域是什么
- 其次就是将这一块区域,按照内容,去划分行和列,并尝试拆分网格
- 划分技巧
- 一行中,其网格单元的高度是一样的
- 一列中,其网格单元的宽度是一样的
- 不同的行的高度可以不一样,不同列的宽度可以不一样
- 设计不必填满每个网格单元。在想留白的地方让网格单元为空即可。
- 合并单元格的灵活性
- 由于grid布局中,一个元素可以占据多个相邻行和列的单元格,如果你发现某一个小区域中的布局,和你目前划分的单元格之间存在冲突,或者后续由于需求改动不再适用,那么可以将这一个区域中的元素合并为为一个,并且该元素占据之前所划分的单元格。
- 然后再在这个元素内部,自行划分grid网格布局或者flex布局。这就是合并单元格的灵活性
- 当然,你可以重新划分单元格为更小的尺寸,不过其改动,和计算可能得不偿失。
- 你也不可能无限划分为非常小的网格单元,这样虽然非常灵活和强大,但是没有任何必要。划分的网格单元不宜过多和过少
- 也就是划分网格的行和列不要太多了。
- grid也支持类似flex中的order来控制其元素放置顺序
定位和层叠上下文
相对、绝对、固定定位,已经较为新的粘性定位
- 定位会将元素彻底从文档流中移走。它允许你将元素放在屏幕的任意位置。还可以将一个元素放在另一个元素的前面或后面,彼此重叠。
- 除了默认的static之外,其他都是属于定位了
固定定位:fixed
- 固定定位其包含块是视口
- 定位的位置需要搭配四种属性一起使用:top、right、bottom和left。这些属性的值决定了固定定位的元素与浏览器视口边缘的距离。
设置这四个值还隐式地定义了元素的宽高。比如指定left: 2em; right: 2em表示元素的左边缘距离视口左边2em,右边缘距离视口右边2em。因此元素的宽度等于视口总宽度减去4em。top、bottom和视口高度也是这样的关系。- 你也可以用width和height显式设置元素宽高
固定元素从文档流中移除了,所以它不再影响页面其他元素的位置。别的元素会跟随正常文档流,就像固定元素不存在一样。也就是说它们通常会在固定元素下面排列,视觉上被遮挡。
绝对定位
- 固定定位让元素相对视口定位,此时视口被称作元素的包含块(containing block),绝对定位的行为和固定定位类似,只是它的包含块不一样。
- 绝对定位不是相对视口,而是相对最近的祖先定位元素(相对、绝对、固定元素)。
- 如果祖先元素都没有定位,那么绝对定位的元素会基于
初始包含块(initial containing block)来定位。初始包含块跟视口一样大,固定在网页的顶部。,粗略上理解为body
- 如果祖先元素都没有定位,那么绝对定位的元素会基于
- 跟固定元素一样,属性top、right、bottom和left决定了元素的边缘在包含块里的位置。
相对定位
- 相对定位可能是最不被理解的定位类型。当第一次给元素加上position: relative的时候,你通常看不到页面上有任何视觉改变。
- 相对定位的元素以及它周围的所有元素,都还保持着原来的位置(这里的解释更为准确一点就是:相对定位的元素,在其在布局时的位置,仍然是文档流所在的布局位置,即在布局中,看起来这个元素还是占据原来的位置一样,且宽高都没有改变。只不过其真正渲染时,他会被渲染到相对定位所在的位置。(css3变换中有些也是一样的)
- 跟固定或者绝对定位不一样,不能用top、right、bottom和left改变相对定位元素的大小。这些值只能让元素在上、下、左、右方向移动。可以用top或者bottom,但它们不能一起用(bottom会被忽略)。同理,可以用left或right,但它们也不能一起用(right会被忽略)。
- 有时可以用这些属性调整相对元素的位置,把它挤到某个位置,但这只是相对定位的一个冷门用法。
更常见的用法是使用position: relative给它里面的绝对定位元素创建一个包含块
层叠上下文和z-index
层叠上下文:我们假定用户正面向(浏览器)视窗或网页,而 HTML 元素沿着其相对于用户的一条虚构的 z 轴排开,层叠上下文就是对这些 HTML 元素的一个三维构想。众 HTML 元素基于其元素属性按照优先级顺序占据这个空间。
- 在同一页面定位多个元素时,可能会遇到两个不同定位的元素重叠的现象。有时我们会发现“错误”的元素出现在其他元素之前。
- 要了解浏览器如何决定元素层叠顺序,首先需要知道浏览器是如何渲染页面的
- 浏览器将HTML解析为DOM的同时还创建了另一个树形结构,叫作
渲染树(render tree)。- 它代表了每个元素的
视觉样式和位置。同时还决定浏览器绘制元素的顺序。顺序很重要,因为如果元素刚好重叠,后绘制的元素就会出现在先绘制的元素前面。
- 它代表了每个元素的
- 层叠顺序(默认情况)
- 通常情况下(使用定位之前),元素在HTML里出现的顺序决定了绘制的顺序。
- 定位元素时,这种行为会改变。
浏览器会先绘制所有非定位的元素,然后绘制定位元素。默认情况下,所有的定位元素会出现在非定位元素前面。
- 改变固定定位元素的html标签的位置不会产生不好的影响,但是对相对定位或绝对定位的元素来说,通常无法用改变标记位置的方法解决层叠问题。
- z-index控制层叠行为
- 相对定位依赖于文档流,绝对定位元素依赖于它的定位祖先节点。这时候需要用z-index属性来控制它们的层叠行为。
- z表示的是笛卡儿x-y-z坐标系里的深度方向。拥有较高z-index的元素出现在拥有较低z-index的元素前面。拥有负数z-index的元素出现在静态元素后面。
- z-index的行为很好理解,但是使用它时要注意两个小陷阱。
- 第一,
z-index只在定位元素上生效,不能用它控制静态元素。 - 第二,给一个定位元素
加上z-index可以创建层叠上下文。(其值不能是auto就行)- 这里只是给这个元素创建出了一个层叠上下文,但是这个层叠上下文只对其子元素进行层叠时有效(例如子元素设置z-index的顺序仅在这个层叠上下文中才生效,而不是对外层的层叠上下文生效)
- 不过这个元素设置了z-index,那么这个元素在层叠时,在外层层叠上下文中会按照设置的z-index生效。
- z-index的默认值就是auto
- 第一,
- 理解层叠上下文
- 一个层叠上下文包含一个元素或者由浏览器一起绘制的一组元素。
- 其中一个元素会作为层叠上下文的根,比如给一个定位元素加上z-index的时候,它就变成了一个新的层叠上下文的根。所有后代元素就是这个层叠上下文的一部分。
- 不要将层叠上下文跟BFC弄混了,它们是两个独立的概念,尽管不一定互斥。
层叠上下文负责决定哪些元素出现在另一些元素前面,而BFC负责处理文档流,以及元素是否会重叠。
- 实际上将层叠上下文里的所有元素一起绘制会造成严重的后果:
层叠上下文之外的元素无法叠放在层叠上下文内的两个元素之间。- 你可以理解为层叠上下文中的元素的层叠顺序和层叠上下文外部的其他任何元素都没有关联了。
- 整个层叠上下文中的元素,其整体的层叠顺序就看该层叠上下文元素本身的层叠顺序。然后内部的元素的层叠顺序自行处理。不会影响外部的顺序。
- 给一个定位元素加上z-index(值不为auto)是创建层叠上下文最主要的方式,但还有别的属性也能创建,比如小于1的opacity属性,还有transform、filter属性。由于这些属性主要会影响元素及其子元素渲染的方式,因此一起绘制父子元素。
- 注意:transform变换属性的值不是 none,则将创建一个层叠上下文。在这种情况下,该元素将作为任何包含的 position: fixed; 或 position: absolute; 元素的包含块。
- 文档根节点(
<html>)也会给整个页面创建一个顶级的层叠上下文。 所有层叠上下文内的元素会按照以下顺序,从后到前叠放:- 层叠上下文的根
- z-index为负的定位元素(及其子元素)
- 非定位元素
- z-index为auto的定位元素(及其子元素)
- z-index为正的定位元素(及其子元素)
定位的建议
- 如果能够依靠文档流,而不是靠明确指定定位的方式实现布局,那么浏览器会帮我们处理好很多边缘情况。记住,定位会将元素拉出文档流。
一般来说,只有在需要将元素叠放到别的元素之前时,才应该用定位。 - 用变量记录z-index,例如预处理的变量或者css变量。不要滥用z-index,可能会导致混乱
- 增量设为10或者100,这样就能在需要的时候往中间插入新值。
粘性定位
粘性定位(sticky positioning)。它是相对定位和固定定位的结合体:正常情况下,元素会随着页面滚动(如同设置了一个相对定位,但是top、left这些不会生效)当到达屏幕的特定位置时,如果用户继续滚动,它就会“锁定”在这个位置。最常见的用例是侧边栏导航。
- 粘性定位是
基于滚动元素的,它不仅仅只针对页面滚动 - 粘性元素永远不会超出父元素的范围,通常不要给父元素设置overflow为hidden或者scroll、auto这种,可能会导致粘性定位无法正常工作。
- 滚动页面的时候,粘性元素的父元素会一直正常滚动,但是粘性元素会在滚动到特定位置时停下来。
- 如果继续滚动得足够远,粘性元素还会恢复滚动。这种情况只在父元素的底边到达粘性元素的底边时发生。
- 也就是滚动到父元素的底部也在最上面时,粘性滚动就失效了
- 注意,只有当父元素的高度大于粘性元素时才会让粘性元素固定
- 不要忘记给粘性元素添加top,不然不会生效
视觉等比适配
在学习响应式设计之前,先介绍一个和响应式设计类似但是完全不一样的东西,且你可能会将他们搞混。即移动h5使用vm或者rem来实现页面中的元素在不同的手机屏幕都能拥有同样的比例(其实就是整个页面的等比缩放)
- 它严格来说并不叫做响应式设计,你可以将其叫做:等比缩放布局、视觉等比适配、弹性布局(和flex那个不是一回事)、定稿式适配、像素级还原适配等
- 它和响应式设计完全不同,两者的设计理念、实现原理、最终效果、技术核心 完全相反
- vm 等比适配:就像你在手机上看一张高清图片,把图片放大 / 缩小,图片里的人物、文字、间距的比例永远不变,只是整体变大变小;
- 响应式设计:就像你用手机看网页版知乎 / 淘宝,大屏时内容分栏展示,小屏时内容单列展示,按钮大小不会因为屏幕变大而翻倍,而是保持「够用的大小」。
- vm原理
- 其本质就是vm是相对于视窗口宽度的,当设计稿的px转换为vm之后,那么其页面所有的元素的长度都是基于视窗口了。不同屏幕的vm在转换为px时,其值都不一样
- 补充:vh 是视口高度单位,规则和 vw 一致,只是基于屏幕高度,移动端用得少,核心还是vw。
- rem也是一样,上文有提到。不过移动端 H5 开发,用 vw 完全足够,效率更高,不用写 JS,99% 的场景都能满足。
- 注意:
在实际开发中,通常使用px,然后在编译阶段,会通过插件将px转换为vw,一个是由于设计稿是px,另外一个就是直接使用vw会非常不直观,要转换。
对比表格
| 特性维度 | 「vm/vh 等比适配」 | 传统「响应式设计」(媒体查询 /flex/grid) |
|---|---|---|
| 核心目标 | 所有屏幕下,页面元素的「视觉比例完全一致」,和设计稿 1:1 像素级还原,小屏缩小、大屏放大,像一张「缩放的图片」 | 不同屏幕下,页面布局 / 元素大小「自适应调整」,保证体验最优,比例可以变化,甚至布局会重构 |
| 元素尺寸变化 | 元素的「宽 / 高 / 间距 / 字号」全部同比例放大 / 缩小,比如设计稿 375px 下按钮宽 100px,750px 宽的手机上按钮一定是 200px | 元素尺寸「非等比」,比如小屏按钮宽 100px,大屏可能还是 120px(不会翻倍),间距也不会同比例放大 |
| 布局结构 | 布局结构「完全不变」,永远是一个样子,只是整体缩放 | 布局结构「可以变化」,比如大屏 3 列、中屏 2 列、小屏 1 列(典型:圣杯布局响应式、栅格响应式) |
| 适配逻辑 | 「一套样式走天下」,不需要写任何媒体查询 @media,一个 vm 单位搞定所有屏幕 | 「多套样式适配多端」,必须依赖媒体查询,为不同宽度区间写不同样式 |
| 本质思想 | 「缩放思维」:把整个页面看作「一张画布」,所有元素都是画布上的内容,画布整体缩放,内容比例不变 | 「适配思维」:把页面看作「一套组件」,组件根据屏幕大小调整自身尺寸 / 位置 / 数量,保证可用 |
| 典型技术 | vm/vh、rem(配合根节点 fontSize 等比计算)、postcss-px-to-viewport 插件 | @media媒体查询、flex:1、grid、百分比宽度、max-width/min-width |
这种「等比适配」方案的适用场景 + 优缺点
- 使用场景:这种 vm 等比适配,是 移动端 H5 的「主流适配方案」,90% 的移动端 H5 页面都用它,尤其是:
- 活动页、营销页、落地页、海报页(最常用):这类页面追求「视觉极致还原」,设计稿上的每一个像素都不能错位,等比缩放是唯一选择;
- 表单页、详情页、个人中心:页面结构简单,不需要布局重构,只需要所有元素比例一致,体验统一;
- 小游戏、可视化 H5:需要精准的元素位置、尺寸比例,不能有任何变形;
- 外包 / 快速开发项目:一套样式搞定所有屏幕,不用写媒体查询,开发效率极高。
- 优点(为什么移动端 H5 首选它)
- 开发成本极低:零媒体查询,不用为不同屏幕写不同样式,一个单位走天下,新手也能快速上手;
- 视觉还原度 100%:和设计稿像素级一致,设计师最满意的方案,不会出现「设计稿好看,页面变形」的问题;
- 适配范围无限:从 320px 的小屏手机到 1024px 的平板,都能完美适配,没有适配死角;
- 无兼容性问题:vm/vh 是 CSS3 的标准单位,所有手机浏览器(包括微信 / 支付宝小程序 webview)都完美支持,不用考虑兼容 hack。
- 缺点(唯一的短板,也是它和响应式的互补点,也是它不能称为「响应式」的重要原因)
- 在「超大屏设备」(比如平板、大屏手机)上,元素会被无限制放大,比如设计稿 375px 的按钮,在 768px 平板上会变成 2 倍大小,文字、图片都会变得很大,视觉上会有点「臃肿」。
- 解决方案:
给页面加一个「最大宽度限制」,完美解决这个问题,也是行业通用的最优解- 小屏手机正常缩放,大屏设备(>750px)页面居中显示,宽度固定 750px,元素不再放大,兼顾「等比适配」和「大屏体验」。
- 处理方式有很多,通常可以使用postcss处理,例如postcss-mobile-forever库
- 通常可以在转换时使用min函数,配合vw和px,即默认vw,如果超出750px时的宽度,则选用px单位
- 或者利用rem和vw再加上一点点媒体查询:html的font-size使用vw,并且根据媒体查询,当屏幕大于750时,使用px。然后容器内的元素,使用rem进行布局。
- 注意字体如果使用vw后,那么无法被缩放了,需要注意
为什么「响应式设计」在移动端 H5 中用得少?
- 响应式设计(媒体查询)是 PC 端的主流方案,在移动端 H5 中用得少,核心原因:
- 移动端的屏幕宽度区间很小(320px ~ 450px),不需要布局重构;
- 响应式需要写大量媒体查询,开发成本高,还容易出现样式冲突;
- 移动端用户更在意「视觉统一」,而不是「布局变化」,等比缩放的体验更好。
响应式设计
响应式设计给Web开发人员带来了一个颇具挑战性的问题:应该如何设计网站,才能让用户在任何设备(大屏、电脑、平板、手机)上访问时,网站都既实用又美观?
注意,之前基本上都是没有实际的响应式设计的开发经验的,所以,这次学习更多的是理念的学习。
初始阶段:分别维护不同平台网站的设计思路
最初开发人员通过创建两个网站来解决这个问题:桌面版和移动版。例如,针对移动设备,服务器会将http://www.wombatcoffee.com重定向到http://m.wom-batcoffee.com。移动版网站通常对小屏幕用户提供的体验较少,设计得更精简。
问题
- 随着市场上出现越来越丰富的设备,这种方法便开始捉襟见肘了。平板设备该用移动版还是桌面版呢?大屏的“平板手机”呢?iPad Mini呢?
- 如果移动端用户想要执行桌面版的功能呢?最终,这种将桌面版和移动版强制分开的方案所带来的麻烦比它解决的问题还多。
- 除此之外,还需要同时维护多个网站。
现阶段方案:响应式设计
更好的方式是给所有用户提供同一份HTML和CSS。通过使用几个关键技术,根据用户浏览器视口的大小(或者屏幕分辨率)让内容有不一样的渲染结果。这种方式不需要分别维护两个网站。只需要创建一个网站,就可以在智能手机、平板,或者其他任何设备上运行。
- 这里的响应式,其基准是基于浏览器的视口宽度的
响应式设计的三大原则如下。
- 移动优先:这意味着在实现桌面布局之前先构建移动版的布局。
@media规则:使用这个样式规则,可以为不同大小的视口定制样式。用这一语法,通常叫作媒体查询(media queries),写的样式只在特定条件下才会生效。- 流式布局:这种方式允许容器根据视口宽度缩放尺寸。
初始之外,还有其他几个需要关注和讨论的话题
- 图片的响应式,因为图片作为非常常见的web资源,也是在不同设备中最值得需要处理的资源。
- 因为网页通常除了接口数据之外,就是最大的就是图片数据了,需要对其特殊处理或者说优化
原则一:移动优先
- 顾名思义就是构建桌面版之前要先构建移动端布局。这样才能确保两个版本都生效。(即通常选择渐进增强)
- 开发移动版网页有很多限制:
- 屏幕空间受限、网络更慢。
- 用户跟网页交互的方式也不一样:可以打字,但是用着很别扭,不能将鼠标移动到元素上触发效果等。
- 如果一开始就设计一个包含全部交互的网站,然后再根据移动设备的限制来制约网站的功能,那么一般会以失败告终。而移动优先的方式则会让你设计网站的时候就一直想着这些限制。一旦移动版的体验做好了(或者设计好了),就可以用“渐进增强”(progressive enhancement)的方式为大屏用户增加体验。
- 渐进增强:先实现满足需求的最小版本,然后再根据能力逐步完善功能
- 优雅降级:先实现需要的所有功能,然后根据限制,做功能的删减或者限制
移动优先的设计
移动版设计就是内容的设计。想想看,在桌面版网页里,一边是文章,另一边是侧边栏,侧边栏里有链接和不太重要的内容。然而在移动端,我们希望文章先出现。换句话说,我们希望最重要的内容先出现在HTML里。- 这一点恰好跟可访问性的关注点不谋而合:一个屏幕阅读器优先读到“重要的内容”,或者用户使用键盘浏览时先获取到文章里的链接,然后才是侧边栏里的链接。
- 也就是说,通常在移动的设计中,重要内容,最先渲染和展示给用户
- 话虽如此,这也不是一条铁律。
- 注意:做响应式设计时,一定要确保HTML包含了各种屏幕尺寸所需的全部内容。你可以对每个屏幕尺寸应用不同的CSS,但是它们必须共享同一份HTML。
- 当设计移动触屏设备的时候,确保所有的关键动作元素都足够大,能够用一个手指轻松点击。千万不要让用户放大页面,才能点中一个小小的按钮或者链接。
响应式设计实现流过程
- 在确定好移动端的布局和内容后,就需要思考较大的视口该如何设计。虽然要先给移动端写布局,但是心里装着整体的设计,才能帮助我们在实现过程中做出合适的决定。
- 在写的过程中思考中等屏幕和大屏幕的断点
- 断点:一个特殊的临界值。屏幕尺寸达到这个值时,网页的样式会发生改变,以便给当前屏幕尺寸提供最佳的布局。
- 通常来说,移动优先的开发方式意味着最常用的媒体查询类型应该是min-width。在任何媒体查询之前,最先写的是移动端样式,然后设置越来越大的断点。
- 最优先的是移动端样式,因为它们不在媒体查询里,所以这些样式对所有断点都有效。然后是针对中等屏幕的媒体查询,其中的规则基于移动端样式构建并且会覆盖移动端样式。最后是针对大屏幕的媒体查询,在这里添加网页最后的布局。
- 因为要先实现移动版设计,所以更应该了解在更大的视口下网页长什么样,这样才能在一开始就写出合适的HTML结构。
- 当移动端开发完成后,还需要添加视口的meta标签(name=viewport)。这个HTML标签告诉移动设备,你已经特意将网页适配了小屏设备。如果不加这个标签,移动浏览器会假定网页不是响应式的,并且会尝试模拟桌面浏览器,那之前的移动端设计就白做了。
- meta标签的content属性里包含两个选项。首先,它告诉浏览器当解析CSS时将设备的宽度作为假定宽度,而不是一个全屏的桌面浏览器的宽度。
- 其次当页面加载时,它使用initial-scale将缩放比设置为100%。
- content属性还有第三个选项user-scalable=no,阻止用户在移动设备上用两个手指缩放。
原则二:媒体查询
响应式设计的第二个原则是使用媒体查询。媒体查询(media queries)允许某些样式只在页面满足特定条件时才生效。这样就可以根据屏幕大小定制样式。可以针对小屏设备定义一套样式,针对中等屏幕设备定义另一套样式,针对大屏设备再定义一套样式,这样就可以让页面的内容拥有多种布局。
- 媒体查询里面的规则仍然遵循常规的层叠顺序。它们可以覆盖媒体查询外部的样式规则(根据选择器的优先级或者源码顺序,同理,也可能被其他样式覆盖。
媒体查询本身不会影响到它里面选择器的优先级。 - 大多数情况下,整个样式表里的媒体查询只会复用少数几个断点。(几个比较固定的尺寸作为断点)
- 媒体特征
- min-width匹配视口大于特定宽度的设备,max-width匹配视口小于特定宽度的设备。它们被统称为媒体特征(media feature)。
- min-width和max-width是目前用得最广泛的媒体特征
- (orientation: landscape)——匹配宽度大于高度的视口。
- (orientation: portrait)——匹配高度大于宽度的视口
在媒体查询断点(例如min-width中的宽度单位)中推荐使用em单位。在各大主流浏览器中,当用户缩放页面或者改变默认的字号时,只有em单位表现一致。以px或者rem单位为断点在Safari浏览器里不太可靠。同时当用户默认字号改变的时候,em还能相应地缩放,因此它更适合当断点。媒体查询还可以放在<link>标签中。在网页里加入<link rel="stylesheet"media="(min-width: 45em)" href="large-screen.css" />,只有当min-width媒体查询条件满足的时候才会将large-screen.css文件的样式应用到页面。- 然而不管视口宽度如何,样式表都会被下载。这种方式只是为了更好地组织代码,并不会节省网络流量。
- 媒体类型
- 最后一个媒体查询的选项是媒体类型(media type)。常见的两种媒体类型是screen和print。
- 使用print媒体查询可以控制打印时的网页布局,这样就能在打印时去掉背景图(节省墨水),隐藏不必要的导航栏。当用户打印网页时,他们通常只想打印主体内容。
- 开发CSS的时候,通常在事后才会处理打印样式,而且只在需要的时候才会去考虑,但还是有必要思考用户是否想要打印网页的。
- 为了帮助用户打印网页,需要采取一些通用步骤。大多数情况下,需要将基础打印样式放在
@media print媒体查询内。- 使用display: none隐藏不重要的内容,比如导航菜单和页脚。当用户打印网页时,他们绝大多数情况下只关心网页的主体内容。
- 还可以将整体的字体颜色设置成黑色,去掉文字后面的背景图片和背景色。
- 大多数情况下,用通用选择器就能实现。配合important,这样就不必担心被后面的代码覆盖。
- 媒体查询的断点选择
- 有时候会忍不住想要根据设备选择断点。这个iPhone 7宽多少像素,那个平板设备宽多少像素,等等。不要总想着设备。市面上有成百上千中设备和屏幕分辨率,无法逐一测试。相反,
应该选择适合设计的断点,这样不管在什么设备上,都能有很好的表现。
- 有时候会忍不住想要根据设备选择断点。这个iPhone 7宽多少像素,那个平板设备宽多少像素,等等。不要总想着设备。市面上有成百上千中设备和屏幕分辨率,无法逐一测试。相反,
容器查询:媒体查询基于视口大小实现响应式设计,但是开发人员和浏览器厂商已经花了好几年时间来寻找更好的解决办法。很多开发人员希望得到的特性是容器查询(container queries),起初叫作元素查询(element queries)。这种查询不是响应视口,而是响应一个元素的容器的大小。
原则三:流式布局
- 响应式设计的第三个也是最后一个原则是流式布局(fluid layout)。流式布局,有时被称作液体布局(liquid layout),指的是使用的容器随视口宽度而变化。
- 它跟固定布局相反,固定布局的列都是用px或者em单位定义。固定容器(比如,设定了width: 800px的元素)在小屏上会超出视口范围,导致需要水平滚动条,而
流式容器会自动缩小以适应视口。 在流式布局中,主容器通常不会有明确宽度,也不会给百分比宽度,但可能会设置左右内边距,或者设置左右外边距为auto,让其与视口边缘之间产生留白。也就是说容器可能比视口略窄,但永远不会比视口宽。在主容器内部,任何列都用百分比来定义宽度(比如,主列宽70%,侧边栏宽30%)。这样无论屏幕宽度是多少都能放得下主容器。- 要习惯将容器宽度设置为百分比,而不是任何固定的值。
- 网页默认就是响应式的。没添加CSS的时候,块级元素不会比视口宽,行内元素会折行,从而避免出现水平滚动条。加上CSS样式后,就需要你来维护网页的响应式特性了。
- 在移动设备的流式布局里,表格的问题特别多。如果可以的话,建议在移动设备上用别的方式组织数据。
响应式图片
在响应式设计中,图片需要特别关注。不仅要让图片适应屏幕,还要考虑移动端用户的带宽限制。图片通常是网页上最大的资源。首先要保证图片充分压缩。还要避免不必要的高分辨率图片,而是否必要则取决于视口大小。也没有必要为小屏幕提供大图,因为大图最终会被缩小。
- 图片压缩(这个没啥好说,不是响应式设计也要压缩图片)
- 为合适的视口大小提供不同尺寸的图片,也叫响应式图片
响应式图片的最佳实践是为一个图片创建不同分辨率的副本。如果用媒体查询能够知道屏幕的大小,就不必发送过大的图片,不然浏览器为了适配图片也会将其缩小。媒体查询能够解决用CSS加载图片的问题,在不同尺寸的媒体查询断点中,为图片加载不同的url地址html里面的img标签,可以使用srcset属性来设置响应式图片- 其本质上也是一种媒体查询,不过它的查询在srcset属性上进行设置
- 它可以为一个img标签指定不同的图片URL,并指定相应的分辨率。浏览器会根据自身需要决定加载哪一个图片
- 现在大多数浏览器支持srcset。不支持的浏览器会根据src属性加载相应的URL。这种方式允许针对不同的屏幕尺寸优化图片。
- 其他图片技巧
- 图片支持原始懒加载loading=”lazy”(不过兼容性暂时不咋地)
响应式设计技巧
- Web设计师Brad Frost列举了一系列响应模式,可以访问https://bradfrost.github.io/this-is-responsive/patterns.html
- 响应式响应式,响应在哪?
- 你可以对同一个功能,在触发响应式断点时,切换为不同的组件,这是一种响应(一行面包导航和弹出层导航的切换)
- 不过这会增加代码量,优先切换样式可能会更好
- 你也可以对同一个组件应用不同的样式,以展示出不同的布局,这也是一种响应(一列、两列或者三列)
- 你可以对同一个功能,在触发响应式断点时,切换为不同的组件,这是一种响应(一行面包导航和弹出层导航的切换)
- 列布局
- 有时候,甚至不需要媒体查询,自然地折行就能实现响应式的列。
- 可以通过在Flexbox布局中使用flex-wrap: wrap并设置合适的flex-basis来实现。
- 还可以在网格布局中使用auto-fit或者auto-fill的网格列,在折行之前就可以决定一行放几个元素。
- 总是确保每个媒体查询都位于它要覆盖的样式之后,这样媒体查询内的样式就会有更高的优先级。
- 有时候移动端的样式可能很复杂,在较大的断点里面需要花费较大篇幅去覆盖样式。此时需要将这些样式放在max-width媒体查询中,这样就只对较小的断点生效,但是用太多的max-width媒体查询也很有可能是没有遵循移动优先原则所致。
max-width是用来排除某些规则的方式,而不是一个常规手段。 - 许多响应式设计遵循这种方法:
- 当设计要求元素并排摆放时,只在大屏上将它们摆放在一行。在小屏下,允许每个元素单独一行,填满屏幕宽度。这种方法适用于列、媒体对象,以及任意在小屏下容易拥挤的元素。
- 图片作为流式布局的一部分,请始终确保它不会超过容器的宽度。为了避免这种情况发生,一劳永逸的办法是在样式表加入规则
img { max-width: 100%; }。
网页响应式设计的结构实现方式千变万化。最终这些方式都会归纳为三大原则:移动优先、媒体查询、流式布局。
其他
- 特性查询(feature query):
@supports规则后面跟着一个小括号包围的声明。如果浏览器理解这个声明(在本例中,浏览器支持网格),它就会使用大括号里面的所有样式规则。如果它不理解小括号里的声明,就不会使用这些样式规则。 - margin的负值,可以使元素偏移,这个技巧在布局(例如列间隔)对齐时可能有用
- 例如在栅格布局中,内部元素使用padding左右来实现gap间距,但是一行的第一个和最后一个元素的无法对齐容器左右,此时容器也设置一个padding大小的负边距,来拉伸容器的宽度,使其对齐容器边界
- margin 是传统的、方向固定的属性(上、右、下、左),而 margin-inline 是基于逻辑方向的现代属性,它根据书写模式(如从左到右 LTR 或从右到左 RTL)自动映射到元素的“行内侧”外边距,使其更适应国际化布局