4.App对象的介绍和动画管理

App对象是一个核心的对象之一,它的目的是把一切业务逻辑之外的逻辑全部把它抽象出来,让业务代码集中于一个文件,这样有助于管理。假如我们把业务的处理算法分布到services文件夹中,处理业务的流程放在Page对象中。剩下的事情应该归属于App对象了:

  1. 文件管理,按需加载。上一篇已经做了介绍,后续弹窗,组件,页面管理都是一样思路;
  2. 路由管理,单页面重要功能之一,下一篇介绍;
  3. 动画管理,这一篇介绍;
  4. 其它配置管理,比如版本号,缓存管理等。 这一篇介绍。

需求

假设已经有一个原型对象已经实现了路由管理ReplaceProto对象,我们创建一个App对象,它有以下的功能:

  1. 版本管理, 让引入的文件加上版本号,缓存清除;
  2. 各种文件管理;
  3. 动画管理器。

实现思路

根据上面需求,创建一个App对象,代码如下

function App(name, staticName, currentName, version) {
    // 版本管理,可以全局定义,也可以指定。全局定义将版本号挂在index.html上,修改比较方便
    version = App.version = App.version || version || Math.random();
    this.version = version;
    ReplaceProto.call(this, name, staticName, currentName);
    // 主要实现
    this.animationList = new AnimationList();

    // 存储按需加载管理对象, 上一篇介绍过
    this.loadPageUnit = new LoadUnit(this.version);
    this.loadComponentUnit = new LoadUnit(this.version);
    this.loadPopUpUnit = new LoadUnit(this.version);
}

App.prototype = create(ReplaceProto.prototype, {
    constructor: App,
    // 增加动画
    addAnimation: function (animation) {
        this.animationList.add(animation);
    },
    // 移除动画,htmlProto代表Page对象或Component对象
    removeAnimationByTarget: function (htmlProto) {
        this.animationList._removeByTarget(htmlProto.uid);
    }
});

这里的主要核心在于AnimationList对象的实现。
我们希望AnimationList有以下用途:

  1. 管理动画,在有动画的时候主动轮循,没有动画的时候轮循关闭;
  2. 为了更好的处理,具有暂停恢复等常规功能;
  3. 提供精确毫秒级的计算,使用requestAnimationFrame。

另外我们希望加入AnimationList的Animation对象有如下功能:

  1. 精确到毫秒级;
  2. 提供于css3动画类似的动画方法;
  3. 能够动画开始,结束,过程中都可以进行控制和交互。

代码实现Animation对象

function Animation(time, delay, type) {
    BaseProto.call(this);
    this.time = time;
    // animationType作为动画函数,这里省略不做介绍
    this.animationType = type || Animation.linear;
    if (type == Animation.normal) this.time = Infinity;
    this.delay = delay || 0;
    // 计时器对象,简单实现不做介绍
    this.stopwatch = new Stopwatch();
    this.ended = false;
};
Animation.prototype = create(BaseProto.prototype, {
    constructor: Animation,
    //动画开始,如果有绑定开始方法,触发开始方法
    _start: function () {
        this.stopwatch.start();
        this.dispatchEvent("start");
    },
    // 通过传入时间值让动画更新,如果没有传入时间值,将会计时器来计算更新
    _update: function (elapsed) {
        elapsed = elapsed || (this.stopwatch.getElapsedTime() - this.delay);
        if (elapsed > 0) {
            // 无尽动画,除非手动设置ended
            if (this.animationType == Animation.normal) {
                if (this.ended) {
                    this._complete();
                    return true;
                } else {
                    this.dispatchEvent("update", { elapsed: elapsed });
                    return false;
                }
            } else {
                if (this.ended || elapsed > this.time) {
                    this._complete();
                    return true;
                } else {
                    var k = this.animationType(elapsed, this.time);
                    this.dispatchEvent("update", { elapsed: k });
                    return false;
                }
            }
        }
        return false;
    },
    // 动画结束触发的方法,如果绑定结束方法,就会触发结束方法
    _complete: function () {
        this.dispatchEvent("complete");
        this.stopwatch.stop();
    },
    // 销毁动画对象
    destroy: function () {
        this.eventDispatcher.destroy();
        this.stopwatch = null;
    }
});

AnimationList的简单代码

function AnimationList() {
    this.reset();
}
AnimationList.prototype = {
    constructor: AnimationList,
    // 将动画加入动画序列
    add: function (animation) {
        // 判断动画是否再动画序列中,可以防止相同的动画被加入多次
        if (!this._contains(animation)) {
            animation.stopwatch.reset();
            animation._start();
            this.animationArray.push(animation);
            // 每个添加的Animation都有个target属性,有助于统一管理包括批量管理
            if (!this.animationObj[animation.target]) {
                this.animationObj[animation.target] = [animation];
            } else {
                this.animationObj[animation.target].push(animation);
            }

            if (!this.running) {
                this.running = true;
                this.requestId = requestAnimationFrame(this.tick.bind(this));
            }
        }
    },
    // 每次更新后调用这个方式派发动画的更新方法
    tick: function () {
        var animationArray = this.animationArray;
        var len = animationArray.length;
        if (len == 0) {
            this.requestId = null;
            this.running = false;
            return;
        }

        var removes = [];
        for (var i = 0; i < len; i++) {
            var aniObj = animationArray[i];

            if (!aniObj.stopwatch.running) continue;
            if (aniObj._update()) {
                removes.push(aniObj);
            }
        }

        for (var i = animationArray.length - 1; i >= 0; i--) {
            var a = animationArray[i];
            if (removes.indexOf(a) > -1) {
                this._remove(a);
            }
        }

        if (animationArray.length > 0) 
            this.requestId = requestAnimationFrame(this.tick.bind(this));
    },
    // 移除某个动画对象
    _remove: function (animation) {
        var animationArray = this.animationArray,
            animationObj = this.animationObj,
            array = animationObj[animation.target];

        var i = animationArray.indexOf(animation);
        animationArray.splice(i, 1);
        var ii = array.indexOf(animation);
        array.splice(ii, 1);
        if (array.length == 0) {
            delete animationObj[animation.target];
        }
        // 停止动画轮询
        if (animationArray.length == 0) this.stop(true);
    },
    // 重置该对象
    reset: function () {
        this.animationArray = [];
        this.animationObj = {};
        this.running = false;
        this.requestId = null;
    },
    // 销毁该对象
    destroy: function () {
        this.stop();
        this.reset();
    }
};

案例介绍

将上一章节的内容代码拷贝下来,并作以下修改

  1. 修改second.html,添加一个canvas元素

    <canvas width="300" height="200" style="width: 300px; height: 200px;"></canvas>
    
  2. 更改second.js,在init方法中添加代码

    var canvas = this.domList.canvas;
     var ctx = canvas.getContext("2d");
     ctx.strokeStyle = "red";
     var animation = new Animation(2000);
     animation.onUpdate(function (obj) {
         var elapsed = obj.elapsed;
         var x = elapsed * canvas.width - 5, y = elapsed * canvas.height - 5;
         ctx.clearRect(0, 0, canvas.width, canvas.height);
         ctx.beginPath();
         ctx.arc(x, y, 5, 0, Math.PI * 2, false);
         ctx.stroke();
         console.log(obj);
     }).onComplete(function () {
         console.log("end");
     });
     this.addAnimation(animation);
    
  3. 查看效果 (主要针对移动端,可以通过手机端查看或者用浏览器模拟移动端)。将代码放在可以访问的服务器或本地服务上,启动服务,通过浏览器访问 [如下地址]http://www.renxuan.tech:2003

结语

主要介绍了App对象的作用,以及与全局动画的关系,深入的解析了动画的原理实现

推广

底层框架开源地址:https://gitee.com/string-for-100w/string
演示网站: https://www.renxuan.tech/

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值