Dojo动画原理解析
dojo中动画部分分为两部分:dojo/_base/fx, dojo/fx。dojo/_base/fx部分是dojo动画的基石,里面有两个底层API:animateProperty、anim和两个常用动画:fadeIn、fadeOut(类似jQuery中的show、hide)。dojo/fx中有两个复合动画:chain(类似jQuery中的动画队列)、combine和三个动画函数:wipeIn、wipeOut、slideTo。
dojo中动画的原理与jquery类似,都是根据setInterval计算当前时间与初试时间差值与总时间的半分比来确定元素动画属性的当前计算值:
percent = (Date.now() - startTime) / duration;
value = (end - start) * percent + start;
接下来我们会一步步的演化,慢慢接近dojo API
首先我们实现一个动画函数,他接受以下参数:
- node: 动画元素
- prop: 动画属性
- start: 动画起始值
- end: 动画结束值
- duration: 动画执行时间
- interval:动画间隔
function Animate(node, prop, start, end, duration, interval) {
var startTime = Date.now();
var timer = setInterval(function(){
var percent = (Date.now() - startTime) / duration;
percent = percent < 0 ? 0 : percent;
percent = percent > 1 ? 1 : percent;
var v = (end - start) * percent + start;
node.style[prop] = v;
if (percent >= 1) {
clearInterval(timer);
}
}, interval);
}
示例:
dojo中所有的动画函数都返回一个Animation的实例:Animation拥有一系列的属性和动画控制方法,下面我们简单的实现play和stop方法:
function Animate(node, prop, start, end, duration, interval/*, delay*/) {
var timer = null;
var startTime =0;
function startTimer() {
timer = setInterval(function(){
var percent = (Date.now() - startTime) / duration;
percent = percent < 0 ? 0 : percent;
percent = percent > 1 ? 1 : percent;
var v = (end - start) * percent + start;
node.style[prop] = isFinite(v) ? v /*+ 'px'*/ : v;
if (percent >= 1) {
clearInterval(timer);
timer = null;
}
}, interval);
}
function stopTimer() {
if (timer) {
clearInterval(timer);
timer = null;
}
}
return {
play: function() {
if (startTime === 0) {
startTime = Date.now();
}
startTimer();
},
stop: function() {
stopTimer();
}
}
}
这里将上文中Animate函数放入startTimer函数中,增加stopTimer用来移除定时器。
示例:
下面我们要支持延时和暂停功能,实现延迟的方法在于使用setTimeout设置延时启动时间,对于暂停功能,我们需要记录所有的暂停时间,在计算当前百分比时减去所有的暂停时间。
function Animate(node, prop, start, end, duration, interval, delay) {
var timer = null;
var startTime =0;
var delayTimer = null;
var paused = false;
var pauseStartTime = null;
var pausedTime = 0;//记录所有的暂停时间
function startTimer() {
timer = setInterval(function(){
var percent = (Date.now() - startTime - pausedTime) / duration;减去暂停消耗的时间
percent = percent < 0 ? 0 : percent;
percent = percent > 1 ? 1 : percent;
var v = (end - start) * percent + start;
node.style[prop] = isFinite(v) ? v /*+ 'px'*/ : v;
if (percent >= 1) {
stopTimer();
}
}, interval);
}
function stopTimer() {
if (timer) {
clearInterval(timer);
timer = null;
}
}
function clearDelayTimer() {
clearTimeout(delayTimer);
delayTimer = null;
}
return {
play: function() {
if (startTime === 0) {
startTime = Date.now();
}
if (paused) {
pausedTime += Date.now() - pauseStartTime;计算暂停时间
startTimer();
paused = false;
} else if (isFinite(delay)) {
delayTimer = setTimeout(function() {
clearDelayTimer();
startTime = Date.now();
startTimer();
}, delay); //delay延迟启动
} else {
startTimer();
}
},
pause: function() {
paused = true;
if (delayTimer) {
clearDelayTimer();
} else {
stopTimer();
pauseStartTime = Date.now();记录本次暂停起始时间
}
},
stop: function() {
stopTimer();
}
}
}
示例:
dojo/fx.animateProperty中可以设置多个动画属性,实现方式不难,只需要在每次动画计算时依次计算各个动画属性即可。
function Animate(node, props, duration, interval, delay) {
var timer = null;
var startTime =0;
var delayTimer = null;
var paused = false;
var pauseStartTime = null;
var pausedTime = 0;
function startTimer() {
timer = setInterval(function(){
var percent = (Date.now() - startTime - pausedTime) / duration;
percent = percent < 0 ? 0 : percent;
percent = percent > 1 ? 1 : percent;
for (var p in props) {
var prop = props[p];
node.style[p] = ((prop.end - prop.start) * percent + prop.start) + (prop.units ? prop.units : '');
}
if (percent >= 1) {
stopTimer();
}
}, interval);
}
function stopTimer() {
if (timer) {
clearInterval(timer);
timer = null;
}
}
function clearDelayTimer() {
clearTimeout(delayTimer);
delayTimer = null;
}
return {
play: function() {
if (startTime === 0) {
startTime = Date.now();
}
if (paused) {
pausedTime += Date.now() - pauseStartTime;
startTimer();
paused = false;
} else if (isFinite(delay)) {
delayTimer = setTimeout(function() {
clearDelayTimer();
startTime = Date.now();
startTimer();
}, delay);
} else {
startTimer();
}
},
pause: function() {
paused = true;
if (delayTimer) {
clearDelayTimer();
} else {
stopTimer();
pauseStartTime = Date.now();
}
},
stop: function() {
stopTimer();
}
}
}
var btnPlay = document.getElementById('btnPlay');
var n = document.getElementById('anim');
var anim = Animate(n, {
opacity: {start: 0.3, end: 1},
width: {start:50, end: 500, units: 'px'}
}, 5000, 25, 1000);
btnPlay.onclick = function() {
anim.play();
}
btnPause = document.getElementById('btnPause');
btnPause.onclick = function() {
anim.pause();
}
翻看dojo代码(dojo/_base/fx line:567)我们会发现,在进行动画之前dojo对一些动画属性做了预处理:
- 针对width/height动画时,元素本身inline状态的处理
- 对于Opacity的处理,IE8以下在style中设置滤镜
- 对于颜色动画的处理
下面我们进行的是事件点的添加,在dojo的实际源码中,回调事件的实现是通过实例化一个dojo/Evented对象来实现的,dojo/Evented是dojo整个事件驱动编程的基石,凡是拥有回调事件的对象都是它的实例。dojo/Evented的核心是dojo/on和dojo/aspect, 这两部分的解释可以看一下我的这几篇文章:
Javascript aop(面向切面编程)之around(环绕)
这里我们将事件回调挂载到实例上
function Animate(node, props, duration, interval, delay, callbacks) {
var timer = null;
var startTime =0;
var delayTimer = null;
var percent = null;
var stopped = false;
var ended = false;
var paused = false;
var pauseStartTime = null;
var pausedTime = 0;
function startTimer() {
timer = setInterval(function(){
if (!percent) {
callbacks.onBegin ? callbacks.onBegin() : null;
}
percent = (Date.now() - startTime - pausedTime) / duration;
percent = percent < 0 ? 0 : percent;
percent = percent > 1 ? 1 : percent;
for (var p in props) {
var prop = props[p];
node.style[p] = ((prop.end - prop.start) * percent + prop.start) + (prop.units ? prop.units : '');
}
callbacks.onAnimate ? callbacks.onAnimate() : null;
if (percent >= 1) {
stopTimer();
ended = true;
callbacks.onEnd ? callbacks.onEnd() : null;
}
}, interval);
}
function stopTimer() {
if (timer) {
clearInterval(timer);
timer = null;
}
}
function clearDelayTimer() {
clearTimeout(delayTimer);
delayTimer = null;
}
return {
play: function() {
if (ended) {
return;
}
if (startTime === 0) {
startTime = Date.now();
callbacks.beforeBegin ? callbacks.beforeBegin() : null;
}
if (paused) {
pausedTime += Date.now() - pauseStartTime;
startTimer();
paused = false;
} else if (isFinite(delay)) {
delayTimer = setTimeout(function() {
clearDelayTimer();
startTime = Date.now();
startTimer();
}, delay);
} else {
startTimer();
}
callbacks.onPlay ? callbacks.onPlay() : null;
},
pause: function() {
paused = true;
if (delayTimer) {
clearDelayTimer();
} else {
stopTimer();
pauseStartTime = Date.now();
}
callbacks.onPause ? callbacks.onPause() : null;
},
stop: function() {
stopTimer();
stopped = true;
callbacks.onStop ? callbacks.onStop() : null;
}
}
}
示例:
dojo/fx中最重要的两个函数就是chain和combine,chain函数允许我们一次执行一系列动画与jQuery中动画队列的功能类似。由于每个Animation实例都拥有onEnd事件,所以chain函数的实现原理就是在每个动画结束后,调用下一个动画的play函数。要模仿这个功能关键是如果在onEnd函数执行后绑定play函数。dojo中使用aspect.after方法,这里我们简单实现:为Function.prototype添加after方法:
Function.prototype.after = function(fn) {
var self = this;
return function() {
var results = self.apply(this, arguments);
fn.apply(this, [results]);
}
}
还有一个问题就是,上文中利用闭包的实现方式,所有对象的play方法都共享一套变量,在多个实例时有很大问题,所以从现在开始我们使用对象方式构造Animate类。
示例:
下一步就是combine,combine允许多个动画联动。combine的实现原理比较简单,依次调用Animation数组中的各对象的方法即可。
Function.prototype.after = function(fn) {
var self = this;
return function() {
var results = self.apply(this, arguments);
fn.apply(this, [results]);
}
}
function Animate(node, props, duration, interval, delay) {
this.node = node;
this.props = props;
this.duration = duration;
this.interval = interval;
this.delay = delay;
this.timer = null;
this.startTime = 0;
this.delayTimer = null;
this.percent = null;
this.stopped = false;
this.ended = false;
this.paused = false;
this.pauseStartTime = null;
this.pausedTime = 0;
}
Animate.prototype._startTimer = function() {
var self = this;
this.timer = setInterval(function() {
if (!self.percent) {
self.onBegin ? self.onBegin() : null;
}
var percent = (Date.now() - self.startTime - self.pausedTime) / self.duration;
percent = percent < 0 ? 0 : percent;
percent = percent > 1 ? 1 : percent;
self.percent = percent;
for (var p in self.props) {
var prop = self.props[p];
self.node.style[p] = ((prop.end - prop.start) * percent + prop.start) + (prop.units ? prop.units : '');
}
self.onAnimate ? self.onAnimate() : null;
if (self.percent >= 1) {
self._stopTimer();
self.ended = true;
self.onEnd ? self.onEnd() : null;
}
}, this.interval);
};
Animate.prototype._stopTimer = function() {
if (this.timer) {
clearInterval(this.timer);
this.timer = null;
}
};
Animate.prototype._clearDelayTimer = function() {
clearTimeout(this._delayTimer);
this._delayTimer = null;
};
Animate.prototype.play = function() {
if (this.ended) {
return;
}
if (this.startTime === 0) {
this.startTime = Date.now();
this.beforeBegin ? this.beforeBegin() : null;
}
if (this.paused) {
this.pausedTime += Date.now() - this.pauseStartTime;
this._startTimer();
this.paused = false;
} else if (isFinite(this.delay)) {
var self = this;
this._delayTimer = setTimeout(function() {
self._clearDelayTimer();
self.startTime = Date.now();
self._startTimer();
}, this.delay);
} else {
this._startTimer();
}
this.onPlay ? this.onPlay() : null;
};
Animate.prototype.pause = function() {
this.paused = true;
if (this._delayTimer) {
this._clearDelayTimer();
} else {
this._stopTimer();
this.pauseStartTime = Date.now();
}
this.onPause ? this.onPause() : null;
};
Animate.prototype.stop = function() {
this._stopTimer();
this.stopped = true;
this.onStop ? this.onStop() : null;
}
var btnPlay = document.getElementById('btnPlay');
var n = document.getElementById('anim');
var anim1 = new Animate(n, {
opacity: {start: 0, end: 1},
width: {start:50, end: 500, units: 'px'}
}, 5000, 25, 1000);
var anim2 = new Animate(n, {
// opacity: {start: 1, end: 0.3},
height: {start:50, end: 500, units: 'px'}
}, 5000, 25, 1000);
var anim3 = new Animate(n, {
opacity: {start: 1, end: 0.3},
height: {start:500, end: 50, units: 'px'}
}, 5000, 25, 1000);
var anim = combine([anim1, anim2]);
// anim = chain([anim, anim3]);
btnPlay.onclick = function() {
anim.play();
}
btnPause = document.getElementById('btnPause');
btnPause.onclick = function() {
anim.pause();
}
function combine(anims) {
var anim = {
play: function() {
for (var i = 0, len = anims.length; i < len; i++) {
anims[i].play();
}
},
pause: function() {
for (var i = 0, len = anims.length; i < len; i++) {
anims[i].pause();
}
},
stop: function() {
for (var i = 0, len = anims.length; i < len; i++) {
anims[i].stop();
}
}
};
return anim;
}
function chain(anims) {
var index = 0;
for (var i = 0, len = anims.length; i < len; i++) {
var a1 = anims[i];
var a2 = anims[i + 1];
if (a2) {
a1.onEnd = a1.onEnd ? a1.onEnd.after(function() {
index++;
anims[index].play();
}) : (function() {}).after(function() {
index++;
anims[index].play();
});
}
}
var anim = {
play: function() {
anims[index].play();
},
pause: function() {
anims[index].pause();
},
stop: function() {
anims[index].stop();
}
};
return anim;
}
示例:
dojo中chain、combine两个函数返回的对象跟Animation拥有同样的方法和属性,这也意味着利用这两个函数我们可以构造出更复杂的动画:
var anim1 = new Animate(n, {
opacity: {start: 0, end: 1},
width: {start:50, end: 500, units: 'px'}
}, 5000, 25, 1000);
var anim2 = new Animate(n, {
// opacity: {start: 1, end: 0.3},
height: {start:50, end: 500, units: 'px'}
}, 5000, 25, 1000);
var anim3 = new Animate(n, {
opacity: {start: 1, end: 0.3},
height: {start:500, end: 50, units: 'px'}
}, 5000, 25, 1000);
var anim = combine([anim1, anim2]);
anim = chain([anim, anim3]);
在此有兴趣的读者可以自行实现。
如果您觉得本文对您有帮助,请不要吝啬点击一下推荐!谢谢
Dojo动画原理解析的更多相关文章
- Android属性动画完全解析(上),初识属性动画的基本用法
转载请注明出处:http://blog.csdn.net/guolin_blog/article/details/43536355 在手机上去实现一些动画效果算是件比较炫酷的事情,因此Android系 ...
- Android属性动画完全解析(中)
转载:http://blog.csdn.net/guolin_blog/article/details/43536355 大家好,在上一篇文章当中,我们学习了Android属性动画的基本用法,当然也是 ...
- Skinned Mesh原理解析和一个最简单的实现示例
Skinned Mesh 原理解析和一个最简单的实现示例 作者:n5 Email: happyfirecn##yahoo.com.cn Blog: http://blog.csdn.net/n5 ...
- Android属性动画完全解析(中),ValueAnimator和ObjectAnimator的高级用法
转载请注明出处:http://blog.csdn.net/guolin_blog/article/details/43536355 大家好,在上一篇文章当中,我们学习了Android属性动画的基本用法 ...
- Android 上SuperUser获取ROOT权限原理解析
Android 上SuperUser获取ROOT权限原理解析 一. 概述 本文介绍了android中获取root权限的方法以及原理,让大家对android 玩家中常说的“越狱”有一个更深层次的认识. ...
- APPcrawler基础原理解析及使用
一.背景 一年前,我们一直在用monkey进行Android 的稳定性测试 ,主要目的就是为了测试app 是否会产生Crash,是否会有ANR,页面错误等问题,在monkey测试过程中,实现了脱离Ca ...
- View Animation 运行原理解析
Android 平台目前提供了两大类动画,在 Android 3.0 之前,一大类是 View Animation,包括 Tween animation(补间动画),Frame animation(帧 ...
- [原][Docker]特性与原理解析
Docker特性与原理解析 文章假设你已经熟悉了Docker的基本命令和基本知识 首先看看Docker提供了哪些特性: 交互式Shell:Docker可以分配一个虚拟终端并关联到任何容器的标准输入上, ...
- 【算法】(查找你附近的人) GeoHash核心原理解析及代码实现
本文地址 原文地址 分享提纲: 0. 引子 1. 感性认识GeoHash 2. GeoHash算法的步骤 3. GeoHash Base32编码长度与精度 4. GeoHash算法 5. 使用注意点( ...
随机推荐
- C# 用POST提交json数据
public void GetResponse(string url, string json) { Encoding encoding = Encoding.UTF8; byte[] data = ...
- git使用--git命令项目提交问题总结
提交遇到Error "remote ref does not exist"解决办法:git fetch -p MY_REMOTE eg. git fetch -p o ...
- jQuery与其他JS库共存
* 事件 * jQuery与其他JS库共存 * 调用jQuery.noConflict()方法 * 表示jQuery将"$"符号的使用权交出 * 通过两种方式将"$&qu ...
- ServletContext获取的方法
ServletContext 代表当前web应用 如何获取ServletContext对象 ServletConfig对象中维护了ServletContext对象的引用,可以通过以下方式获得 Ser ...
- Win10专业版激活方法可查版本
Win10专业版激活步骤 ------安装Win10专业版,请win+R,键入winver回车,可查看版本------ 1.点击左下角windows按钮,找到设置并打开,依次点击"更新和安全 ...
- AngularJS学习总结
第一章 简单认识AngularJS 1.双向数据绑定 可通过ng-model监控输入 ng-app属性声明所有被其包含的内容都属于这个AngularJs应用,这也是我们在web应用中嵌套Angula ...
- java基本数据类型取值范围
在JAVA中一共有八种基本数据类型,他们分别是 byte.short.int.long.float.double.char.boolean 整型 其中byte.short.int.long都是表示整数 ...
- Java程序员从笨鸟到菜鸟之(一百零一)sql注入攻击详解(二)sql注入过程详解
在上篇博客中我们分析了sql注入的原理,今天我们就来看一下sql注入的整体过程,也就是说如何进行sql注入,由于本人数据库和网络方面知识有限,此文章是对网上大量同类文章的分析与总结,其中有不少直接引用 ...
- 【树状数组套权值线段树】bzoj1901 Zju2112 Dynamic Rankings
谁再管这玩意叫树状数组套主席树我跟谁急 明明就是树状数组的每个结点维护一棵动态开结点的权值线段树而已 好吧,其实只有一个指针,指向该结点的权值线段树的当前结点 每次查询之前,要让指针指向根结点 不同结 ...
- 找一个四位数,要求该四位数的四倍刚好是该四位数的反序。 即b1b2b3b4 * 4 = b4b3b2b1
找一个四位数,要求该四位数的四倍刚好是该四位数的反序. 即b1b2b3b4 * 4 = b4b3b2b1 解: 第一步,确认最末位 假设 b1b2b3b4 + b4b3b2b1 = [x0]x1x2x ...