先放场景示例
const manager = new TweenManager({duration, start, end, easing})
while(manager.next()) {
await TweenManager.frame()
const value = manager.currentValue
// do something
}
复制代码
引子
写js动画是件挺麻烦的事.需要缓动函数,需要定时器,递归的话需要考虑边界条件.
而且缓动函数用起来也很别扭,参数多,又难记.t,b,c,d每次用都得去看下注释.
最近用到一个浏览器的API,createTreeWalker
,这个方法返回一个TreeWalker
对象.可以用来遍历dom树.
用法如下
while(tree.nextNode()){
const dom = tree.currentNode
// dosomething
}
复制代码
一时间思维发散.如果按照这种设计方式来搞一个缓动函数的管理类,应该可以简化Tween的使用方式,省的每次去记API.
帧管理
实现上一节提到的还不够.写动画麻烦还在于requestAnimationFrame
这个API用起来也是挺麻烦的.需要递归调用.
比如一个回到顶部的动画
function backTop(duration) {
const doc = document.body.scrollTop ? document.body : document.documentElement;
const start = doc.scrollTop;
const stamp = Date.now()
function step() {
let currentStep = Date.now() - stamp
if (currentStep >= duration) {
doc.scrollTop = 0
return
}
doc.scrollTop = Tween.Linear(currentStep, start, -start, duration)
requestAnimationFrame(step)
}
step()
}
复制代码
写法是比较繁琐的.仔细考虑,其实递归只是期望定时执行而已.如果是其他语言.一个sleep就搞定了.
JavaScript
虽然没有sleep,但有了async/await
,我们也可以实现一个.
const sleep = time => new Promise(resolve => setTimeout(resolve, time));
(async () => {
console.log('1');
await sleep(1000);
console.log('2');
})();
复制代码
而requestAnimationFrame
其实可以与sleep(16)
互相代替,那我们可以实现一个frame
方法.
const frame = () => requestAnimationFrame ? new Promise(requestAnimationFrame) : sleep(16);
(async () => {
await frame();
console.log('1 frame');
})();
复制代码
具体实现
我们期望的用法确定了.接下来实现.其实代码不多.
const Tween = {
Linear: function (t, b, c, d) {
return c * t / d + b;
},
}
const dftOption = {
duration: 300,
start: 0,
end: 0,
easing: Tween.Linear,
}
class TweenManager {
get distance() {
return this.$options.end - this.$options.start
}
get now() {
return Date.now ? Date.now() : new Date().getTime()
}
get currentStep() {
return this.now - this.stamp
}
get currentValue() {
const {distance, currentStep} = this
const {duration, easing, start} = this.$options
return easing(currentStep, start, distance, duration)
}
constructor(opt = {}) {
this.$options = {...dftOption, ...opt}
this.stamp = this.now
}
next() {
return this.$options.duration > this.currentStep
}
static sleep(time = 0) {
return new Promise(resolve => setTimeout(resolve, time))
}
static frame() {
return requestAnimationFrame ? new Promise(requestAnimationFrame) : TweenManager.sleep(16)
}
}
复制代码
使用示例
同样是回到顶部的动画,可以简化成这样.
function backTop(duration) {
const doc = document.body.scrollTop ? document.body : document.documentElement;
const start = doc.scrollTop;
const manager = new TweenManager({duration, start, end: 0, easing: Tween.Linear});
while (manager.next()) {
await TweenManager.frame();
doc.scrollTop = manager.currentValue;
}
console.log('done');
}
复制代码
好像没少多少???
嗯,心智负担降了一丢丢.多少还是方便一点了吧...
溜了溜了...