时间:4.11
题目:JS动画的基本原理(一)
主讲人:月影
1.动画的基本原理
- 定时器改变对象的属性
- 根据新的属性重新渲染动画
2.动画的种类
- Javascript动画:操作DOM、Canvas
- CSS动画:transition、animation
- SVG动画:SMIL
3.JS动画优缺点
- 优点:灵活度、可控性、性能
- 缺点:易用性
简单动画例子:
let rotation = 0;
requestAnimationFrame(function update() {
block.style.transform = `rotate(${rotation++}deg)`;
requestAnimationFrame(update);
});
另一个版本:
let rotation = 0;
let startTime = null;
const T = 2000;
requestAnimationFrame(function update() {
if(!startTime) startTime = Date.now();
const p = (Date.now() - startTime)/T;
block.style.transform = `rotate(${360 * p}deg)`;
requestAnimationFrame(update);
});
通用化:
function update({target}, count) {
target.style.transform = `rotate(${count++}deg)`;
}
class Ticker {
tick(update, context) {
let count = 0;
requestAnimationFrame(function next() {
if(update(context, ++count) !== false) {
requestAnimationFrame(next);
}
});
}
}
const ticker = new Ticker();
ticker.tick(update, {target: block});
Timing:
class Timing {
constructor({duration, easing} = {}) {
this.startTime = Date.now();
this.duration = duration;
this.easing = easing || function(p){return p};
}
get time() {
return Date.now() - this.startTime;
}
get p() {
return this.easing(Math.min(this.time / this.duration, 1.0));
}
}
class Ticker {
tick(update, context, timing) {
let count = 0;
timing = new Timing(timing);
requestAnimationFrame(function next() {
count++;
if(update(context, {count, timing}) !== false) {
requestAnimationFrame(next);
}
});
}
匀速运动:
2s 内向右匀速运动 200px
function update({target}, {timing}) {
target.style.transform = `translate(${200 * timing.p}px, 0)`;
}
const ticker = new Ticker();
ticker.tick(update,
{target: block},
{duration: 2000}
);
自由落体运动:
function update({target}, {timing}) {
target.style.transform = `translate(0, ${200 * timing.p}px)`;
}
const ticker = new Ticker();
ticker.tick(update, {target: block}, {
duration: 2000,
easing: p => p ** 2,
});
摩擦力:
function update({target}, {timing}) {
target.style.transform = `translate(${200 * timing.p}px, 0)`;
}
const ticker = new Ticker();
ticker.tick(update, {target: block}, {
duration: 2000,
easing: p => p * (2 - p),
});
平抛:
class Timing {
constructor({duration, easing} = {}) {
this.startTime = Date.now();
this.duration = duration;
this.easing = easing || function(p){return p};
}
get time() {
return Date.now() - this.startTime;
}
get op() {
return Math.min(this.time / this.duration, 1.0);
}
get p() {
return this.easing(this.op);
}
}
function update({target}, {timing}) {
target.style.transform =
`translate(${200 * timing.op}px, ${200 * timing.p}px)`;
}
旋转+平抛:
function update({target}, {timing}) {
target.style.transform = `
translate(${200 * timing.op}px, ${200 * timing.p}px)
rotate(${720 * timing.op}deg)
`;
}
贝塞尔轨迹:
function bezierPath(x1, y1, x2, y2, p) {
const x = 3 * x1 * p * (1 - p) ** 2 + 3 * x2 * p ** 2 * (1 - p) + p ** 3;
const y = 3 * y1 * p * (1 - p) ** 2 + 3 * y2 * p ** 2 * (1 - p) + p ** 3;
return [x, y];
}
function update({target}, {timing}) {
const [px, py] = bezierPath(0.2, 0.6, 0.8, 0.2, timing.p);
target.style.transform = `translate(${100 * px}px, ${100 * py}px)`;
}
const ticker = new Ticker();
ticker.tick(update, {target: block}, {
duration: 2000,
easing: p => p * (2 - p),
});