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. 使用注意点( ...
随机推荐
- cookie,session原理,以及如何使用chrome查看。
首先,先补充下chrome浏览器的使用. 1.1.php源码: <?php $cookieDomain = '.elf.com'; setcookie(, '/', $cookieDomain) ...
- exe文件放在其他位置
set PATH=%PATH%;%UGII_ROOT_DIR%call "E:\ZY\exe\uds_rename_parts_mohao.exe"E:\ZY\exe\uds_re ...
- Codeforces Round #200 (Div. 1) D. Water Tree(dfs序加线段树)
思路: dfs序其实是很水的东西. 和树链剖分一样, 都是对树链的hash. 该题做法是:每次对子树全部赋值为1,对一个点赋值为0,查询子树最小值. 该题需要注意的是:当我们对一棵子树全都赋值为1的 ...
- tomcat详情
[转载]http://grass51.blog.51cto.com/4356355/1123400
- javaWeb实现文件上传与下载 (转)
文件上传概述 实现web开发中的文件上传功能,需完成如下二步操作: 在web页面中添加上传输入项 在servlet中读取上传文件的数据,并保存到本地硬盘中. 如何在web页面中添加上传输入项? < ...
- redis迁移工具-redis-migrate-tool使用测试
https://github.com/vipshop/redis-migrate-tool一.安装redis-migrate-tool a.下载redis-migrate-tool软件包 https: ...
- IAR之文件路径设置
1.命令解释 $PROJ_DIR$表示工程所在路径 $TOOLKIT_DIR$表示IAR安装目录所在头文件路径 \..\表示返回上一级目录. 2.头文件路径设置 打开工程文件,找到"opti ...
- ios xcode 下 报出 ”xx“is missing from working copy 的问题
在项目中提交过svn后,再在本机上删除不用的图片资源后,build后会有 ”xx“is missing from working copy 的警告.在网上找了些资料后,总结下. 直接在终端下用s ...
- 「2014-2-26」Unicode vs. UTF-8 etc.
目测是个老问题了.随便一搜,网上各种总结过.这里不辞啰嗦,尽量简洁的备忘一下. 几个链接,有道云笔记链接,都是知乎上几个问题的摘录:阮一峰的日志,1-5 还是值得参考,但是之后的部分则混淆了 Wind ...
- 如何编写高效的jQuery代码
jQuery的编写原则: 一.不要过度使用jQuery 1. jQuery速度再快,也无法与原生的javascript方法相比,而且建立的jQuery对象包含的信息量很庞大.所以有原生方法可以使用的场 ...