本次解析将分为2篇文章,当前是第二篇,第一篇在这里
另外,为了能更好的理解这个库,个人写了一个此库的压缩版,实现了核心的功能(主要也是为了更好理解核心功能),内容更少方便阅读,
地址在这里
继续上一篇,先把结构图拉过来:
// anime主体
function anime(params){
// 定义instance 也是最终返回值
let instance = createNewInstance(params);
// 外部API 从当前位置开始执行动画
instance.play = function() {}
// 配置 startTime 和 engineTime(关键)
instance.tick = function(t) {}
// 对当前engineTime进行判断,确定动画方案(关键)
function setInstanceProgress(engineTime) {}
// 计算动画当前位置 并且赋值(关键)
function setAnimationsProgress(insTime){}
// 直接跳到参数time的时间所在的位置
instance.seek = function(time) {}
// 外部API 暂停
instance.pause = function() {}
// 外部API 反转
instance.reverse = function() {}
// 外部API reset
instance.reset = function() {}
// 外部API 重新开始
instance.restart = function() {}
/*...*/
return instance
}
- setAnimationsProgress(省略了一些配置的定义)
这个函数接受一个参数,就是当前位置所消耗时间(动画起始点),然后在里面计算出每一个动画目标的位置,并且赋值
// 计算动画当前位置 并且赋值
function setAnimationsProgress(insTime) {
/* ... */
// 这个while逐个计算当前实例中的每个动画的当前位置(通过时间和算法)
while (i < animationsLength) {
/* ... */
// 消耗的时间占总持续时间的比例 在起点终点之间
const elapsed = minMaxValue(insTime - tween.start - tween.delay, 0, tween.duration) / tween.duration;
// 通过算法计算当前进度
const eased = isNaN(elapsed) ? 1 : tween.easing(elapsed, tween.elasticity);
/* ... */
// 遍历每一个到达点执行
for (let n = 0; n < toNumbersLength; n++) {
let value;
const toNumber = tween.to.numbers[n];
const fromNumber = tween.from.numbers[n];
if (!tween.isPath) {
// 计算当前具体位置
value = fromNumber + (eased * (toNumber - fromNumber));
} else {
// 进行SVG path计算
value = getPathProgress(tween.value, eased * toNumber);
}
/* ... */
numbers.push(value);
}
/* ... */
if (!isNaN(n)) {
// 组合单位 '135.546'+'px'
if (!b) {
progress += n + ' ';
} else {
progress += n + b;
}
}
/* ... */
// 组合结果 'translateX('+'135.546px'+')`
setTweenProgress[anim.type](animatable.target, anim.property, progress, transforms, animatable.id);
anim.currentValue = progress;
i++;
}
// 遍历结果,逐个target赋值
const transformsLength = Object.keys(transforms).length;
if (transformsLength) {
for (let id = 0; id < transformsLength; id++) {
if (!transformString) {
const t = 'transform';
// 配置兼容性
transformString = (getCSSValue(document.body, t) ? t : `-webkit-${t}`);
}
// 设置style
instance.animatables[id].target.style[transformString] = transforms[id].join(' ');
}
}
// 记录当前位置所对应的时间
instance.currentTime = insTime;
// 设置进度
instance.progress = (insTime / instance.duration) * 100;
}
剩下的就是一些操作函数了:
- instance.seek
// 直接跳到参数time的时间所在的位置
instance.seek = function(time) {
setInstanceProgress(adjustTime(time));
}
- instance.pause
// 外部API 暂停
instance.pause = function() {
const i = activeInstances.indexOf(instance);
// 删除activeInstances 后续engine中找不到便不会执行
if (i > -1) activeInstances.splice(i, 1);
instance.paused = true;
}
- instance.reverse
// 外部API 反转
instance.reverse = function() {
toggleInstanceDirection();
startTime = 0;
lastTime = adjustTime(instance.currentTime);
}
- instance.restart
// 外部API 重新执行
instance.restart = function() {
instance.pause();
instance.reset();
instance.play();
}
- instance.reset
// 外部API reset
instance.reset = function() {
const direction = instance.direction;
const loops = instance.loop;
// 当前位置,进度 归零
instance.currentTime = 0;
instance.progress = 0;
instance.paused = true;
instance.began = false;
instance.completed = false;
instance.reversed = direction === 'reverse';
instance.remaining = direction === 'alternate' && loops === 1 ? 2 : loops;
setAnimationsProgress(0);
for (let i = instance.children.length; i--; ){
instance.children[i].reset();
}
}
总结
- 使用了
requestAnimateFrame
和CSS
动画提高流畅度。 - 使用了缓动函数,只需要通过
当前动画消耗的时间
,搭配其他定义的配置项,就可以计算出当前动画具体位置。
此次解析就到这里结束,如有错误,请指出,感谢!