前言
16 年年底入坑前端,原因之一就是被网页中各种炫酷的动画震撼到了,想着我要是也能自己写出那些炫酷的动画就好了!当然动画看起来只是简单的平移旋转,放大缩小,其实知识点还是不少,今天闲来无事,就来回顾一下我在前端世界中接触到的那些动画。本人水平有限,本文参考了很多大神的文章,结合了自己一些见解,可能会有一些错误的表述,欢迎指正!
浏览器架构
对于前端动画,浏览器基本是我们的唯一舞台,自然需要了解一下相关基础知识:
首先浏览器是一个多线程多进程的架构,
线程:
JS 引擎线程
: 调用 V8 处理 js 相关脚本GUI 线程
: 负责解析网页,处理 HTML, CSS, 计算 dom 元素的样式和位置,最后渲染出来
进程:
Browser process
: 负责协调其他线程,可以当做React
的Root
组件,
主要负责地址栏,书签栏,前进后退按钮,网络请求等通用部分的工作
Renderer Process
: 一个 tab 内的网页渲染,打开一个 tab 就新开一个子进程,超过一定的数量就会合并,从这里可以看出,为啥开很多个 tab 页,会很卡,因为占了很多的内存,当然有 类似One Tab
的这种插件帮你优化GPU Process
: 负责 3D 绘制,对,我们常说的transform GPU 加速
就是它的工作
我们可以点击浏览器右上角 三个点
-> 更多工具
-> 任务管理器
可以看到浏览器的一个进程情况
这里只做一个引入,详细的知识可以看 这篇文章, 写的非常好。对于本文,我们需要了解
GUI 线程
和 GPU 进程
这两个
网页渲染
一个网页,由三剑客 (html + css + js) 组成
1 | <head> |
浏览器在拿到 DOM 结构后,会分成若干个图层,根据编写的 css:
- 绘制颜色,边框,颜色,大小等,这个叫
重绘 (repaint)
- 计算每个图层节点的样式,生成对应的位置,这个叫
重排 (reflow)
- 所以图层完事之后,浏览器会交给
GPU 进程
合并所有图层,最终显示在页面上
重绘和重排会涉及到重新计算,相对来说很耗费浏览器性能,在手机上更明显,这里就引出了第一个前端动画需要注意的点,动画无非就是对 DOM 进行 缩放,平移,放大 等操作,这些理论上会触发重绘和重排,会造成卡顿,所以想让动画很流畅,我们要使用 不影响文档流,不造成重绘重排的属性,也就是 transform3d
和 will-change
属性,使用了这两个属性,可以让当前 dom 生成一个独立的图层,从而不影响其他图层,同时触发 GPU 加速
.
过渡效果
一个状态到另一个状态,比如将元素位置向右移动 20px, 如果没有过渡效果,立即生效,这样是没有动画效果的,iOS 系统之所有流畅,不 “卡”, 除了强大的处理器之外,还有一个很重要的原因就是动画,动画自然且舒服,给人一种很流畅的感觉,所以一个合理的过渡效果很重要,在 CSS3
, 我们通过 transition
属性 指定元素的过渡效果,
CSS3
内置了几种过渡效果:
那么如果不想用内置的,想自定义一下,比如想模拟物体撞击反弹,这时候就需要用到 贝赛尔曲线,
贝塞尔曲线有四个任意点构成一条曲线,CSS3
的表示如下:
1 | transition: all 0.3s cubic-bezier(0, 0.66, 0.31, 1.18); |
对应的效果如下
当然,可以看到我这里的自定义贝塞尔曲线有点不自然,我们使用过渡效果应该考虑现实世界中真实物体的运动规律,不然就会很鬼畜。
动画的不同种类
讲到这,好像才点题了,没办法,说一下相应的知识点便于我们更好的理解,这里我结合实际例子,结合我看到的网上各路大神的文章,讲解下不同的动画种类的应用,不讲解太详细的代码,讲一下大致的思路
平移动画 (轮播图)
平移大概有三种方式
1 | margin-left: 10px; |
1 | position: absolute; |
1 | transform: translateY(10px); |
上面三种代码都是往右平移 10px, 但是前两种会触发重排,性能没有第三种好,所以第三种,也是主流的一种平移方式,当然,想让元素有动画的效果,还需要加上 transition
, 告诉元素的过渡时间,这个基本接触过前端的都知道
1 | transform: translateY(10px); |
我们还可以改写成这样,触发 GPU
3d 加速
1 | transform: translate3d(10px, 0, 0); |
而轮播图的原理就是将所有元素并排在一起,依次向左或向右平移
缩放动画 (封面图)
缩放大概有两种方式
1 | width: 150%; |
1 | transform: scale(1.5); |
同理,第一种也会触发重排,所以我们通常使用第二种 transform
的方式
旋转动画 (音乐播放器专辑封面)
旋转我们可以指定按照水平还是 X,Y 轴旋转,比较简单,没啥好说的
1 | transform: rotate(20deg); |
旋转动画比较常见的是在 Web 端的音乐播放器,有一个一直旋转的封面图,另一个场景也比较常见,就是 loading 加载中,原理都一样,rotate
转就完事了
1 | @keyframes rotate { |
自定义组合动画
自定义动画我们使用 CSS3
的 animation
或者 js
的 animate
api, 这里我们以 CSS3
的 animation
为例,文字从屏幕右边飞过来,并且放大,
首先使用 @keyframes
声明动画的执行规则,然后 使用 animation: [animateName]
执行动画
1 | @keyframes test { |
渐变动画 (按钮点击水波效果)
CSS3
中常见的渐变 (Gradients) 有两种
- 线性渐变(Linear Gradients)- 向下/向上/向左/向右/对角方向
- 径向渐变(Radial Gradients)- 由它们的中心定义
线性渐变比较常见的应用之一就是进度条
1 | background: repeating-linear-gradient( |
径向渐变一个 比较炫酷的例子, 在很多组件库会见到,鼠标点击按钮时,会有水波一样的效果,核心代码如下
1 | position: absolute; |
具体细节可以查看张鑫旭大神的文章:
滤镜动画 (霓虹灯)
现在的相机都提供了很多花哨的滤镜,CSS3
也不例外,我们通过 filter: [value]
指定滤镜,场景的滤镜有:
1 | filter: blur(5px); // 高斯模糊 |
如果只想改变元素的背景区域,可以使用 backdrop-filter, 属性值一样
比较常见的是 blur
和 grayscale
, 前者可以用来做 IOS
的 毛玻璃
效果,当然,backdrop-filter
也是可以的
1 | .mask { |
grayscale
最典型的例子就是最近的疫情,国家哀悼日,很多网站你会发现都变灰了,其实用的就是这个属性
1 | html { |
可以看到,结合 transition
, 我们可以在动态改变 filter
滤镜值的时候,让它有过渡效果,从而实现动画。
另外再张鑫旭大神的博客中还看到个 好玩的动画, 结合 hue-rotate
滤镜 和 linear-gradient
线性渐变 实现了炫酷的霓虹灯动画
一段普通的文本
1 | <h1>李金珂 666</h1> |
加上一段普通的样式
1 | h1 { |
一个杀马特风格的文本就出来了
接着配合
hue-rotate
改变文本的色相-webkit-background-clip: text
裁减掉背景,就像 PS 的魔棒一样,保留文字-webkit-text-fill-color: transparent
让文本变镂空,这样就能看到文字背后的彩色背景
一个酷炫的霓虹灯动画就完成了
1 | @keyframes hue { |
不同的滤镜组合还能实现各种酷炫的效果,更多例子可以点击 这里
SVG 动画 (文字描边)
其实 SVG
是很复杂且强大的一个东西,我自己也只掌握到了一点皮毛,相信大家接触 SVG
还是因为字体图标,iconfont
, 写页面的时候,会用到,要么是设计师给你切好了 SVG
的图片,要么是去网上找,当做字体库引进来,
下面这段代码
1 | <svg |
对应的图标长这样
当然,这是设计师用工具生成的 SVG
代码,我发现身边很多开发也不懂这些代码具体的意思,SVG
是可缩放的矢量图形,可以很灵活的改变大小,形状,还可以添加滤镜,内置的图形有:
https://developer.mozilla.org/zh-CN/docs/Web/SVG/Tutorial/Basic_Shapes
rect
矩形circle
圆形ellipse
椭圆line
线polyline
多边形path
路径
其实 path
是最有东西的,可以绘制任意的形状,所以字体图标生成之类的工具,就是对 path 的 编辑,从而生成自定义的形状,
其中 d
属性,用来描述具体的路径,fill
就相当于 background-color
, stroke
表示绘制颜色,stroke-width
表示 绘制粗细,想象一下就好比你用一支铅笔,在绘制你自己的图形,
然后我们看到上面示例中有很多神秘代码:
1 | d="M39.226 27.5v10.665C39.225 39.179 38.355... |
其中大写字母代表 绝对定位
, 小写字母代表 相对定位
, 可以理解成一个指令,每个指令具体的含义如下
1 | M = moveto 移动到 |
每个指令具体的应用可以查看 这篇文章
现在我们就使用 path
写三个字,做一个描边的动画
1 | <svg> |
三个炫酷的大字映入眼帘,这就是 SVG
的强大,现在我们让他动起来,实现一个描边动画,要实现描边动画,需要用到两个关键属性
1 | stroke-dasharray:定义描边的虚线长度,如果提供奇数个,则会自动复制该值成偶数 |
将这两个 值 设置为一样的值,且相对大一点,这时你会发现刚才辛辛苦苦写的字看不见了
1 | stroke-dashoffset: 2000; |
最后我们让 他的 dashoffset
从 2000
回到 0
, 这样看起来就是一个描边的效果
1 | path { |
Canvas 动画 (帧动画)
canvas
不用多说,不会还有前端不知道 canvas
吧 ? 不会吧不会吧!
canvas 就是一个画布,我们在上面绘制各种图形和动画,默认支持 GPU
的加速,性能比较好,有 2d context
和 3d context (WebGL)
, 作为一个带专生,WebGL
需要一些数学功底,我没怎么接触过所以就不展开说明了,这里主要说明下 2d 的 canvas.
众所周知,动画片的原理就是有一组连续播放的静态图片组成,每一张图片,我们叫做一帧,把每一帧快速播放,人眼会产生视觉暂留,从而形成 “动画”
所以在 canvas
里绘制动画,需要用到 js
相关的 timer api, 让图片快速 “动起来”
setInterval
计时器requestAnimationFrame
根据浏览器的刷新频率自动调整动画的时间间隔
当然,都 2020
年了,现在做动画基本都是用 requestAnimationFrame
, 性能更好,动画表现更流畅,这里用一个简单的帧动画伪代码来演示一下:
前面说了,我们需要将图片一张一张的绘制在 canvas 上,需要用到 ctx.drawImage
API,
每次绘制将上一次的图片清除,需要用到 ctx.clearRect
1 | <body> |
3D 动画 (翻书)
话不多说,先来一张图感受下,炫酷就完事了
首先声明结构
1 | <div class="container"> |
container
声明 3d 舞台page
表示一页书
1 | .container { |
transform-style: preserve-3d
表示当前以 3D
的方式进行过渡,现在以 rotateY(-150deg)
Y 轴旋转的时候就有 3d 的效果了
页面滚动动画
页面滚动动画比较常见,他不会在网页加载的时候立即执行,而是等用户滚到一定的位置时触发,视觉和交互的体验更棒,
以 Ant Design Landing 网站为例,滚动到指定区域,会触发对应的滚动动画,看起来非常酷炫
原理也比较简单,我们可以编写想执行动画区域的 css
, 写好动画,监听滚动条高度,匹配对应的区域的 offsetTop
, 如果快到达时,给当前区域添加 class
即可,
另外一种方案,使用浏览器的 API intersectionObserver
监听目标元素是否在可视范围内
https://developer.mozilla.org/zh-CN/docs/Web/API/IntersectionObserver
1 | var intersectionObserver = new IntersectionObserver(function (entries) { |
一些优秀的第三方库
- Animate.css 写好了各种类型的动画 css, 粘贴复制即可
- Ant Motion 一个 React 的 动画库
结语
以上是我个人平时学习或者看到的一些动画的总结,在微软都放弃 IE 的今天,我们再也不用考虑那恶心的浏览器兼容,和 hack, 如果今天还有人问你各种 IE 的问题,纯属脑瘫,没有了 IE 的束缚,我们可以放心大胆的使用各种新特性,实现各种酷炫的动画,一个恰到好处的动画,能给网页增色不少,当然我认为动画的实现手段都是次要的,最主要的是想象力和创造力,你说是吗?