CSS flex 排版与动画 — 重学 CSS
同学们好,我是来自 《技术银河》的 💎 三钻 。
上一部分我们讲到了盒
、盒模型
和整个正常流
中的所有重要知识点和问题。这一部分我们来了解一下 Flex 排版的详细知识。
Flex 排版
在之前的《实现中学习浏览器原理》篇章中,其实已经有了比较详细的接触到 Flex 排版的知识。这里我们基本上是重新复习一下 Flex 的排版技术。
Flex 的排版逻辑还是分为三步:
收集
盒
进行计算
盒
在主轴方向的排布计算
盒
在交叉轴方向的排布
对 flex 排版来说,是没有文字的,所以说 flex 排版我们是收集所有的盒进行。因为 flex 它是可以调整排布的方向的,所以我们不会用正常的
top
、left
、bottom
、right
的体系去描述。而是用主轴和交叉轴去描述的。
分行
根据主轴尺寸,把元素分进行。
每加入一个元素到当前行,我们就会让它与行剩余的空间去做比较。
如果当前行已经满了,就创建一个新行,把新元素放到下一行。
若设置了
no-wrap
,则强制分配进入第一行。(到计算主轴的时候,我们再去处理这些溢出的部分)
计算主轴方向
找出所有 Flex 元素
把主轴方向的剩余尺寸按比例分配给这些元素
若剩余空间为负数,所有 flex 元素为 0,等比压缩剩余元素
Flex 里面有一个 Flex 属性的,Flex 为 1 就分一份,Flex 为 2 就分两份,如果我们这一行剩余空间是 300px,那么分一份的会分到 100px,而分两份的就会得到 200px。
如果剩余空间为负数,所有带 flex 属性的元素都会被置为 0。然后把剩余的那些元素做等比压缩。
计算交叉轴方向
根据每一个行最大元素尺寸计算行高
根据行高
flex-align
和item-align
,确定元素具体位置
CSS 动画
CSS 当中控制表现的无非就是三类:
控制元素位置和尺寸的信息
控制位置和最后实际看到的渲染信息
交互与动画的信息
接下来就和大家一起来学习另外的一些 CSS 的属性。
Animation
首先介绍一下 CSS 里面最直接的一些特性,先来讲讲 Animation
。Animation 它包含着两个部分:
使用
@keyframes
去定义动画的关键帧
首先我们可以使用
keyframes
这个 @ rule 来定义一个关键帧。然后使用from
和to
,它们里面定义的都是 CSS declaration(CSS 属性和值得声明)
使用
animation
属性去使用关键帧的部分
在具体的 CSS 规则里面我们可以设置 animation 这个属性。Animation 这个属性它又分为几个部分。这里我们给
div
标签定义了一个 animation 关键帧,也就是我们上面预定义的myKeyFrames
。然后我们给它定义了 5 秒,并且让它是一个infinite
的循环模式,infinte 就是说这个动画是无限循环的。
好我们看一下实际的例子是怎么样的:
这里的代码与我们刚刚讲到的是一样的,style 标签里面含有我们设定的 myKeyFrame
。然后给 div
标签加入了 animation
属性并且绑定了我们预设置的 myKeyFrame
关键帧。运行的效果如下:
如果我们打开 "控制台" 我们会发现这个元素的
background
属性是没有在更变的,但是如果我们去取这个元素的computed
属性的话,就会发现是一直在不断变化的。这个就是 CSS animation 的基本用法。
Animation 属性
接下来我们来详细看一下 Animation
有哪些属性,它的属性有 6 个组成的部分:
animation-name —— 时间曲线
animation-duration —— 动画的时长
animation-timing-function —— 动画的时间曲线
animation-delay ——动画开始前的延迟
animation-iteration-count —— 动画的播放次数
animation-direction —— 动画的方向
Keyframes 定义
Keyframes 的定义是可以使用% (百分比)
也可以使用 from to
。而 from
大致相当于 0%
,to
大致相当于 100%
。
每一个关键帧里面我们都是可以定义很多的属性。有一个常见的技巧就是在这个里面去定义 transition
而不是使用 animation 的 timing-function,来让这个值发生改变。
这样的话,每个关键帧之间的 timing-function 都可以用不一样的。不像 animation 中,一旦指了一个值,那么它的整个 timing-function 就确定了,也就没有办法分段去指定了。
Transition 使用
Transition 的使用与 animation 差不多,它也的属性一共有 4 条:
transition-property —— 要变换的属性
transition-duration —— 变换的时长
transition-timing-function —— 时间曲线
transition-delay —— 延迟
Cubic-bezier 三次贝塞尔曲线
Timing-function
它其实来自于一个三次贝塞尔曲线,我们所有的 timing-function 都跟三次贝塞尔曲线有相关。
我们可以通过 cubic-bezier.com 的网站开始了解 cubic bezier
。
我们先来看看,这个网站中最右边的这个线图。这个图的横轴
表示的是 时间 (TIME)
,而纵轴
表示的是 进展 (PROGRESSION)
。横轴的时间是一个比例的时间,单位是 % 百分比。而纵轴代表着属性变化的进展也是一个比例的进展,所以也是 % 百分比为单位的。两个轴的区间都是 0 到 1 (0% 到 100%)。
在这里我们可以随意移动 红点
和 绿点
这两个控制点,就可以得到对应的动画的曲线。
如果我们想让我们的动画在中间有一个回弹,我们可以把我们的控制点移动到大概像以下的位置。然后点击 "GO!",即可查看效果。
右边线图画出来的动画曲线,会被赋予我们红色的动画方块上,点击 "GO!"的时候,红色方块的动画就是与我们画的曲线图一致的。
这里我们可以看到,根据我们的曲线图,我们的红色方块确实在中间的位置回弹了一下,然后到达我们的重点。这里我们会注意到,最终到达终点的时间是一致的,无论我们定义的曲线是怎么样的,我们设置的 duration
动画时长是 1 秒,那整个动画的时间就是 1 秒。
而这个就是我们去设置三次贝塞尔曲线可以达到的效果。
这里我们还可以把这个回弹,超出这个对应的范围,我们可以看看这个效果:
看到上面的效果,我们可以让这个红色的方块弹过这个终点,然后再弹回来。这个就是我们三次贝塞尔曲线的定义。
在 CSS 动画当中,其实里面有几个内置的几个三次贝塞尔曲线的:
ease —— 是一个标准的缓动曲线,经历过无数前辈摸索出来的,也是一种最自然的曲线形态
linear —— 是一个直线,相当于退化为一个一次曲线,这个我们是没有必要去使用的
ease-in —— 缓动启动,往往是用于退出动画
ease-out —— 缓动停止,用于进入动画
ease-in-out —— 是
ease-in
和ease-out
的对称的动画
Cubic-bezier 是怎么运作的?
接下来我们详细看看 cubic-bezier 的知识点。
一次贝塞尔曲线
首先我们来看看这张图,上面有一个黑色实心的点在一条直线上移动。这个点从 P0 到 P1 沿着一条直线移动,然后下方有一个 t
代表着时间从 0 到 1 的过程。这个我们把它叫做 "一次贝塞尔曲线 (Linear Curves)
",也就是一条直线。
二次贝塞尔曲线
二次贝塞尔曲线一共有三个固定的点 、、和 ,而 是一个控制点。
在二次贝塞尔曲线中,我们有两个中间点 和 ,当时间从 0 到 1 的过程中,这两个点可以在相对应的线上移动。同时 和 可以连接起来建立一条直线(也就是绿色的直线)。
点可以在 和 连接的直线之上移动,最终描绘出一条一次贝塞尔曲线(灰线)
点可以在 和 连接的直线之上移动,最终描绘出一条一次贝塞尔曲线(灰线)
B 点代表着我们移动的黑点,从 为起点, 为终点,但是条件是这个黑点不能走出 和 连接的绿线之外,最终描绘出来的就是一条二次贝塞尔曲线(红线)
还是听不懂是怎么回事?其实就是多条线连接了起来,最后这些线的边缘就会画出一个曲线。如果大家小时候有玩过软线画,可能就对这个概念有似曾相识的感觉。
三次贝塞尔曲线
在三次贝塞尔曲线中,我们多加了一个控制点。然后再两条绿线生成的蓝点之间再做一次贝塞尔插值,这样我们就得到了一个三次贝塞尔曲线。我们也可以看到最后绘制出来的是一条红色的轨迹。
在这里我们发现贝塞尔曲线是用 t
控制的,但是实际上在我们刚刚看的网站中看到贝塞尔曲线里面,横轴是时间,对我们这里的图就是 X,进展就是 Y(也就是每个点的纵向坐标)。
所以贝塞尔曲线的定义,往往是不可以用于直接计算贝塞尔曲线的形状。如果我们想得出关于 X
和 Y 的方程和坐标是需要使用 "
牛顿积分法
"的。而牛顿积分法是一个比较著名的数值算法,如果我们想要去实现牛顿积分的话,我们可以直接去抄 webkit 里面对应的代码,然后把它写成 JavaScript 是非常的简单的。但是如果我们想自己推理出来这个东西,就有点复杂了。不过我们程序员毕竟是以工程为主,所以我们没有必要去追求数学的证明过程。这个留给我们的科学家来做就好了。
贝塞尔曲线拟合
贝塞尔曲线理论上,我们可以用控制点可以拟合任何的形状。
比如说我们想拟合一个弧形,或者很像一个半圆形。但是我们究竟能否做出一个半圆形呢?其实我们也不是特别的知道。但是贝塞尔曲线有两样东西是一定能够拟合出来的:
直线 —— 只要把两个控制点都放在两个点的直线上
抛物线 —— 在数学上可以证明是贝塞尔曲线可以完美拟合的
这里呢,圆形是证明了是不可直接拟合的。因为我们想一想,三个点怎么能拟合出圆形呢?但是我们可以用贝塞尔曲线去分段拟合圆弧。这个虽然没有完美的拟合圆弧的方案,但是通过分段分细,我们的圆弧越细,我们对圆弧的拟合的成绩就越好。以上这些就是数学上的知识了,但是无论如何贝塞尔曲线是有强大的拟合能力的。
Color 颜色
接下来我们开始讲颜色。首先关于颜色我们要了解一些基本的知识。
一般来说我们都会觉得,平常我们看到的光是一个单色光。但其实在自然界中,我们极少会见到真正的单色光。大部分时候光都是多个颜色的光混合在一起的。
光的颜色其实就是光波的长度(Wavelength)。我们人类眼睛有一个可见的范围,就是 400 纳米到 760 纳米之间的光。有的人他天赋异禀,就有可能超出这个范围一些。有的人看到的红色更多一些,有的看的紫色更多一些。所以每个人的视觉上会有偏差。
所以自然界中是没有纯色的光,有一些光它会比较接近人的纯色,比如说激光,就是我们小时候玩过的激光笔,按着一个按键就会有红色或者绿色的激光。(喜欢撸猫的同学,估计也经常会玩到这个玩意)
激光笔的光强度是非常的高的,所以它照到地上也可以产生一个很集中的亮点。但是如果是彩色手电筒就不行了,因为它是用玻璃把光过滤了一下,所以它的光的纯度和强度都没有那么高。小时候玩激光笔的时候,都有家长经常会警告我们,激光笔千万不能照射到眼睛上,这样是非常危险的,也就是因为它的光强度非常的高。
这里我们要有一个认识,就是我们大部分看到的光都是混色的混合光。
但是我们为什么可以看到这么多五颜六色呢?这里我们就要从 "三原色" 开始讲起了。
CMYK 与 RGB
我们从小讲到颜色都会说到红
、黄
、蓝
三种原色,那么为什么是红黄蓝三种颜色呢?那又为什么红黄蓝三种颜色就能跳出所有其他的颜色和光呢?我们不是说光是不同的波长吗?理论上在不同的波长是调不出来的事吗?因为光再怎么混,它是同时到达我们的眼球的。
其实呀,原因是在我们的眼睛里面的。我们的眼睛里面有用于感觉颜色和感受强光的视锥细胞,那么视锥细胞我们只有 3 种。这三种视锥细胞分别能感应
红
、绿
、蓝
三原色的光。这个也是我们RGB
颜色的一个来历。
所以不管自然界的光有多复杂,最后给我们眼睛的刺激都只有三种。所以我们只要把红、绿、蓝三色配成一定的比例最后就能看成相应的颜色。
有些同学就觉得奇怪了,小时候学的三原色是红、黄、蓝,长大之后写代码的时候就变成了红、绿、蓝(统称为 RGB)。我想问问大家有没有觉得这个事情有点奇怪呢?
我相信有些同学就会刨根问底去了。
我来告诉大家答案吧:
"其实我们小时候学的红、黄、蓝,它不是红黄蓝"。等等。。。
让我慢慢道来哈。
其实应该是
品红
、黄
和青
。而这三个颜色正好是红绿蓝的补色,也就是说我们色谱中的三原色和我们小时候调色的时候使用的三原色,它们是一个补色的三原色。那么为什么我们小时候调的是补色的三原色呢?因为我们小时候调的是颜料,颜料它其实是吸收对应的光的。而光是混合起来增强对应的光的。所以我们想用颜料调出所有对应的颜色,我们就要使用红黄蓝三原色。
在印刷行业里面都会使用 品红
、青
、黄
三原色,也叫 CMY
色系。我们也会发现在印刷行业用的不是 CMY 颜色,而是 CMYK
颜色。那又为什么是 CMYK 颜色呢?
其实也非常好理解,因为彩色的颜料相对比较贵,如果我们想调出黑色的话,那就需要把 CMY 颜色都混合到一起才能出这个黑色。不过黑色本来就是一种非常便宜的油墨,而品红、青和黄都是非常昂贵的油墨。这就意味着我们基本上不会用这三种贵的油墨混合起来调出一个特别便宜的黑色油墨。(这一听就是一个亏本的生意呀!)
所以说在印刷行业用的基本都是 CMYK 颜色。这样他们就可以最大限度,使用最低成本的去节约油墨。凡是需要颜色变暗一些的,我们就会使用这个 CMYK 里面的黑色的墨去调配。
在我们在使用 CMY 的时候,虽然它会有很多等价的写法,但是有一个基本的原则,就是最大化的去使用 K。
HSL 与 HSV
这里我们就讲完颜色的基本原理了,但是在编程中我们就会发现 RGB 这个颜色或者是 CMYK 的颜色都并不好用。因为它们是跟我们对颜色的认知的直觉是不一致的。但是跟我们的生理结构是一致的。所以考虑到我们程序员的感受而考虑,又有了一种新的颜色的谱系:HSL
和 HSV
。
在 W3C 的标准中,关于 HSL 和 HSV 还有一次争论到底用哪个更好。
HSL 和 HSV 的 H
和 S
都是一个比较固定的颜色表示。
H
就是 Hue
的缩写,表示的是 色相
。首先我们有 6 种颜色拼成了一个色盘,然后我们可以通过 Hue 去指定一个在色盘中的角度,然后就可以指定这个颜色的色相。
S
就是 Saturation
的缩写,表示的是 纯度
。比如颜色里面的杂色的数量,颜色中的 S
越高,这个颜色就越鲜艳越纯。
最后一个就是 L
或者 V
。首先 L
就是 Lightness
的缩写,表示的是 亮度
。而 V
就是 Value
,可以翻译成色值
,但是在理论上它真正表示的是 Brightness
也就是 明度
。
HSL 和 HSV 在很多的时候实际上几乎是完全等价的。但是唯一不一样的就是:
Value 到 100% 的时候颜色就会变成一个纯色
Lightness 就不一样了,它是一个上下对称的,Lightness 到 0% 的时候他是黑色,而到 100% 的时候就是纯白色。所以我们取一个颜色的纯色我们是需要取 Lightness 的中间值的。
两者就是一个逻辑的不一样,有些人认为 HSV 更符合人类的认知
W3C 最后选择了
HSL
,因为L
是一个对称的。不过两种各有各的道理,大家喜欢用哪个都是可以的。但是 W3C 里面限定了我们只能用HSL
,但是如果我们想用HSV
也是没有问题的,因为它们是可以互转。所以在颜色取值的时候,我们还是可以选择更喜好的那个的。当然我们也可以不使用HSL
的函数,我们直接把它算成RGB
也是可以的。
说到这里,无论如何其实 HSL 或者是 HSV 都是一个非常重要的颜色表示法。它重要在哪呢?我们接下来用一个例子来讲述一下。
这里我们加入了一个按钮,并且用 JavaScript 来动态改变按钮的边框、背景和阴影颜色。
我们可以看到实际运行的效果和我们想要的是一致的。按钮的颜色在不断的变化,但是这个按钮本身颜色的明暗关系
和颜色的鲜艳程度关系
都在改变颜色的同时得以保留。
这正是 HSL 颜色作为一种语义化的颜色的存在意义。如果我们想把整个的页面换一个颜色的风格的话,我们只需要统一的去更换
色相 (Hue)
即可。加上 CSS 的变量或者是 JavaScript 的操作的配合的时候,我们就有非常弹性的操作空间。
关于颜色我们就讲到这里啦~
Render 绘制
最后我们来讲讲 CSS 中的绘制这一块。关于绘制我们要讲到三类的绘制:
几何图形
border
box-shadow
border-radius
文字
font
text-decoration
位图
background-image
在 CSS 的属性里面关于绘制的属性非常的繁杂,有各式各样关于绘制不同的规定。但是真的要比较起来,其实也无非就这三类。
几何图形
几何图形
是由我们的一些 CSS 属性去规定的,比如 border
、box-shadow
、border-raius
等,它们会产生一些几何图形。也有一些同学会用它们来做一些想不到的效果,winter 老师叫这些为 "黑魔法"。比如说有一些 "CSS 高手" 用这个 border 去拼各种各样的图形,还有看过拼出五角星的。
不过现代的 CSS 当中有更加直接和通用的办法,所以就不再推荐大家使用这些原生自带的属性去做它们定义以外的事情(所谓的"黑魔法")。当然当时没有现代 CSS 那么发达的时候,这些同学们的研究确实是值得敬佩的。
文字
文字
的属性 font
、text-decoration
这些都会产生一些不同的图形。文字它的 font 这一类属性,既会影响前面说的 layout (排版) 效果,也会影响我们绘制的效果。
在文字的 font 字体文件里面,规定了每个文字的字形叫 glyph
。这个字形其实与我们的矢量图是差不多的,它最后也会被以类似于矢量图的方式画到图片上。
位图
最后一类就是位图
,它典型的代表就是 background-image
。当然我们的 img
标签也会产生图片。
图形绘制
关于浏览器是如何去完成绘制的,其实我们在《实现中学习浏览器原理》的文章当中做了一个非常简单的方块绘制。但是实际上是会依赖到一个图形库。比如说手机上就会依赖 Skia
图形库,在 Windows 上还有一个依赖 GDI
的版本。那么在更底下的都是使用 Shader
去绘制的。
为了让大家更直观的去理解 Shader 是如何绘制的,我们来看看一下一段 Shader 的代码:
首先我们用一个
Fragment Shader
来绘制一个
Fragment Shader
大概是由一个main
函数中的输入和输入来定义的它的输入并不像我们 JavaScript 一样写在参数里面,它输入就是
FragCoord
这个变量FragCoord
有一个 xy 的坐标输入就是一个
FragColor
只要我们把这个
FragColor
算对就可以了那么一个 main 的执行过程就是根据坐标去算一个颜色
这里 main 只算一个点过程,因为我们可以用 GPU 去进行加速,所以这个 main 函数会瞬间被执行数万遍,最后把这个颜色给算出来。
最后绘制出来的就是 Vue
的 logo:
这里是一个 canvas 并不是一个图片。我们这里用了它来代替了 Vue 原本的图片。大家不用深入的去搞懂
frag
的代码,只需要有一个概念 Shader 是怎么绘制的即可。
应用小技巧
这里给大家带来一个小技巧,刚刚在讲到几何图形的时候说到,不太推荐大家使用现成属性去拼各式各样的图形。根据 winter 老师说到 "我看到最夸张的,大家有用 gradient 拼出一个超级玛丽"。
就算老师们都不推荐我们去这么做,我也认同,确实这些真的属于 "Overkill",也就是老生常谈的 "杀鸡焉用牛刀"一个意思。但是我还是很好奇这个大神,所以我去找了这段代码。
上面这个图,不是游戏里面的 gif,是确确实实用 CSS 的 gradient 属性做出来的。请相信你的眼睛!
虽然说在我们平时开发业务的时候这个真的没有什么用途,但是我还是要为这位大神点个赞!就算没有什么卵用,起码他证明了 CSS 确实是很强大的,学前端真的可以无所不能的!
好奇的同学想看这位大神的代码,可以点击这个传送门前往~
好,回归正题!回归正题!回归正题!
这些做法其实都是属于奇技淫巧,并没有给大家带来多大的用处,也没有足够的通用性,在性能的表现上也是非常的差。
所以这里就推荐一个 "官招
" (就是官方推荐的招式),就是使用 data url + svg
。这里因为我们可以使用 background-image
,所以我们可以把这个 svg
变成 data uri
。
在所有我们需要用到图片的地方,我们都可以使用
inline
的svg
去描绘这个图片。因为 svg 是一个专业的矢量图的格式,所以任何的图形基本上都是难不倒它的。顶多就是会难倒我们的 "射鸡师" 而已。
这里我们来一个例子:
这个例子绘制出来的就是一个椭圆形:
这个椭圆也可以用于 CSS 的各个地方,不管你想要什么样的图形,因为我们的 svg 里面支持 path。所以说我们可以想要什么图形就要什么图形,最后都可以绘制出来。
我是来自《技术银河》的三钻:
"学习是为了成长,成长是为了不退步。坚持才能成功,失败只是因为没有坚持。同学们加油哦!下期见!"
博主开始在B站每天直播学习, 欢迎过来《直播间》一起学习。
我们在这里互相监督,互相鼓励,互相努力走上人生学习之路,让学习改变我们生活!
学习的路上,很枯燥,很寂寞,但是希望这样可以给我们彼此带来多一点陪伴,多一点鼓励。我们一起加油吧! (๑ •̀ㅂ•́)و
彩蛋来啦!
今天是特别的日子,是程序员们的日子,在 1024 的今天我给同学们送上祝福:
程序员朋友们,在这个属于自己的节日里请写一个特别的程序:输入快乐的语言,编译动人的文字,插入美丽的模块,运行有趣的过程,压缩无聊的心理,打断烦躁的心情,释放快乐的代码,执行快乐的循环。
为了感谢在这一年里面所有关注我的粉丝们,还有未来将要关注我的同学们,这里我无私贡献出我的个人珍藏:69 张高清 3K 4K 程序员必备鼓励师墙纸!~
是不是很想要?是不是有点小鸡冻呀?🤣 可能现在很多同学都在大喊 "我要怎么领取呀!"。不要着急哈。
领取很简单,博主这一年来一直坚持写高质量文章也不容易嘛。你们的关注和点赞就是最大的原动力。这里有个小小的要求,希望各位同学可以满足我一下。
扫一扫下面的二维码,关注我的公众号《技术银河》,然后回复 "1024赐予我力量吧" 即可得到下载链接哦!是不是很简单?但是这个简单的事情能给予博主极大的力量哦!再次感谢我的粉丝们的持续支持,还有感谢即将关注我的同学们!
希望看到这里也为博主这篇文章来个三连:点赞,收藏,关注。
版权声明: 本文为 InfoQ 作者【三钻】的原创文章。
原文链接:【http://xie.infoq.cn/article/f37cf000fed7285bcecd814a3】。文章转载请联系作者。
评论