前端动画原理与实现
@LeeGenD
概念
- 根据时间的变化,连续地改变元素的样式造成的动态效果
基本要素
- 动画时长:
- 动画进程:
- 缓动函数(easing):
- 元素样式函数:
缓动函数
让运动看起来更自然
线性缓动函数

function easing(p) {
return p;
}
曲线缓动函数

function easing(p) {
return Math.pow(p,4);
}
正弦缓动函数

function easing(p) {
return Math.sin(0.5 * Math.PI * p);
}
贝塞尔曲线缓动函数
一种只需要很少的控制点就能生成复杂平滑曲线的方法

任选三个控制点A、B、C并连接

D点为AB上的任意一点
贝塞尔曲线缓动函数
一种只需要很少的控制点就能生成复杂平滑曲线的方法

当D点运动时,取AD:AB=BE:BC

连接DE,并取点F,使DF:DE=AD:AB
贝塞尔曲线缓动函数
一种只需要很少的控制点就能生成复杂平滑曲线的方法

找到所有的F点,则为贝塞尔曲线

多维贝塞尔曲线
AE:AB = BF:BC = CG:CD = EH:EF = FI:FG = HJ:HI
贝塞尔曲线缓动函数
#div1 {
transition-timing-function: linear|ease|ease-in|ease-out|ease-in-out|cubic-bezier(n,n,n,n);
}




ease
cubic-bezier(0.25, 0.1, 0.25, 1.0)
ease-in-out
cubic-bezier(0.42, 0, 0.58, 1.0)
ease-in
cubic-bezier(0.42, 0, 1.0, 1.0)
ease-out
cubic-bezier(0, 0, 0.58, 1.0)
缓动函数示例
速查表:http://easings.net/zh-cn

元素样式函数
改变元素样式,形成动画
基础位置移动
/*
* 以下例子中我们默认:
* 最大运动距离S为200px
* 缓动函数为线型缓动函数,即e=p
*/
var S = 200;
function easing(p) {
return p;
}
基础位置移动
/**
* x=S*t/T
* 求导后 v=S/T 即速度恒定
*/
function animation(p) {
return S * p;
}
匀速运动
基础位置移动
匀速运动
基础位置移动
/**
* x=S*(t/T)^2
* 求导后 v=2S/(T^2)*t 即匀加速
*/
function animation(p) {
return S * p * p;
}
匀加速运动
基础位置移动
匀加速运动
基础位置移动
function animation(p) {
return S * p * (2-p);
}
匀减速运动
基础位置移动
匀减速运动
基础位置移动
function animation(p) {
return S * Math.sin(2 * Math.PI * p);
}
简谐振动
基础位置移动
简谐振动
组合位置移动
斜线运动
组合位置移动
抛物线运动
组合位置移动
正弦线运动
组合位置移动
圆周运动
帧动画
动态改变每一帧显示的图片,形成动画效果
var imgList = ['./sprite/stand/1.png',
'./sprite/stand/2.png',...]
var listSize = imgList.length;
function animation(p) {
var index = parseInt((listSize - 1)* p);
return imgList[index];
}
帧动画
动态改变每一帧显示的图片,形成动画效果

效果示例
JS动画
-
DOM动画
-
canvas动画
定时器
setTimeout/setInterval
/**
* @param {function} callback 需要延迟执行的函数
* @param {number} time 延迟执行时间
* @returns {number} 作为一个唯一的标识符.你可以
将该值作为参数传给 window.clearTimeout() 来取消这
个回调函数。
*/
function setTimeout(callback, time) {};
requestAnimationFrame
/**
* @param {function} callback 需要执行的函数
* @returns {number} 作为一个唯一的标识符.你可以
将该值作为参数传给 window.cancelAnimationFrame()
来取消这个回调函数。
*/
function requestAnimationFrame(callback) {};
- 由于浏览器中js是单线程的,而setTimeout/setInterval只有在线程空闲时才会执行,所以延迟时间并不准确
- 优化并行的动画动作,把能够合并的动作放在一个渲染周期内完成,如JS动画和CSS动画/变换或SVG SMIL动画,从而呈现出更流畅的动画效果。
- 页面最小化,或者被Tab切换了,将停止requestAnimationFrame,减少CPU,内存的压力
动画对象封装
/**
* @description
* 动画对象
* @param {number} duration 运动时间
* @param {function} progress 运动函数
* @param {function} easing 缓动函数
*/
function Animation(duration, progress, easing) {
this.duration = duration || 1000;
this.progress = progress || function(e) {};
this.easing = easing || function(p) {
return p
};
}
Animation.prototype = {
start: function(callback) {
var startTime = new Date();
var _this = this;
var runAnimation = function() {
var p = (new Date() - startTime) / _this.duration;
p = p > 1 ? 1 : p;
var e = _this.easing(p);
_this.progress(e);
if (p == 1) {
callback && callback();
} else {
window.requestAnimationFrame(runAnimation);
}
}
runAnimation();
}
}
动画对象封装
// 匀速运动(示例)
new Animation(150, function(e) {
var x = 200 * e;
var y = 0;
bullet.style.transform = 'translate(' + x + 'px,' + y + 'px)';
});
动画对象封装
// 先旋转,然后匀速运动(示例)
var a1 = new Animation(1000, function(e) {
ele.style.transform = 'rotate(' + 360 * e + 'deg)';
});
var a2 = new Animation(1000, function(e) {
ele.style.transform = 'translate(' + 200 * e + 'px,0)';
});
a1.start(function() {
a2.start();
});
示例:子弹击出

子弹匀速运动
子弹速度骤降,匀速运动
木板向后旋转
子弹自由落体
击中木板
击中铁板
示例:子弹击出
子弹匀速运动
子弹速度骤降,匀速运动
木板向后旋转
子弹自由落体
击中木板
击中铁板
// 子弹匀速运动
var bulletFly1 = new Animation(150, function(e) {
var x = 200 * e;
var y = 0;
bullet.style.transform = 'translate(' + x + 'px,' + y+ 'px)';
});
// 子弹匀速运动2
var bulletFly2 = new Animation(750, function(e) {
var x = 200 + 300 * e;
var y = 0;
bullet.style.transform = 'translate(' + x + 'px,' + y+ 'px)';
});
// 木板倒下
var woodenWallFall = new Animation(200, function(e) {
woodenWall.style.transform = 'rotate(' + 90 * e + 'deg)';
});
// 子弹落地
var bulletFall = new Animation(400, function(e) {
var x = 200 + 300 - 50 * e;
var y = 80 * e * e;
bullet.style.transform = 'translate(' + x + 'px,' + y+ 'px)';
});
bulletFly1.start(function() {
bulletFly2.start(function() {
bulletFall.start();
});
woodenWallFall.start();
});
示例:子弹击出
子弹匀速运动
子弹速度骤降,匀速运动
木板向后旋转
子弹自由落体
击中木板
击中铁板
css3动画
-
transition
-
keyframes
transition
/**
* transition-property: none | all | property;
* transition-duration: time;
* transition-timing-function: linear|ease|ease-in|ease-out|ease-in-out|cubic-bezier(n,n,n,n);
* transition-delay: time;
*/
#id {
transition: property duration timing-function delay;
}
/* transition使用示例 */
.box {
width: 50px;
height: 50px;
background-color: #ade;
transition: all 2s;
-moz-transition: all 2s;/* Firefox 4 */
-webkit-transition: all 2s;/* Safari 和 Chrome */
-o-transition: all 2s;/* Opera */
}
.box.active {
width: 300px;
background-color: #ead;
}
transition
keyframes
@keyframes animationname {keyframes-selector {css-styles;}}
值 | 描述 |
---|---|
animationname | 动画名称 |
Keyframes-selector | 动画帧的时长的百分比 |
css-styles | CSS 样式属性 |
/* 元素匀速向下移动 */
@keyframes mymove
{
from {top:0px;}
to {top:200px;}
}
keyframes
#id{
animation: name duration timing-function delay iteration-count direction;
}
值 | 描述 |
---|---|
name | 动画名称 |
duration | 动画时长 |
timing-function | 速度曲线(linear/ease/ease-in/其他) |
delay | 延迟时间 |
iteration-count | 运行次数 (n/infinite) |
direction | 方向(normal/alternate) |
keyframes
/* keyframes使用示例 */
.box {
animation: cleanTable 5s infinite;
}
@keyframes cleanTable {
0% {
top: 0px;
background: #fff;
}
16.6% {
top: 160px;
background: #ccc;
}
33.3% {
top: 30px;
background: #999;
}
50% {
top: 130px;
background: #666;
}
66.6% {
top: 60px;
background: #333;
}
83.3% {
top: 100px;
background: #000;
}
100%{
top: 0px;
background: #fff;
}
}
其他
/* 告诉浏览器将要发生变化的属性是什么,并使用GPU来渲染变化 */
#id {
transition: transform 0.3s;
}
#id:hover {
will-change: transform;
}
#id:active {
transform: scale(1);
}
/* keyframes动画结束监听 */
dom.addEventListener('webkitAnimationEnd', function(){
// 动画完成回调
});
will-change
webkitAnimationEnd
动画性能优化
减小reflow与repaint
-
reflow:页面上元素的位置是联动的,当我们调解一个元素的大小或者坐标值,可能会触发其它元素位置的变化,称为回流或重排(reflow)
-
repaint:当元素的外观改变,如颜色、背景色等,需要重新绘制,称为重绘(repaint)

减小reflow与repaint
-
尽量使用css的transform
-
tansform可以用来改变元素位置、大小、旋转等
-
tansform不会引发其它元素的reflow
-
-
尽量为js动画元素添加absolute、fixed的定位
-
使用canvas

尽量使用CSS3动画
-
浏览器会对CSS3动画进行优化
-
避免多余的JS逻辑代码
-
适用于简单动画
/* CSS3 keyframes动画 */
@keyframes animationname {keyframes-selector {css-styles;}}
/**
* CSS3 transition动画
* transition-property: none | all | property;
* transition-duration: time;
* transition-timing-function: linear|ease|ease-in|ease-out|ease-in-out|cubic-bezier(n,n,n,n);
* transition-delay: time;
*/
#id {
transition: property duration timing-function delay;
}
3D硬件加速
-
对需要运行动画的元素,开启GPU渲染
element.style {
transform: translate3d(-10px,-10px,0);
}
element.style:hover {
will-change: transform;
}
使用raf
优化并行的动画动作,把能够合并的动作放在一个渲染周期内完成,如JS动画和CSS动画/变换或SVG SMIL动画,从而呈现出更流畅的动画效果。
页面最小化,或者被Tab切换了,将停止requestAnimationFrame,减少CPU,内存的压力
requestAnimationFrame
Q&A
前端动画原理与实现
By leegend
前端动画原理与实现
- 355