# Animator 类

# 概述

动画对象

# 构造函数

参数 类型 说明
target Object 是指 style 或 shape 中所有的值
loop Boolean 是否循环播放
getter Function 从 target 中获取数据的方法
setter Function 为 target 赋值的方法

# 字段

# _tracks

动画轨迹, 在 when 方法执行时,会加入开始和结束的时间轴和值

# _target

style 或 shape 具体的值

# _loop

是否循环播放

# _getter

从 this._target 中获取数据的方法

# _setter

想 this._target 中写入数据的方法

# _clipCount

好尴尬啊~,只有声明,没有引用,看定义是私有变量

# _delay

动画延迟时间

# _doneList

动画执行结束后的回调函数集合

# _onframeList

动画执行期间的回调函数集合

# _clipList

动画剪辑集合

# 方法

# when

定义关键帧,即动画对象在某个时刻的属性。

创建动画轨迹, when 可能会多次使用

tracks 在使用的时候会排序, 所以多个 when 增加的关键帧不需要考虑时间轴的大小顺序

when: function (time, props) {
    var tracks = this._tracks;
    for (var propName in props) {
        if (!props.hasOwnProperty(propName)) {
            continue;
        }
        if (!tracks[propName]) {
            tracks[propName] = [];
            var value = this._getter(this._target, propName);
            if (value == null) {
                continue;
            }
            if (time !== 0) {
                tracks[propName].push({
                    time: 0,
                    value: cloneValue(value)
                });
            }
        }
        tracks[propName].push({
            time: time,
            value: props[propName]
        });
    }
    return this;
}

# during

为关键帧添加回调函数,在关键帧运行后执行。

during: function (callback) {
    this._onframeList.push(callback);
    return this;
}

# pause

暂停动画。

pause: function () {
    for (var i = 0; i < this._clipList.length; i++) {
        this._clipList[i].pause();
    }
    this._paused = true;
}

# resume

恢复动画。

resume: function () {
    for (var i = 0; i < this._clipList.length; i++) {
        this._clipList[i].resume();
    }
    this._paused = false;
}

# isPaused

获得动画是否处于暂停状态。

isPaused: function () {
    return !!this._paused;
}

# _doneCallback

??

_doneCallback: function () {
        // Clear all tracks
        this._tracks = {};
        // Clear all clips
        this._clipList.length = 0;

        var doneList = this._doneList;
        var len = doneList.length;
        for (var i = 0; i < len; i++) {
            doneList[i].call(this);
        }
    }

# start

开始执行动画。

创建动画剪辑并且添加到 animation 中

start: function (easing, forceAnimate) {

    var self = this;
    var clipCount = 0;

    var oneTrackDone = function () {
        clipCount--;
        if (!clipCount) {
            self._doneCallback();
        }
    };

    var lastClip;
    for (var propName in this._tracks) {
        if (!this._tracks.hasOwnProperty(propName)) {
            continue;
        }
        var clip = createTrackClip(
            this, easing, oneTrackDone,
            this._tracks[propName], propName, forceAnimate
        );
        if (clip) {
            this._clipList.push(clip);
            clipCount++;

            if (this.animation) {
                this.animation.addClip(clip);
            }

            lastClip = clip;
        }
    }

    if (lastClip) {
        var oldOnFrame = lastClip.onframe;
        lastClip.onframe = function (target, percent) {
            console.log(target.percent, percent)
            oldOnFrame(target, percent);

            for (var i = 0; i < self._onframeList.length; i++) {
                self._onframeList[i](target, percent);
            }
        };
    }

    if (!clipCount) {
        this._doneCallback();
    }
    return this;
}

# stop

停止动画。

stop: function (forwardToLast) {
    var clipList = this._clipList;
    var animation = this.animation;
    for (var i = 0; i < clipList.length; i++) {
        var clip = clipList[i];
        if (forwardToLast) {
            clip.onframe(this._target, 1);
        }
        animation && animation.removeClip(clip);
    }
    clipList.length = 0;
}

# delay

设置动画延迟开始的时间。

delay: function (time) {
    this._delay = time;
    return this;
}

# done

设置动画结束的回调函数

done: function (cb) {
    if (cb) {
        this._doneList.push(cb);
    }
    return this;
}

# getClips

获取所有的 Clip

getClips: function () {
    return this._clipList;
}

# 通用方法

# createTrackClip

创建动画剪辑, 并定义每一帧需要计算值的方法 onframe

参数 类型 说明
animator Animator 动画对象
easing String 缓动效果名称
oneTrackDone Function Clip 调用 ondestroy 时的回调函数
keyframes Object[] 关键帧
propName String 属性名称,用于描述关键帧或者动画剪辑
forceAnimate Boolean 强制动画
function createTrackClip(animator, easing, oneTrackDone, keyframes, propName, forceAnimate) {
    // 见下面
}

简单赋值、判断等

var getter = animator._getter;
var setter = animator._setter;
var useSpline = easing === 'spline';

var trackLen = keyframes.length;
if (!trackLen) {
    return;
}

不深入研究,暂时认为不是数组。所以下面的代码在尝试值的类型,以及获取时间轴上的最大值。

// 时间轴的第一个值
var firstVal = keyframes[0].value;
// 判断是否为数组或类似数组
var isValueArray = isArrayLike(firstVal);
// 是不是颜色值
var isValueColor = false;
// 是否字符串
var isValueString = false;
// 暂时不考虑, 默认为 0
var arrDim = isValueArray ? getArrayDim(keyframes) : 0;
// 获取时间轴的最大值
var trackMaxTime;
// Sort keyframe as ascending
keyframes.sort(function (a, b) {
    return a.time - b.time;
});
trackMaxTime = keyframes[trackLen - 1].time;

获取每一帧的百分比和值, 如果是颜色值的话,会转换为颜色数组,如果所有的关键帧的值都一样,会退出整个函数。

这样的话就解释了,为什么 clip 有可能为空

// 每个关键帧的百分比
var kfPercents = [];
// 每个关键帧的值
var kfValues = [];
// 上一个关键帧的值
var prevValue = keyframes[0].value;
// 是不是所有的值都一样
var isAllValueEqual = true;
for (var i = 0; i < trackLen; i++) {
    kfPercents.push(keyframes[i].time / trackMaxTime);
    // Assume value is a color when it is a string
    var value = keyframes[i].value;

    // 检查值是否相等,深度检查值是否为数组
    if (!((isValueArray && isArraySame(value, prevValue, arrDim))
        || (!isValueArray && value === prevValue))) {
        isAllValueEqual = false;
    }
    prevValue = value;

    // 尝试将字符串转换为颜色数组
    if (typeof value === 'string') {
        var colorArray = color.parse(value);
        if (colorArray) {
            value = colorArray;
            isValueColor = true;
        }
        else {
            isValueString = true;
        }
    }
    kfValues.push(value);
}
if (!forceAnimate && isAllValueEqual) {
    return;
}

修复非数字问题,包括数组

var lastValue = kfValues[trackLen - 1];
// Polyfill array and NaN value
for (var i = 0; i < trackLen - 1; i++) {
    if (isValueArray) {
        fillArr(kfValues[i], lastValue, arrDim);
    }
    else {
        if (isNaN(kfValues[i]) && !isNaN(lastValue) && !isValueString && !isValueColor) {
            kfValues[i] = lastValue;
        }
    }
}
isValueArray && fillArr(getter(animator._target, propName), lastValue, arrDim);

声明 Clip 中的 onframe 回调函数, 针对回调函数中的内容,一会看

var lastFrame = 0;
var lastFramePercent = 0;
var start;
var w;
var p0;
var p1;
var p2;
var p3;

if (isValueColor) {
    var rgba = [0, 0, 0, 0];
}

var onframe = function (target, percent) {

    var frame;
    if (percent < 0) {
        frame = 0;
    }
    else if (percent < lastFramePercent) {
        // Start from next key
        // PENDING start from lastFrame ?
        start = Math.min(lastFrame + 1, trackLen - 1);
        for (frame = start; frame >= 0; frame--) {
            if (kfPercents[frame] <= percent) {
                break;
            }
        }
        frame = Math.min(frame, trackLen - 2);
    }
    else {
        for (frame = lastFrame; frame < trackLen; frame++) {
            if (kfPercents[frame] > percent) {
                break;
            }
        }
        frame = Math.min(frame - 1, trackLen - 2);
    }
    lastFrame = frame;
    lastFramePercent = percent;

    var range = (kfPercents[frame + 1] - kfPercents[frame]);
    if (range === 0) {
        return;
    }
    else {
        w = (percent - kfPercents[frame]) / range;
    }
    if (useSpline) {
        p1 = kfValues[frame];
        p0 = kfValues[frame === 0 ? frame : frame - 1];
        p2 = kfValues[frame > trackLen - 2 ? trackLen - 1 : frame + 1];
        p3 = kfValues[frame > trackLen - 3 ? trackLen - 1 : frame + 2];
        if (isValueArray) {
            catmullRomInterpolateArray(
                p0, p1, p2, p3, w, w * w, w * w * w,
                getter(target, propName),
                arrDim
            );
        }
        else {
            var value;
            if (isValueColor) {
                value = catmullRomInterpolateArray(
                    p0, p1, p2, p3, w, w * w, w * w * w,
                    rgba, 1
                );
                value = rgba2String(rgba);
            }
            else if (isValueString) {
                // String is step(0.5)
                return interpolateString(p1, p2, w);
            }
            else {
                value = catmullRomInterpolate(
                    p0, p1, p2, p3, w, w * w, w * w * w
                );
            }
            setter(
                target,
                propName,
                value
            );
        }
    }
    else {
        if (isValueArray) {
            interpolateArray(
                kfValues[frame], kfValues[frame + 1], w,
                getter(target, propName),
                arrDim
            );
        }
        else {
            var value;
            if (isValueColor) {
                interpolateArray(
                    kfValues[frame], kfValues[frame + 1], w,
                    rgba, 1
                );
                value = rgba2String(rgba);
            }
            else if (isValueString) {
                // String is step(0.5)
                return interpolateString(kfValues[frame], kfValues[frame + 1], w);
            }
            else {
                value = interpolateNumber(kfValues[frame], kfValues[frame + 1], w);
            }
            setter(
                target,
                propName,
                value
            );
        }
    }
};

实例化 Clip 并且返回

 var clip = new Clip({
    target: animator._target,
    life: trackMaxTime,
    loop: animator._loop,
    delay: animator._delay,
    onframe: onframe,
    ondestroy: oneTrackDone
});

if (easing && easing !== 'spline') {
    clip.easing = easing;
}

return clip;