2021SC@SDUSC
plainArray()函数
function plainArray(arr) {
const result = [];
for (let i = 0, len = arr.length; i < len; i++) {
if (arr[i]) {
result.push(arr[i].x);
result.push(arr[i].y);
}
}
return result;
}
传入一个数组类型变量,返回值也是一个数组类型变量,其返回的内容是传入参数arr的每一项的(x,y)坐标信息。
interpolateNumber()函数
function interpolateNumber(a, b) {
a = +a;
b -= a;
return function(t) {
return a + b * t;
};
}
差值函数,传入两个数据a,b,对ab两个数据进行一定处理之后,根据需要,将所得数值返回。具体作用目前未知。
interpolateArray()函数
function interpolateArray(a, b) {
const nb = b ? b.length : 0;
const na = a ? Math.min(nb, a.length) : 0;
const x = new Array(na);
const c = new Array(nb);
let i;
for (i = 0; i < na; ++i) x[i] = interpolateNumber(a[i], b[i]);
for (; i < nb; ++i) c[i] = b[i];
return function(t) {
for (i = 0; i < na; ++i) c[i] = x[i](t);
return c;
};
}
这个函数过程中,首先定义两个常量来存储传入的两个数组参数的长度(要么是长度,如果不存在,则为零),然后根据数组长度新建了两个全新数组x,c。然后利用两个for循环将新建两个数组的内容补充:其中x中存储的是传入的参数数组a,b的对应位置的数据差值(即文章提到的第二个差值函数),然后将数组c的内容初始化为b。最后定义了一个返回函数,在根据需要时将不同的数组返回。
animator类
class Animator {
constructor(shape, source, timeline) {
this.hasStarted = false;
this.hasEnded = false;
this.shape = shape;
this.source = source;
this.timeline = timeline;
this.animate = null;
}
// delay, attrs, duration, easing
to(cfg = {}) {
const delay = cfg.delay || 0;
const attrs = cfg.attrs || {};
const duration = cfg.duration || 1000;
let easing; // 缓动函数
if (typeof (cfg.easing) === 'function') {
easing = cfg.easing;
} else {
easing = Easing[cfg.easing] || Easing.linear;
}
const animInfo = {
shape: this.shape,
delay,
duration,
easing
};
const interpolate = {}; // 差值函数
for (const attrName in attrs) {
let startValue = this.source[attrName];
let endValue = attrs[attrName];
if (attrName === 'points') {
startValue = plainArray(startValue);
endValue = plainArray(endValue);
interpolate.points = interpolateArray(startValue, endValue);
this.source.points = startValue;
attrs.points = endValue;
} else if (attrName === 'matrix') {
interpolate.matrix = interpolateArray(startValue, endValue);
} else {
interpolate[attrName] = interpolateNumber(startValue, endValue);
}
}
animInfo.interpolate = interpolate;
animInfo.startState = this.source;
animInfo.endState = attrs;
this.timeline.pushAnim(animInfo);
this.animate = animInfo;
return this;
}
onFrame(callback) { // 自定义每一帧动画的动作
if (this.animate) {
this.animate.onFrame = function(frame) {
callback(frame);
};
}
return this;
}
onStart(callback) {
if (this.animate) {
this.animate.onStart = function() {
callback();
};
}
return this;
}
onUpdate(callback) {
if (this.animate) {
this.animate.onUpdate = function(frame) {
callback(frame);
};
}
return this;
}
onEnd(callback) {
if (this.animate) {
this.animate.onEnd = function() {
callback();
};
}
return this;
}
}
类中定义了一个结构体,其中存储了每一帧动画的具体情况,以及一条时间线timeline;定义了四个具体方法来实现动画。
delay定义的是动画的延迟时间,如果有的话,设置为cfg.delay即已经定义好的,如果没有那么就设置为0即没有延迟时间;
attrs定义的是帧数组,同样的道理,如果有的话,即设置为原有的数组,如果没有那么就设置为空,即没有帧;
duration定义的是动画持续时间,同样,要么为原本具有的时间,要么为1000,这里的1000指的是计算机内部的计时单位。
定义了一个缓动函数easing 其中调用到了导入的easing.js里面的内容,easing.js的内容见下一部分。
其余的部分基本上是涉及到了我们前面所介绍的函数,在此不做赘述。
通过以上的部分,我们可以利用对每一帧动画的位置设定(即每一帧图画的xy坐标的设定)以及其存在时间、延迟时间等等一系列参数,来实现我们想要的动画效果。
总结
不容置疑的一点是,F2强大的动画机制下面是由数量极多的函数以及一些精妙的策略支撑的。本文仅仅是对F2动画机制下的一个小部分animate.js文件的分析,内容比较简单,但每一个函数的作用都恰到好处。
这一部分通过简单的对每一帧动画的持续时间、延迟出现时间、坐标位置的设定来实现比较简单的动画效果。
当然,作者目前接触的内容并不是很多,分析可能不是那么准确,在以后的深入研究中会进一步完善每一篇文章,力争准确分析。
附录:导入包easing.js
function linear(k) {
return k;
}
function quadraticIn(k) {
return k * k;
}
function quadraticOut(k) {
return k * (2 - k);
}
function quadraticInOut(k) {
if ((k *= 2) < 1) {
return 0.5 * k * k;
}
return -0.5 * (--k * (k - 2) - 1);
}
function cubicIn(k) {
return k * k * k;
}
function cubicOut(k) {
return --k * k * k + 1;
}
function cubicInOut(k) {
if ((k *= 2) < 1) {
return 0.5 * k * k * k;
}
return 0.5 * ((k -= 2) * k * k + 2);
}
function elasticIn(k) {
let s;
let a = 0.1;
let p = 0.4;
if (k === 0) return 0;
if (k === 1) return 1;
if (!p) {
p = 0.3;
}
if (!a || a < 1) {
a = 1;
s = p / 4;
} else {
s = p / (2 * Math.PI) * Math.asin(1 / a);
}
return -(a * Math.pow(2, 10 * (k -= 1)) * Math.sin((k - s) * (2 * Math.PI) / p));
}
function elasticOut(k) {
let s;
let a = 0.1;
let p = 0.4;
if (k === 0) return 0;
if (k === 1) return 1;
if (!p) {
p = 0.3;
}
if (!a || a < 1) {
a = 1;
s = p / 4;
} else {
s = p / (2 * Math.PI) * Math.asin(1 / a);
}
return (a * Math.pow(2, -10 * k) * Math.sin((k - s) * (2 * Math.PI) / p) + 1);
}
function elasticInOut(k) {
let s;
let a = 0.1;
let p = 0.4;
if (k === 0) return 0;
if (k === 1) return 1;
if (!p) {
p = 0.3;
}
if (!a || a < 1) {
a = 1;
s = p / 4;
} else {
s = p / (2 * Math.PI) * Math.asin(1 / a);
}
if ((k *= 2) < 1) {
return -0.5 * (a * Math.pow(2, 10 * (k -= 1)) * Math.sin((k - s) * (2 * Math.PI) / p));
}
return a * Math.pow(2, -10 * (k -= 1)) * Math.sin((k - s) * (2 * Math.PI) / p) * 0.5 + 1;
}
function backIn(k) {
const s = 1.70158;
return k * k * ((s + 1) * k - s);
}
function backOut(k) {
const s = 1.70158;
return (k = k - 1) * k * ((s + 1) * k + s) + 1;
}
function backInOut(k) {
const s = 1.70158 * 1.525;
if ((k *= 2) < 1) {
return 0.5 * (k * k * ((s + 1) * k - s));
}
return 0.5 * ((k -= 2) * k * ((s + 1) * k + s) + 2);
}
function bounceIn(k) {
return 1 - bounceOut(1 - k);
}
function bounceOut(k) {
if ((k /= 1) < (1 / 2.75)) {
return 7.5625 * k * k;
} else if (k < (2 / 2.75)) {
return 7.5625 * (k -= (1.5 / 2.75)) * k + 0.75;
} else if (k < (2.5 / 2.75)) {
return 7.5625 * (k -= (2.25 / 2.75)) * k + 0.9375;
}
return 7.5625 * (k -= (2.625 / 2.75)) * k + 0.984375;
}
function bounceInOut(k) {
if (k < 0.5) {
return bounceIn(k * 2) * 0.5;
}
return bounceOut(k * 2 - 1) * 0.5 + 0.5;
}
export {
linear,
quadraticIn,
quadraticOut,
quadraticInOut,
cubicIn,
cubicOut,
cubicInOut,
elasticIn,
elasticOut,
elasticInOut,
backIn,
backOut,
backInOut,
bounceIn,
bounceOut,
bounceInOut
};