2021SC@SDUSC山东大学软件学院软件工程应用与实践——claygl(源码分析1)

2021SC@SDUSC

目录

一. clay.animation.Animator类概述

二. clay.animation.Animator类的作用

三. clay.animation.Animator类源码分析

1. new Animator(target, loop, getter, setter, interpolater)

2. delay(time) → {clay.animation.Animator}

3. done(func) → {clay.animation.Animator}

4. during(callback) → {clay.animation.Animator}

5. getClips() → {Array.}

6. start(easing) → {clay.animation.Animator}

7. stop()

8. then(time, props, easingopt) → {clay.animation.Animator}

9. when(time, props, easingopt) → {clay.animation.Animator}


一. clay.animation.Animator类概述

    Animator 对象只能通过 Animation.prototype.animate 方法创建。创建后,我们可以使用clay.animation.Animator#when方法来添加所有关键帧,并使用clay.animation.Animator#start来启动它。通过运行该方法,剪辑将自动创建并添加到创建此延迟对象的动画实例中。

二. clay.animation.Animator类的作用

    通过Animator类,我们可以创建一个动画,并对该动画进行修改,包括添加帧,删除帧,开始动画,结束动画等操作,当完成对动画的设置后,即可运行代码,实现claygl模型动画的实现,为模型添加动态效果。

三. clay.animation.Animator类源码分析

1. new Animator(target, loop, getter, setter, interpolater)

     该函数为Animator的构造函数,其参数及类型为:

NameType
targetObject
loop  boolean
getterfunction
setterfunction
interpolaterfunction

   

    其源码如下:

function Animator(target, loop, getter, setter, interpolater) {
    this._tracks = {};
    this._target = target;
    this._loop = loop || false;
    this._getter = getter || defaultGetter;
    this._setter = setter || defaultSetter;
    this._interpolater = interpolater || null;
    this._delay = 0;
    this._doneList = [];
    this._onframeList = [];
    this._clipList = [];
    this._maxTime = 0;
    this._lastKFTime = 0;
}

    从源码中可以看出,Animator类内共有12个成员变量,在构造函数中可接收5个参数,其中,target为必备的参数,其余参数为可选参数,具体情况如下:

参数是否必备默认值作用
target

动画的目标节点
loopfalse循环播放
getterdefaultGettergetter方法
setterdefaultSettersetter方法
interpolaternull

2. delay(time) → {clay.animation.Animator}

    该函数可根据输入的毫秒数进行延迟执行

    参数time为延迟的时间,单位是毫秒,类型为number。

    其源代码为:

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

    当执行该函数时,Animator对象会将成员变量delay的值设置为输入的time时间,并返回当前对象,实现延迟效果

3. done(func) → {clay.animation.Animator}

    该函数为动画结束后的回调函数,当动画完成时执行该函数

    参数func为动画完成时,所需执行的函数或行为,类型为function

    其源代码为:

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

    当执行done函数时,会判断是否设置了回调函数func,若设置了,则将func加入到成员变量doneList列表中,并返回当前对象,当动画结束后,则会执行doneList数组中的方法,可添加多个func函数。

4. during(callback) → {clay.animation.Animator}

    该函数为运行动画时的回调函数,当动画运行时执行该函数

    参数callback为动画运行时,所运行的回调函数,类型为function,callback有两个参数,即动画的目标以及触发该回调函数的动画百分比

    其源代码为:

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

    当执行during函数时,会往成员变量onframeList数组中添加回调函数callback,并返回该对象。在动画运行到callback函数指定的某一阶段百分比时,执行该callback函数,可添加多个callback函数。

5. getClips() → {Array.<clay.animation.Clip>}

    该函数获取start方法创建的所有剪辑,在执行start方法后,获取创建的所有剪辑

    该函数无形式参数

    其源代码为:

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

    该函数将clipList中所有的剪辑进行返回,从而获取start函数调用后,clipList里面所添加的所有剪辑

6. start(easing) → {clay.animation.Animator}

    该函数为启动动画的函数,当执行该函数时执行动画

    参数easing

    其源代码为:

start: function (globalEasing) {
        var self = this;
        var clipCount = 0;
        var oneTrackDone = function() {
            clipCount--;
            if (clipCount === 0) {
                self._doneCallback();
            }
        };
        var lastClip;
        var clipMaxTime = 0;
        for (var propName in this._tracks) {
            var clip = createTrackClip(
                this, globalEasing, oneTrackDone,
                this._tracks[propName], propName, self._interpolater, self._maxTime
            );
            if (clip) {
                clipMaxTime = Math.max(clipMaxTime, clip.life);
                this._clipList.push(clip);
                clipCount++;
                // If start after added to animation
                if (this.animation) {
                    this.animation.addClip(clip);
                }
                lastClip = clip;
            }
        }

    当执行start函数时,创建一个self变量并将当前动画对象赋值给self,同时创建局部变量clipCount并赋值为0。

    此时定义一个函数oneTrackDone,其作用是将clipCount变量自减,并判断clipCount是否全等于0,若全等于0则执行_doneCallback函数,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);
        }
}

    doneCallback函数首先将成员变量track设置为空,并将clipList的长度设置为0。创建一个doneList局部变量,并赋值本对象的成员变量doneList,同时创建len局部变量并赋值doneList的长度,通过for循环,对本对象执行doneList数组的每一个方法。

    结合来看,oneTrackDone函数的作用是不断地对clipCount进行自减,当自减至0时,执行动画结束时的回调函数数组的所有函数。

    下面,定义局部变量lastclip,以及clipMaxTime(赋值为0)。对本对象的局部变量_tracks对象的所有属性进行遍历。在每一次遍历中,首先调用createTrackClip函数,createTrackClip函数如下:

function createTrackClip(animator, globalEasing, oneTrackDone, keyframes, propName, interpolater, maxTime) {
    var getter = animator._getter;
    var setter = animator._setter;
    var useSpline = globalEasing === 'spline';
    var trackLen = keyframes.length;
    if (!trackLen) {
        return;
    }
    // Guess data type
    var firstVal = keyframes[0].value;
    var isValueArray = isArrayLike(firstVal);
    // For vertices morphing
    var arrDim = (
            isValueArray
            && isArrayLike(firstVal[0])
        )
        ? 2 : 1;
    // Sort keyframe as ascending
    keyframes.sort(function(a, b) {
        return a.time - b.time;
    });
    // Percents of each keyframe
    var kfPercents = [];
    // Value of each keyframe
    var kfValues = [];
    // Easing funcs of each keyframe.
    var kfEasings = [];
    var prevValue = keyframes[0].value;
    var isAllValueEqual = true;
    for (var i = 0; i < trackLen; i++) {
        kfPercents.push(keyframes[i].time / maxTime);
        // Assume value is a color when it is a string
        var value = keyframes[i].value;
        // Check if value is equal, deep check if value is array
        if (!((isValueArray && isArraySame(value, prevValue, arrDim))
            || (!isValueArray && value === prevValue))) {
            isAllValueEqual = false;
        }
        prevValue = value;
        kfValues.push(value);
        kfEasings.push(keyframes[i].easing);
    }
    if (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)) {
                kfValues[i] = lastValue;
            }
        }
    }
    isValueArray && fillArr(getter(animator._target, propName), lastValue, arrDim);
    // Cache the key of last frame to speed up when
    // animation playback is sequency
    var cacheKey = 0;
    var cachePercent = 0;
    var start;
    var i, w;
    var p0, p1, p2, p3;
    var onframe = function(target, percent) {
        // Find the range keyframes
        // kf1-----kf2---------current--------kf3
        // find kf2(i) and kf3(i + 1) and do interpolation
        if (percent < cachePercent) {
            // Start from next key
            start = Math.min(cacheKey + 1, trackLen - 1);
            for (i = start; i >= 0; i--) {
                if (kfPercents[i] <= percent) {
                    break;
                }
            }
            i = Math.min(i, trackLen - 2);
        }
        else {
            for (i = cacheKey; i < trackLen; i++) {
                if (kfPercents[i] > percent) {
                    break;
                }
            }
            i = Math.min(i - 1, trackLen - 2);
        }
        cacheKey = i;
        cachePercent = percent;
        var range = (kfPercents[i + 1] - kfPercents[i]);
        if (range === 0) {
            return;
        }
        else {
            w = (percent - kfPercents[i]) / range;
            // Clamp 0 - 1
            w = Math.max(Math.min(1, w), 0);
        }
        w = kfEasings[i + 1](w);
        if (useSpline) {
            p1 = kfValues[i];
            p0 = kfValues[i === 0 ? i : i - 1];
            p2 = kfValues[i > trackLen - 2 ? trackLen - 1 : i + 1];
            p3 = kfValues[i > trackLen - 3 ? trackLen - 1 : i + 2];
            if (interpolater) {
                setter(
                    target,
                    propName,
                    interpolater(
                        getter(target, propName),
                        p0, p1, p2, p3, w
                    )
                );
            }
            else if (isValueArray) {
                catmullRomInterpolateArray(
                    p0, p1, p2, p3, w, w*w, w*w*w,
                    getter(target, propName),
                    arrDim
                );
            }
            else {
                setter(
                    target,
                    propName,
                    catmullRomInterpolate(p0, p1, p2, p3, w, w*w, w*w*w)
                );
            }
        }
        else {
            if (interpolater) {
                setter(
                    target,
                    propName,
                    interpolater(
                        getter(target, propName),
                        kfValues[i],
                        kfValues[i + 1],
                        w
                    )
                );
            }
            else if (isValueArray) {
                interpolateArray(
                    kfValues[i], kfValues[i+1], w,
                    getter(target, propName),
                    arrDim
                );
            }
            else {
                setter(
                    target,
                    propName,
                    interpolateNumber(kfValues[i], kfValues[i+1], w)
                );
            }
        }
    };

     该函数的作用是创建一个动画剪辑。在start成员方法的源代码中,对于_tracks字典里面的每一个键值对对象,创建该对象的一个剪辑,并赋值给clip局部变量,如果clip对象不为空,则与之前创建的剪辑中时间最长的剪辑作比较,若该剪辑的时间更长,则将其时间赋值给clipMaxTime,否则clipMaxTime维持最大值。然后将该剪辑放进_clipList数组中,对clipCount进行自增,表示添加一个剪辑。同时判断本对象的animation是否存在,若存在,则将该剪辑也加入到animation中。最后设置本对象的lastclip为clip,表示该clip剪辑为最后一个剪辑。

    当剪辑组不为空,及最后一个剪辑存在时,创建一个局部变量oldOnFrame,并将最后一个剪辑的onframe赋给oleOnFrame,随后定义一个构造函数onframe(target,percent),该构造函数首先对oldOnFrame进行执行,传入参数为形参target和percent,然后,对本对象的_onframeList数组进行遍历,并通过target和percent参数执行onframeList的每一个方法,目的在于执行_onframeList中的每一个动画,从而实现动画开始效果。

    最后,判断clipCount是否不为0,如果不为0,表示还有剪辑,直接退出并返回本对象,若为0则说明动画剪辑已经播放结束,执行动画结束的函数回调,即_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);
        }
    }

    _doneCallback()方法将本对象的_tracks字典清空,并将_clipList的长度设置为0;并获取_doneList对象和其长度,赋值给doneList和len局部变量,通过这两个变量,通过for循环函数逐个调用doneList中的方法。

    可见,最后一步的目的是判断动画是否播放完毕,若播放完毕则执行播放完毕后所有的回调函数,并返回本对象,若未播放完毕,则直接返回本对象。

7. stop()

    该函数为停止动画的函数,当执行该函数时停止动画

    无需传入参数

    其源代码为:

   stop: function () {
        for (var i = 0; i < this._clipList.length; i++) {
            var clip = this._clipList[i];
            this.animation.removeClip(clip);
        }
        this._clipList = [];
    }

    当执行该函数后,该函数会对本对象的clipList数组进行遍历,对于clipList中的每一个对象,创建一个clip局部变量并赋值给clip,同时在正在播放的animation动画对象中移除与clip一样的对象,当移除animation所有的clip对象后,清空clipList。

    该方法的目的是使animation正在播放的动画剪辑对象清空,从而实现动画的停止播放

8. then(time, props, easingopt) → {clay.animation.Animator}

    该函数为在动画的末尾加上一段动画,及增加动画的时间,增加新的动画

    该函数共有三个参数,分别为:

    time:类型为number,表示自最后一个动画关键帧之后的时间

    props:类型为对象,是一个键值对对对象,值可以为数字,一位数组和二维数组,表示动画执行的对象

    easing:类型为string或function,表示动画的执行脚本

    该函数的源代码如下:

    then: function (duringTime, props, easing) {
        this.when(duringTime + this._lastKFTime, props, easing);
        this._lastKFTime += duringTime;
        return this;
    }

    首先,该函数调用本函数的when方法,传入的参数为输入的时间duringTime加上本对象的_lastKFTime的值,表示在原动画的末尾添加动画,而props和easing则是直接传入形参props和easing,when的用法具体如下。

    当使用了when追加动画后,则需要将本对象的_lastKFTime结束结束时间加上新增加动画的时间,最后返回本对象

9. when(time, props, easingopt) → {clay.animation.Animator}

    该函数为在已创建的动画中插入一段新的动画的函数,在输入相应的参数并执行该函数后,在指定的time时刻上插入一段动画

    该函数共有三个参数,分别为:

     time:类型为number,表示插入关键帧的时刻

    props:类型为对象,是一个键值对对对象,值可以为数字,一位数组和二维数组,表示动画执行的对象

    easing:类型为string或function,表示动画的执行脚本

    该函数的源代码如下:

   when: function (time, props, easing) {
        this._maxTime = Math.max(time, this._maxTime);
        easing = (typeof easing === 'function' ? easing : easingFuncs[easing]) || noopEasing;
        for (var propName in props) {
            if (!this._tracks[propName]) {
                this._tracks[propName] = [];
                // If time is 0
                //  Then props is given initialize value
                // Else
                //  Initialize value from current prop value
                if (time !== 0) {
                    this._tracks[propName].push({
                        time: 0,
                        value: cloneValue(
                            this._getter(this._target, propName)
                        ),
                        easing: easing
                    });
                }
            }
            this._tracks[propName].push({
                time: parseInt(time),
                value: props[propName],
                easing: easing
            });
        }
        return this;
}

    首先,该函数首先将输入的时刻与原动画截止的时间进行比较,并将其值大的变量赋值给本对象的截止时间_maxTime。

    然后,对easing进行判断,若是function函数,则将easingFuncs[easing]对象赋给easing,否则,若是string字符串,则维持自身,若easing为null,则设置easing为默认值noopeasing。

    接下来,则是对props对象中的键值对进行不断遍历。首先获取props对象中的键key,判断本对象的_track字典中是否有对应该key下的对象,如果没有,则创建_track下的同名称键,并设置值为空数组。同时,若时间为0,则给props一个初始值,否则,从当前属性初始化值,即若是time不全等于0,则初始化其time为0,值value为从本对象的_getter中克隆而来的对应key下的value,同时设置初始的easing为局部变量easing。否则,则从当前属性初始化值,设置对应_track下对应key的值,time为形参中的time并对其格式化为整数,value为对应props下的value,easing为局部变量easing。最后返回本对象。

    因此,通过该操作,可以通过指定time在特定的时间内插入动画,而动画的方法有props和easing进行决定。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值