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# 将sheet中数据转为list
public IList<T> ExportToList<T>(ISheet sheet, string[] fields) where T : class,new() { I ...
- eclipse配置javaee环境
笔者开发javaee项目时惯用myeclipse,但由于个人笔记本性能较低,myeclipse对内存的消耗极大,所以考虑换成eclipse开发.本文介绍eclipse配置javaee开发环境的一些体会 ...
- MongoDB的真正性能-实战百万用户一-一亿的道具
使用情景 开始之前,我们先设定这样一个情景: 1.一百万注册用户的页游或者手游,这是不温不火的一个状态,刚好是数据量不上不下的一个情况.也刚好是传统MySql数据库性能开始吃紧的时候. 2.数据库就用 ...
- jquery easyui window中的datagrid,只能显示一次问题
最近项目中用到easyui 的动态创建window ,window中嵌入了datagruid.第一次打开是能显示数据,但再次打开时确没显示: 注:url已成功返回了数据. 多次查阅easyui帮助文档 ...
- 【温故Delphi】GAEA用到Win32 API目录
Delphi是Windows平台下著名的快速应用程序开发工具,它在VCL中封装并使用了大量的Win32 API. GAEA基于VCL开发的工具类产品,在程序中使用了大量的Win32 API,将经常用到 ...
- Height Half Values
public class HeightDemo { /** * 题目:一球从100米高度自由落下,每次落地后反跳回原高度的一半: * 再落下,求它在第10次落地时,共经过多少米?第10次反弹多高? * ...
- 关于c++
http://www.ezlippi.com/blog/2014/12/c-open-project.html
- 基于Chrome内核(WebKit.net)定制开发DoNet浏览器
1. 源起 a) 定制.Net浏览器 本人是一名C#开发者,而作为C#开发者,做客户端应用中最头痛的一件事就是没有一个好的UI解决方案, WinFrom嘛,效率虽然还不错,但是做一些特殊 ...
- 使用JSON Schema来验证接口数据
最近在做一些关于JSON Schema的基建,JSON Schema可以描述一个JSON结构,那么反过来他也可以来验证一个JSON是否符合期望的格式. 如果之前看我写的<使用joi来验证数据模型 ...
- lockfree
为什么要lockfree 按我的理解, lockfree就是不去 调用操作系统给定的锁机制. 1. 会有system call, and system call is expensive; 比如pt ...