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. 使用注意点( ...
随机推荐
- FIFO
FIFO存储器 FIFO是英文First In First Out 的缩写,是一种先进先出的数据缓存器,他与普通存储器的区别是没有外部读写地址线,这样使用起来非常简单,但缺点就是只能顺序写入数据,顺序 ...
- 《笨办法学C》笔记之Makefile
使用gcc编译C语言源码 在Linux系统中,C语言源码需要用gcc编译为二进制可执行文件,才能够运行. $ gcc test.c -o test 这句命令就将test.c文件编译为test二进制可执 ...
- C# 动态加载程序集dll (实现接口)
一.程序集(接口程序集):LyhInterface.Dll namespace LyhInterface { public interface ILyhInterface { void Run(); ...
- Git常用
创建本地库 mkdir [dirname] cd [dirname] git init 1.创建项目目录 2.进入目录 3.git初始化 [dirname]为自己取的文件夹名字,例如mkdir myd ...
- Android 时间维护服务 TimeService(针对于特殊定制设备)
此方法针对于无法自动获取网络时间的特殊设备,正常 Android 设备直接调用 System.currentTimeMillis(); 方法获取当前时间即可. TimeService 集成于 Serv ...
- 设置代码Code高亮显示成蓝色
下面方法是让设置的关键字高亮显示,考虑到了注释与字符串的影响,所以备用,以便将来能够用到. private static void ColorizeCode(RichTextBox rtb) { st ...
- linux下的5个查找命令
在Linux中,有很多方法可以做到这一点.国外网站LinuxHaxor总结了五条命令,你可以看看自己知道几条.大多数程序员,可能经常使用其中的2到3条,对这5条命令都很熟悉的人应该是不多的. 1. f ...
- C#数据结构杂记
定义任何类时记得要定义无参构造函数,否则在反序列化的时候会抛出异常. [Serialize]声明该类可以被序列化 Const与readonly的区别 const本质上是常量没有任何方法修改值,read ...
- 文本框如果不输入任何内容提交过后是一个空字符串还是null
1.在表单不填就提交的情况下,text类型和textarea类型的表单域,提交到服务端为空 2.checkbox.readio.select等表单域在为不填情况下不会提交到服务器,也就是说服务器接收不 ...
- 数据库表中存在Text类型的属性时,写sql语句时需要注意喽!
之前,习惯性地写查询语句时,查询条件用“=”判断.今天写程序的时候,查询时突然报了一个错误:数据类型text 和varchar 在equal to 运算符中不兼容.查看了一下数据库发现,其中有一个属性 ...