大纲:
前言
动画实现方式
如何实现关键帧动画和滚动驱动的动画
作为前端开发者,精通动画技术不仅是一项必备技能,更是展现对 CSS 掌握的关键。
微信小程序支持使用this.animate动画方法可以实现关键帧动画和滚动驱动的动画,那么今天主要介绍如何实现关键帧动画和滚动驱动的动画,为项目创建更丝滑、更炫酷且高性能的动画效果。
前期
从动画类型出发,动画的实现方式主要分为:逐帧动画、补间动画、SVG动画等。
逐帧动画是在时间帧上逐帧绘制帧内容,由于是一帧一帧的画,所以逐帧动画具有非常大的灵活性,几乎可以表现任何想表现的内容。
实现逐帧动画方式:
- GIF实现
- CSS实现(animation - step)
- JS+DOM实现
- JS+canvas实现
补间动画,又叫做Tween动画或者关键帧动画,指的是人为设定动画的关键状态,也就是关键帧,而关键帧之间的过渡过程只需要由计算机处理渲染的一种动画形式。
实现补间动画常见方式:
- CSS3 Animation:通过animation(除steps()以外的时间函数)属性在每个关键帧之间插入补间动画。
- CSS3 Transition:区别于animation,transition只能设定初始和结束时刻的两个关键帧状态。
- 利用JavaScript实现动画:例如JavaScript动画库或框架
在前端,实现动画的方式主要有:
- javascript动画
-
- setInterval/setTimeout
- requestAnimationFrame
- SVG(可伸缩矢量图形);
- CSS3 transition;
- CSS3 animation;
javascript + DOM
主要是通过setInterval/setTimeout、requestAnimationFrame方法的持续循环绘制动画帧,动态的改变显示属性(比如:DOM样式,cnvas绘图数据)调用改变某个元素的css样式以达到元素样式变化的效果,示例如下:
1、setInterval 实现
xml
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<style type="text/css">
#box1 {
width: 200px;
height: 200px;
background: #ccc;
}
</style>
</head>
<body>
<div id="box1">box1</div>
<script>
let box1 = document.getElementById('box1');
let left = 0;
let timer = setInterval(function(){
if(left<window.innerWidth-200){
box1.style.marginLeft = left+'px';
left ++;
}else {
clearInterval(timer);
}
},16);
</script>
</body>
</html>
存在的问题
javascript 实现动画通常会导致页面频繁性重排重绘,消耗性能,一般使用在桌面端浏览器,在移动端上会有明显的卡顿。
2、requestAnimationFrame 实现
ini
<script>
let box1 = document.getElementById('box1');
let left = 0;
window.requestAnimationFrame = window.requestAnimationFrame || window.mozRequestAnimationFrame || window.webkitRequestAnimationFrame || window.msRequestAnimationFrame;
requestAnimationFrame(step);
function step() {
if(left<window.innerWidth-200){
box1.style.marginLeft = left+'px';
left ++;
requestAnimationFrame(step);
}
}
</script>
setInterval与requestAnimationFrame对比效果如下图:
为什么setInterval的执行效果会比较快呐?
1.setInterval/setTimeout 时间不准确,因为它的执行取决于主线程执行的时间。
2.如果计时器频率高于浏览器刷新的频率,即使代码执行了,浏览器没有刷新,也是没有显示的,出现掉帧情况,不流畅。
而 raf 解决了 setTimeout 动画带来的问题:
1.浏览器刷新屏幕时自动执行,无需设置时间间隔 和 setTimeout 一样是 n 毫秒之后再执行,但这个 n 毫秒,自动设置成浏览器刷新频率,浏览器刷新一次,执行一次,不需要手动设置; 浏览器不刷新,就不执行,没有排队掉帧的情况。
2.高频函数节流 对于 resize、scroll 高频触发事件来说,使用 requestAnimationFrame 可以保证在每个绘制区间内,函数只被执行一次,节省函数执行的开销。 如果使用 setTimeout、setInterval 可能会在浏览器刷新间隔中有无用的回调函数调用,浪费资源。
JS+canvas 动画
canvas 作为H5 新增元素,是借助Web API 来实现动画的
示例:
xml
<!DOCTYPE html>
<html>
<head>
<style>
</style>
</head>
<body>
<h1>canvas</h1>
<canvas id="canvas" width="700" height="550"></canvas>
<script>
let canvas = document.getElementById("canvas");
let ctx = canvas.getContext("2d");
let left = 0;
let timer = setInterval(function(){
ctx.clearRect(0,0,700,550); // 清除背景
ctx.beginPath();
ctx.fillStyle = "#ccc";
ctx.fillRect(left,0,100,100);
ctx.stroke();
if(left>700){
clearInterval(timer);
}
left += 1;
},16);
</script>
</body>
</html>
借助了定时重绘的方式,比如:setInterval,setTimeout, requestAnimationFrame,重复绘制实现动画的效果。
SVG
SVG动画由SVG元素内部的元素属性控制,一般通过一下几个元素控制:
- : 用于控制动画延时
- :对属性的连续改变进行控制
- :颜色变化,但用就能控制
- :控制如缩放、旋转等几何变化
- :控制SVG内元素的移动路径
示例:
xml
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
<style>
*{
margin:0;
padding:0;
}
</style>
</head>
<body>
<svg id="box" width="800" height="400" xmlns="http://www.w3.org/2000/svg" version="1.1">
<rect x="" y="" width="100" height="100" fill="rgb(255,0,0);" stroke="" stroke-width="">
<set attributeName="x" attributeType="XML" to="100" begin="4s"/>
<animate attributeName="x" attributeType="XML" begin="0s" dur="4s" from="0" to="300"/>
<animate attributeName="y" attributeType="XML" begin="0s" dur="4s" from="0" to="0"/>
<animateTransform attributeName="transform" begin="0s" dur="4s" type="scale" from="1" to="2" repeatCount="1" />
<animateMotion path="M10,80 q100,120 120,20 q140,-50 160,0" begin="0s" dur="4s" repeatCount="1" />
</rect>
</svg>
</body>
</html>
比较:
SVG的一大优势是含有较为丰富的动画功能,原生绘制各种图形、滤镜和动画,并且能被js调用。html是对dom的渲染,那么svg就是对图形的渲染。 但是,另一方面元素较多且复杂的动画使用svg渲染会比较慢,而且SVG格式的动画绘制方式必须让内容嵌入到HTML中使用。CSS3的出现让svg的应用变得相对少了。
CSS3 transition
transition是过度动画。但是transition并不能实现独立的动画,只能在某个标签元素样式或状态改变时进行平滑的动画效果过渡,而不是马上改变。
xml
<!DOCTYPE html>
<html>
<head>
<style>
#base {
width: 100px;
height: 100px;
background-color: red;
border: 5px solid #5daf34;
transform: scale(2);
transition: 2s ease-in 0ms;
transition-property: width, height, background-color, border, transform;
/* transition: width 2s ease-in, height 2s ease-in, background-color 2s ease-in, border 2s ease-in, transform 2s ease-in; */
}
#base:hover {
width: 10px;
height: 10px;
background-color: rgb(216, 200, 200);
}
#container {
width: 100px;
height: 100px;
background-color: rgb(0, 255, 34);
}
</style>
</head>
<body>
<h1>transtion</h1>
<!-- 方式1: 通过伪类当你将鼠标悬停在 #base 上时,你应该能够看到期望的动画效果。 -->
<div id="base">box</div>
<!-- 方式2:通过js -->
<div id="container">container</div>
<script>
const container = document.querySelector('#container')
container.style = `
transition:unset;
transform:scale(1,1) rotate(0deg);
transition-property:unset;
transform-origin:50% 50% 0;
backgroundColor:blue;`
setTimeout(() => {
container.style = `
transition:1000ms ease-in 0ms;
transform:scale(2) rotate(45deg);
transform-origin:50% 50% 0;
background-color:yellow;
transition-property:transform,background-color;`
})
setTimeout(() => {
container.style = `
width: 200px;
transition:1000ms linear 0ms;
transform:scale(1,1) rotate(0deg);
transition-property:width;
transform-origin:50% 50% 0;`
},1000)
</script>
</body>
</html>
CSS3 animation
animation 算是真正意义上的CSS3动画。通过对关键帧和循环次数的控制,页面标签元素会根据设定好的样式改变进行平滑过渡。而且关键帧状态的控制是通过百分比来控制的。
示例:
xml
<!DOCTYPE html>
<html>
<head>
<style>
#box {
width: 100px;
height: 100px;
background-color: red;
animation: identifier 3000ms linear 0ms;
animation-fill-mode: forwards;
}
@keyframes identifier {
0% {
rotate: 0;
background-color: blue;
transform: scale(1);
}
50% {
rotate: 45deg;
background-color: yellow;
transform: scale(2);
}
100% {
transform: scale(2);
background-color: green;
rotate: 90deg;
}
}
</style>
</head>
<body>
<h1>animation</h1>
<div id="box">box</div>
</script>
</body>
</html>
animation 与 transtion比较:
- transtion 只能定义开始状态和结束状态,不能定义中间状态,而animation动画则可以通过设置百分比进行多个状态间变换
- transtion 一般需要添加样式类或更改状态(如悬停)来触发,animation 是自动开始的
- animation 更负责一下,可以允许你按照实际需求添加很多的keyframes来创建动画,可自动触发,可循环
trantion:A to B
animation:A to B to C
关键帧动画和滚动驱动的动画
关键帧动画
关键帧动画是一种通过在动画序列中定义关键帧(keyframes)来指定动画效果的技术。每个关键帧定义了动画的特定状态,而动画系统会自动在这些状态之间进行插值,以形成平滑的动画效果。
实现关键帧的手段有很对种,css3 animation 和 transtion 都可实现关键帧动画,Web animations API 也提供了实现关键帧的方法,并且定义了Keyframe Format规范,实现方式如下:
xml
<!DOCTYPE html>
<html>
<head>
<style>
.container {
height: 300px;
overflow-y: scroll;
position: relative;
}
.square {
background-color: deeppink;
width: 100px;
height: 100px;
margin-top: 100px;
position: absolute;
bottom: 0;
}
.stretcher {
height: 600px;
}
.con1 {
background-color: aliceblue;
margin-bottom: 50px;
}
.squ1 {
animation-name: rotateAnimation;
animation-duration: 1s;
}
@keyframes rotateAnimation {
0% {
transform: rotate(90deg);
}
80% {
transform: rotate(360deg);
}
100% {
transform: rotate(0deg);
}
}
.con2 {
background-color: rgb(62, 93, 119);
}
</style>
</head>
<body>
<div class="container con1">
<div class="square squ1">css</div>
<div class="stretcher str1"></div>
</div>
<div class="container con2">
<div class="square squ2">web api</div>
<div class="stretcher str2"></div>
</div>
<script>
// 使用Web Api 实现
const squ2 = document.querySelector(".squ2");
const keyframes1 = [
{ rotate: "90deg",},
{ rotate: "360deg", offset: 0.8},
{ rotate: "0deg", offset: 1}
];
const optiions = {
duration: 1000,
iterations: 1, // 重复次数
}
squ2.animate( keyframes1, optiions );
</script>
</body>
</html>
其中,offset用来指定每个关键帧的偏移量,也就是css实现方式中@keyframes 中百分比,没设置offset则关键帧之间匀速间隔。
滚动驱动动画
CSS 滚动驱动动画 指的是将动画的执行过程由页面滚动 进行接管,也就是这种情况下,动画只会跟随页面滚动的变化而变化 ,滚动多少,动画就执行多少,时间不再起作用。
实际效果:
图一 滚动动画实际效果图
那么如何实现这种通过滚动驱动的动画效果呐?
首先想到的可能是通过JS 监听滚动事件实现动画效果,但是当动画过于复杂时,根据滚动距离修改动画会进行大量的计算,可能会存在如下问题:
- 现代浏览器在单独的进程上执行滚动,因此只能异步传递滚动事件
- 由于是异步传递,因此主线程动画容易出现卡顿
因此,为了解决滚动卡顿的问题,CSS 滚动驱动的动画应运而生了,从 chrome 115开始,开始支持 CSS 滚动驱动动画(CSS scroll-driven animations),有了它,几乎以前任何需要JS监听滚动的交互都可以纯 CSS 实现了,就是这么强大。
如何改变动画的时间线呐?那就需要用到这个核心概念了:animation-timeline,表示动画时间线(或者叫时间轴),用于控制 CSS 动画进度的时间线,是必不可少的一个属性。
css
/* 关键词 */
animation-timeline: none;
animation-timeline: auto;
/* 命名时间线 */
animation-timeline: --timeline_name;
/* 滚动时间线 */
animation-timeline: scroll();
animation-timeline: scroll(scroller axis);
/* 视图时间线 */
animation-timeline: view();
animation-timeline: view(axis inset);
滚动驱动的动画,不是基于动画的时间轴进展来进行动画处理,而是基于滚动的时间轴,滚动时间轴分为命名时间进度线(scroll-timeline-name)和匿名滚动进度时间线(scroll()方法)。
scroll() 可以传递2个参数,分别是滚动容器scroller 和 滚动方向 axis,scroller支持一下几个关键字:
- nearest:使用最近的祖先滚动容器(默认)
- root:使用文档视口作为滚动容器。
- self:使用元素本身作为滚动容器。
axis 表示滚动方向,支持一下关键字:
- block:滚动容器的块级轴方向(默认)。
- inline:滚动容器内联轴方向。
- y:滚动容器沿 y 轴方向。
- x:滚动容器沿 x 轴方向。
因为scroller仅支持3个关键词,不能通过指定任意选择器,在架构复杂的场景中,自动查找就不适用了,并且最近的祖先元素还要受到绝对定位的影响,这时就需要我们手动指定滚动容器,因次官方提供了scroll-timeline-name ,本文也主要讲述该方式实现滚动动画。
具体做法是,在滚动容器 上添加一个属性scroll-timeline-name,这个属性值必须以--开头,就像 CSS 变量一样,还可以通过scroll-timeline-axis设置滚动方向,此时的animation-timeline就不用默认的scroll()了,而是改用前面设置的变量,示意如下:
css
@keyframes animate-it { ... }
/*滚动容器*/
.scroller {
scroll-timeline-name: --my-scroller;
scroll-timeline-axis: inline;
}
.animationEle {
animation: animate-it linear;
animation-timeline: --my-scroller;
}
这样就可以将指定滚动容器的滚动进度映射到动画上,非常自由,上述图一的css代码实现如下:
xml
<!DOCTYPE html>
<html>
<head>
<style>
.container {
height: 300px;
overflow-y: scroll;
position: relative;
background-color: aliceblue;
margin-bottom: 50px;
}
.square {
background-color: deeppink;
width: 100px;
height: 100px;
margin-top: 100px;
position: absolute;
bottom: 0;
}
.stretcher {
height: 600px;
}
.con1 {
scroll-timeline-name: --squareTimeline;
}
.squ1 {
animation-name: rotateAnimation;
animation-duration: 1s;
animation-timeline: --squareTimeline;
}
@keyframes rotateAnimation {
form {
transform: rotate(90deg);
}
to {
transform: rotate(360deg);
}
}
</style>
</head>
<body>
<div class="container con1">
<div class="square squ1"></div>
<div class="stretcher str1"></div>
</div>
</body>
</html>
除了使用css设置外,还提供了对应的api,实现如下:
xml
<!DOCTYPE html>
<html>
<head
<style>
.container {
height: 300px;
overflow-y: scroll;
position: relative;
}
.square {
background-color: deeppink;
width: 100px;
height: 100px;
margin-top: 100px;
position: absolute;
bottom: 0;
}
.stretcher {
height: 600px;
}
.con2 {
background-color: rgb(62, 93, 119);
}
</style>
</head>
<body>
<div class="container con2">
<div class="square squ2"></div>
<div class="stretcher str2"></div>
</div>
<script>
// 使用Web API 实现
const con2 = document.querySelector(".con2");
const squ2 = document.querySelector(".squ2");
const timeline = new ScrollTimeline({
source: con2,
axis: "block",
});
const keyframes1 = [
{ rotate: "90deg"},
{ rotate: "360deg"}
];
const options = {
timeline,
rangeStart: "20%", // 动画开始范围
rangeEnd: "50%", // 动画结束范围
}
squ2.animate(keyframes1, options);
</script>
</body>
</html>
由此,可以简单方便的实现滚动驱动的动画,动画效果非常丝滑流畅,无卡顿。
但是遗憾的是,受浏览器版本限制,在移动端开发中,ios的safari是不支持使用的,受到很大限制,因此下面将介绍一种无浏览器显示,也能实现滚动驱动动画的方式。
anime.js 实现关键动画、滚动驱动的动画
anime.js介绍
Anime.js是一个灵活的轻量JavaScript动画库,支持CSS属性、JS对象、DOM属性和SVG。压缩后仅6.2K,且不依赖任何第三方库,加载迅速。它提供了简单易用的 API,可以用来处理各种动画需求,包括元素的移动、旋转、缩放等。
Anime.js 主要特点包括:
- 轻量级: Anime.js 体积小巧,使其成为在项目中引入的理想选择,不会对网页加载性能产生过大影响。
- 简单易用: 具有直观的 API,使得创建动画变得非常容易,即使对于不熟悉动画编程的开发者也能够轻松上手。
- 强大的动画功能: 提供了丰富的动画选项,支持各种缓动函数、关键帧、循环、回调等。
- 兼容性: 兼容多种浏览器,包括 Chrome、Firefox、Safari 等主流浏览器。
使用方式
安装 Anime.js
你可以通过 npm 或 yarn 安装 Anime.js:
csharp
npm install animejs
# 或
yarn add animejs
然后,你可以通过 ES6 模块的方式导入:
javascript
import anime from 'animejs';
或者直接通过 CDN 引入:
xml
<script src="https://cdn.jsdelivr.net/npm/animejs"></script>
创建动画
下面是一个基本的例子,演示如何使用 Anime.js 创建一个简单的动画:
php
anime({
targets: '.box',
translateX: 500,
easing: 'easeInOutQuad',
duration: 1000,
complete: function(anim) {
console.log('Animation complete!');
console.log(anim);
}
});
在这个例子中,我们选择了类名为 box 的元素,将其在 X 轴上平移 250px,使用了 easeInOutQuad 缓动函数,在 1000 毫秒内完成动画,并在动画完成时触发了一个回调函数。
参数介绍
anime(options) 函数用于创建动画。options 参数是一个包含动画属性的对象,一些常 用的属性如下:
属性 | 类型 | 默认值 | 必填 | 说明 |
---|---|---|---|---|
targets | Array/String/Element | 是 | 应用动画的目标元素或元素集合,可以是一个 CSS 选择器、DOM 元素或元素数组 | |
css 属性 | 否 | 动画过程中要改变的 CSS 属性,例如:translateX, translateY, rotate, scale, opacity | ||
duration | Number | 否 | 动画持续的时间,单位:ms | |
delay | Number | 否 | 动画开始前的延迟时间,单位:ms | |
endDalay | Number | 否 | 动画结束后延迟时间,单位:ms | |
easing | String | easeOutElastic | 否 | 动画缓动函数 |
loop | Number/Boolean | 否 | 动画的循环次数,可以是一个数字或 true(无限循环) | |
autoplay | Boolean | true | 否 | 是否在创建时自动播放动画 |
回调函数 | Function | 否 | 动画执行过程中的回调函数,begin, complete, update等 | |
keyframes | Array | 否 | 定义动画的关键帧 |
实现关键帧动画
以下是一个使用 keyframes 的示例:
yaml
anime({
targets: '.box',
keyframes: [
{ translateY: -100, rotate: 0, duration: 1000 },
{ translateY: 0, rotate: 360, duration: 2000 },
{ translateY: 100, rotate: 0, duration: 1000 }
],
loop: true
});
上述代码所示,我们可以为一个或一组元素对象实现关键帧动画。
keyframes关键帧与 CSS 中的 @keyframes 规则类似,并且可以为每项定义动画持续时间,使用 keyframes 属性能够提供更灵活、复杂的动画效果,允许你在不同的时间点定义不同的样式,从而实现更具创意和变化的动画。
实现滚动驱动的动画
anime() 是AnimeJs的主要方法,创建和启动动画,并返回一个动画实例animation。通过提供了一系列的实例方法进行控制、配置和操作动画。
常用的动画实例方法有:
- play():播放动画
- pause():暂停动画
- restart():重新启动动画
- reverse():反转动画的播放方向
- seek(time):将动画设置到指定的时间点(以毫秒为单位)
- finished:一个Promise,在动画完成时解析
滚动驱动的动画实现,就是使用animation.seek()方法,动画不进行自动播放,通过监听滚动距离,将动画指定到特定地点。
示例:
xml
<!DOCTYPE html>
<html>
<head
<style>
.container {
height: 300px;
overflow-y: scroll;
position: relative;
}
.square {
background-color: deeppink;
width: 100px;
height: 100px;
margin-top: 100px;
position: absolute;
bottom: 0;
}
.stretcher {
height: 600px;
}
.con3 {
background-color: rgb(62, 93, 119);
}
</style>
</head>
<body>
<div class="container con3">
<div class="square squ3"></div>
<div class="stretcher str3"></div>
</div>
<script>
// 使用 animeJs 实现
const con3 = document.querySelector(".con3");
const squ3 = document.querySelector(".squ3");
// 设置动画触发的起始和滚动位置
const startPosition = 0
const endPosition = 100;
const animation = anime({
targets: squ3,
keyframes: [
{ rotate: 0, duration: 0, easing: "linear" },
{ rotate: 90, duration: 1000, easing: "linear" }
],
autoplay: false // 是否自动播放
})
function handleScroll() {
var scrollPosition = con3.scrollTop; // 获取滚动容器的滚动位置
requestAnimationFrame(()=>{
console.log('Scroll Position:', scrollPosition);
const progress = (scrollPosition - startPosition) / (endPosition - startPosition);
animation.seek(animation.duration * progress);
})
}
</script>
</body>
</html>
效果如下:
简述实现原理
Anime.js 的实现方式就是属于补间动画,实现原理涉及到使用 JavaScript 操作 CSS 属性,并使用 requestAnimationFrame(RAF)来实现流畅的动画效果。
动画的执行主要分为2部分,分别是:创建元素的动画对象,执行动画。
创建元素的动画对象流程图:
每一项animation包含的属性有animatable、delay、duration、endDelay、property、tweens,animatable为DOM元素,tweens为缓动动画列表。
执行动画流程图:
结语
动画的实现方式有很多种,通常在不同的项目需求中,我们可以选择更合适的动画创建方式实现我们需要的效果。本文主要讲述了借助animeJs如何实现我们的动画效果以及简要介绍了实现原理,结合animeJs的keyframes、动画属性和动画实例对象方法,可以实现各种酷炫的动画效果,增强我网页动态效果。
animeJs功能很强大,但是在V3版本中也存在一些缺点,比如:transform相关的2d、3d属性不支持(scale、scale3d)等,但是可以使用对应的scaleX,scaleY等代替,毕竟是不花钱的,也许V4商用版本是支持的