序
App对象是一个核心的对象之一,它的目的是把一切业务逻辑之外的逻辑全部把它抽象出来,让业务代码集中于一个文件,这样有助于管理。假如我们把业务的处理算法分布到services文件夹中,处理业务的流程放在Page对象中。剩下的事情应该归属于App对象了:
- 文件管理,按需加载。上一篇已经做了介绍,后续弹窗,组件,页面管理都是一样思路;
- 路由管理,单页面重要功能之一,下一篇介绍;
- 动画管理,这一篇介绍;
- 其它配置管理,比如版本号,缓存管理等。 这一篇介绍。
需求
假设已经有一个原型对象已经实现了路由管理ReplaceProto对象,我们创建一个App对象,它有以下的功能:
- 版本管理, 让引入的文件加上版本号,缓存清除;
- 各种文件管理;
- 动画管理器。
实现思路
根据上面需求,创建一个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有以下用途:
- 管理动画,在有动画的时候主动轮循,没有动画的时候轮循关闭;
- 为了更好的处理,具有暂停恢复等常规功能;
- 提供精确毫秒级的计算,使用requestAnimationFrame。
另外我们希望加入AnimationList的Animation对象有如下功能:
- 精确到毫秒级;
- 提供于css3动画类似的动画方法;
- 能够动画开始,结束,过程中都可以进行控制和交互。
代码实现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();
}
};
案例介绍
将上一章节的内容代码拷贝下来,并作以下修改
-
修改second.html,添加一个canvas元素
<canvas width="300" height="200" style="width: 300px; height: 200px;"></canvas>
-
更改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);
-
查看效果 (主要针对移动端,可以通过手机端查看或者用浏览器模拟移动端)。将代码放在可以访问的服务器或本地服务上,启动服务,通过浏览器访问 [如下地址]http://www.renxuan.tech:2003
结语
主要介绍了App对象的作用,以及与全局动画的关系,深入的解析了动画的原理实现
推广
底层框架开源地址:https://gitee.com/string-for-100w/string
演示网站: https://www.renxuan.tech/