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事件机制兼容性解决方案

  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动画原理解析的更多相关文章

  1. Android属性动画完全解析(上),初识属性动画的基本用法

    转载请注明出处:http://blog.csdn.net/guolin_blog/article/details/43536355 在手机上去实现一些动画效果算是件比较炫酷的事情,因此Android系 ...

  2. Android属性动画完全解析(中)

    转载:http://blog.csdn.net/guolin_blog/article/details/43536355 大家好,在上一篇文章当中,我们学习了Android属性动画的基本用法,当然也是 ...

  3. Skinned Mesh原理解析和一个最简单的实现示例

    Skinned Mesh 原理解析和一个最简单的实现示例   作者:n5 Email: happyfirecn##yahoo.com.cn Blog: http://blog.csdn.net/n5 ...

  4. Android属性动画完全解析(中),ValueAnimator和ObjectAnimator的高级用法

    转载请注明出处:http://blog.csdn.net/guolin_blog/article/details/43536355 大家好,在上一篇文章当中,我们学习了Android属性动画的基本用法 ...

  5. Android 上SuperUser获取ROOT权限原理解析

    Android 上SuperUser获取ROOT权限原理解析 一. 概述 本文介绍了android中获取root权限的方法以及原理,让大家对android 玩家中常说的“越狱”有一个更深层次的认识. ...

  6. APPcrawler基础原理解析及使用

    一.背景 一年前,我们一直在用monkey进行Android 的稳定性测试 ,主要目的就是为了测试app 是否会产生Crash,是否会有ANR,页面错误等问题,在monkey测试过程中,实现了脱离Ca ...

  7. View Animation 运行原理解析

    Android 平台目前提供了两大类动画,在 Android 3.0 之前,一大类是 View Animation,包括 Tween animation(补间动画),Frame animation(帧 ...

  8. [原][Docker]特性与原理解析

    Docker特性与原理解析 文章假设你已经熟悉了Docker的基本命令和基本知识 首先看看Docker提供了哪些特性: 交互式Shell:Docker可以分配一个虚拟终端并关联到任何容器的标准输入上, ...

  9. 【算法】(查找你附近的人) GeoHash核心原理解析及代码实现

    本文地址 原文地址 分享提纲: 0. 引子 1. 感性认识GeoHash 2. GeoHash算法的步骤 3. GeoHash Base32编码长度与精度 4. GeoHash算法 5. 使用注意点( ...

随机推荐

  1. 以太坊只能合约摸索——第一关,ubuntu开发环境部署

    1. 安装“eth”命令行工具 sudo add-apt-repository ppa:ethereum/ethereum-qt sudo add-apt-repository ppa:ethereu ...

  2. 开启 mysql 远程访问

    如何开启MySQL的远程帐号-1)首先以 root 帐户登陆 MySQL 在 Windows 主机中点击开始菜单,运行,输入“cmd”,进入控制台,然后cd 进入MySQL 的 bin 目录下,然后输 ...

  3. MCMC and Bayesian Data Analysis(PPT在文件模块)

    How to generate a sample from $p(x)$? Let's first see how Matlab samples from a $p(x)$. In Matlab, t ...

  4. [python]自动化将markdown文件转成html文件

    *:first-child { margin-top: 0 !important; } body>*:last-child { margin-bottom: 0 !important; } /* ...

  5. app之间的跳转,进入二级界面

    功能实现:A跳到B并打开B中指定页面.http://blog.csdn.net/dollyyang/article/details/50325307 点击页面判断是否安装app并打开,否则跳转app ...

  6. Memcached服务介绍及安装指南

    一.memcached服务介绍 1.为什么需要memcached服务 A:第一种场景 网站访问大多数情况下都需要查询数据库操作,如果网站的流量很大并且大多数的访问会造成数据库高负荷的状况下,由于大部分 ...

  7. 通过其他页面跳转到tableBar指示的界面

    通过代理实现 让tableBar遵从代理,让代理的实现方法设置 [self setSelectedIndex:2]; setSelectedIndex:2 代表tableBar的item第3个处于选中 ...

  8. chrome 浏览器 手动同步书签 && 安装离线插件

    现在查的很严,所以使用chrome浏览器 同步功能不是特别好,就算是FQ也会出现 同步延迟的情况,所以定时使用手动同步还是很靠谱的行为,就在网上找了下,借鉴下.亲测可用. 在Chrome浏览器没有一个 ...

  9. #知识#室内设计原理ing

    室内设计原理 第一章 室内设计的含义和基本观点 人的一生,绝大部分时间是在室内度过的,因此,人们设计创造的室内环境,必然会直接关系到室内生活.生产活动的质量,关系到人们的安全.健康.效率.舒适等等.室 ...

  10. Object-C中一些不同于C系列语言表现的特性

    这段时间体验和学习OC,虽然这么多年基本都在使用C系列语言(C,C++,C#),但是仍然有很多的不习惯. 当然,这些不习惯不代表讨厌或者不好,也许这些就是OC作为Apple开发首选语言而显得特殊的一些 ...