原生javascript封装动画库
****转载自自己发表于牛人部落专栏的文章****
一、前言
本文记录了自己利用原生javascript构建自己的动画库的过程,在不断改进的过程中,实现以下动画效果:
针对同一个dom元素上相继发生的动画,针对以下功能,尝试实现方案,(从一个元素向多个元素的拓展并不难,这里不做深入探究):
功能1.知道动画A和动画B的发生顺序(如A先发生,B后发生),能够按照代码撰写顺序实现动画A结束时,动画B调用
功能2.在满足功能1的基础上更进一步,当不知道动画A和动画B的发生顺序(如点击按钮1触发动画A,点击按钮2触发动画B,哪个按钮先点击不确定),能够达到1)两个动画不产生并发干扰;2)可以根据按钮的先后点击顺序,一个动画结束后另一个动画运行,即实现动画序列,以及动画的链式调用。
整个代码实现的过程,是不断改进的过程,包括:
1.利用requestAnimationFrame替代setTimeout来实现动画的平滑效果。
关于requestAnimationFrame的更多资料可参考这篇博客:http://www.zhangxinxu.com/wordpress/2013/09/css3-animation-requestanimationframe-tween-%E5%8A%A8%E7%94%BB%E7%AE%97%E6%B3%95/
2.尝试引入promise
关于promise的介绍可以参考此系列博客:https://github.com/wangfupeng1988/js-async-tutorial
3.尝试引入队列控制
队列结合running标识符来避免并发干扰;
二、相关辅助代码
以下是动画库实现的相关辅助代码,动画库的实现依赖于一下js文件,必须优先于动画库引入:
1.tween.js 实现各种缓动效果,具体可参见博客:http://www.zhangxinxu.com/wordpress/2016/12/how-use-tween-js-animation-easing/
代码如下:
- /**
- *Tween 缓动相关
- */
- var tween = {
- Linear: function(t, b, c, d) {
- return c * t / d + b;
- },
- Quad: {
- easeIn: function(t, b, c, d) {
- return c * (t /= d) * t + b;
- },
- easeOut: function(t, b, c, d) {
- return -c * (t /= d) * (t - ) + b;
- },
- easeInOut: function(t, b, c, d) {
- if ((t /= d / ) < ) return c / * t * t + b;
- return -c / * ((--t) * (t - ) - ) + b;
- }
- },
- Cubic: {
- easeIn: function(t, b, c, d) {
- return c * (t /= d) * t * t + b;
- },
- easeOut: function(t, b, c, d) {
- return c * ((t = t / d - ) * t * t + ) + b;
- },
- easeInOut: function(t, b, c, d) {
- if ((t /= d / ) < ) return c / * t * t * t + b;
- return c / * ((t -= ) * t * t + ) + b;
- }
- },
- Quart: {
- easeIn: function(t, b, c, d) {
- return c * (t /= d) * t * t * t + b;
- },
- easeOut: function(t, b, c, d) {
- return -c * ((t = t / d - ) * t * t * t - ) + b;
- },
- easeInOut: function(t, b, c, d) {
- if ((t /= d / ) < ) return c / * t * t * t * t + b;
- return -c / * ((t -= ) * t * t * t - ) + b;
- }
- },
- Quint: {
- easeIn: function(t, b, c, d) {
- return c * (t /= d) * t * t * t * t + b;
- },
- easeOut: function(t, b, c, d) {
- return c * ((t = t / d - ) * t * t * t * t + ) + b;
- },
- easeInOut: function(t, b, c, d) {
- if ((t /= d / ) < ) return c / * t * t * t * t * t + b;
- return c / * ((t -= ) * t * t * t * t + ) + b;
- }
- },
- Sine: {
- easeIn: function(t, b, c, d) {
- return -c * Math.cos(t / d * (Math.PI / )) + c + b;
- },
- easeOut: function(t, b, c, d) {
- return c * Math.sin(t / d * (Math.PI / )) + b;
- },
- easeInOut: function(t, b, c, d) {
- return -c / * (Math.cos(Math.PI * t / d) - ) + b;
- }
- },
- Expo: {
- easeIn: function(t, b, c, d) {
- return (t == ) ? b : c * Math.pow(, * (t / d - )) + b;
- },
- easeOut: function(t, b, c, d) {
- return (t == d) ? b + c : c * (-Math.pow(, - * t / d) + ) + b;
- },
- easeInOut: function(t, b, c, d) {
- if (t == ) return b;
- if (t == d) return b + c;
- if ((t /= d / ) < ) return c / * Math.pow(, * (t - )) + b;
- return c / * (-Math.pow(, - * --t) + ) + b;
- }
- },
- Circ: {
- easeIn: function(t, b, c, d) {
- return -c * (Math.sqrt( - (t /= d) * t) - ) + b;
- },
- easeOut: function(t, b, c, d) {
- return c * Math.sqrt( - (t = t / d - ) * t) + b;
- },
- easeInOut: function(t, b, c, d) {
- if ((t /= d / ) < ) return -c / * (Math.sqrt( - t * t) - ) + b;
- return c / * (Math.sqrt( - (t -= ) * t) + ) + b;
- }
- },
- Elastic: {
- easeIn: function(t, b, c, d, a, p) {
- if (t == ) return b;
- if ((t /= d) == ) return b + c;
- if (!p) p = d * .;
- if (!a || a < Math.abs(c)) {
- a = c;
- var s = p / ;
- } else var s = p / ( * Math.PI) * Math.asin(c / a);
- return -(a * Math.pow(, * (t -= )) * Math.sin((t * d - s) * ( * Math.PI) / p)) + b;
- },
- easeOut: function(t, b, c, d, a, p) {
- if (t == ) return b;
- if ((t /= d) == ) return b + c;
- if (!p) p = d * .;
- if (!a || a < Math.abs(c)) {
- a = c;
- var s = p / ;
- } else var s = p / ( * Math.PI) * Math.asin(c / a);
- return (a * Math.pow(, - * t) * Math.sin((t * d - s) * ( * Math.PI) / p) + c + b);
- },
- easeInOut: function(t, b, c, d, a, p) {
- if (t == ) return b;
- if ((t /= d / ) == ) return b + c;
- if (!p) p = d * (. * 1.5);
- if (!a || a < Math.abs(c)) {
- a = c;
- var s = p / ;
- } else var s = p / ( * Math.PI) * Math.asin(c / a);
- if (t < ) return -. * (a * Math.pow(, * (t -= )) * Math.sin((t * d - s) * ( * Math.PI) / p)) + b;
- return a * Math.pow(, - * (t -= )) * Math.sin((t * d - s) * ( * Math.PI) / p) * . + c + b;
- }
- },
- Back: {
- easeIn: function(t, b, c, d, s) {
- if (s == undefined) s = 1.70158;
- return c * (t /= d) * t * ((s + ) * t - s) + b;
- },
- easeOut: function(t, b, c, d, s) {
- if (s == undefined) s = 1.70158;
- return c * ((t = t / d - ) * t * ((s + ) * t + s) + ) + b;
- },
- easeInOut: function(t, b, c, d, s) {
- if (s == undefined) s = 1.70158;
- if ((t /= d / ) < ) return c / * (t * t * (((s *= (1.525)) + ) * t - s)) + b;
- return c / * ((t -= ) * t * (((s *= (1.525)) + ) * t + s) + ) + b;
- }
- },
- Bounce: {
- easeIn: function(t, b, c, d) {
- return c - Tween.Bounce.easeOut(d - t, , c, d) + b;
- },
- easeOut: function(t, b, c, d) {
- if ((t /= d) < ( / 2.75)) {
- return c * (7.5625 * t * t) + b;
- } else if (t < ( / 2.75)) {
- return c * (7.5625 * (t -= (1.5 / 2.75)) * t + .) + b;
- } else if (t < (2.5 / 2.75)) {
- return c * (7.5625 * (t -= (2.25 / 2.75)) * t + .) + b;
- } else {
- return c * (7.5625 * (t -= (2.625 / 2.75)) * t + .) + b;
- }
- },
- easeInOut: function(t, b, c, d) {
- if (t < d / ) return Tween.Bounce.easeIn(t * , , c, d) * . + b;
- else return Tween.Bounce.easeOut(t * - d, , c, d) * . + c * . + b;
- }
- }
- };
2.辅助工具util.js,其中包括样式获取和设置的方法,以及requestAnimationFrame,cancelAnimationFrame,获取当前时间戳兼容的方法
- //获取元素属性
- //元素属性都按照整数计算
- var getStyle = function(dom, prop) {
- if (prop === 'opacity' && dom.style.filter) {
- return window.style.filter.match(/(\d+)/)[];
- }
- var tmp = window.getComputedStyle ? window.getComputedStyle(dom, null)[prop] : dom.currentStyle[prop];
- return prop === 'opacity' ? parseFloat(tmp, ) : parseInt(tmp, );
- };
- //设置元素属性
- var setStyle = function(dom, prop, value) {
- if (prop === 'opacity') {
- dom.style.filter = '(opacity(' + parseFloat(value / ) + '))';
- dom.style.opacity = value;
- return;
- }
- dom.style[prop] = parseInt(value, ) + 'px';
- };
- //requestAnimationFrame的兼容处理
- (function() {
- var lastTime = ;
- var vendors = ['webkit', 'moz'];
- for (var x = ; x < vendors.length && !window.requestAnimationFrame; ++x) {
- window.requestAnimationFrame = window[vendors[x] + 'RequestAnimationFrame'];
- window.cancelAnimationFrame = window[vendors[x] + 'CancelAnimationFrame'] ||
- window[vendors[x] + 'CancelRequestAnimationFrame'];
- }
- if (!window.requestAnimationFrame) {
- window.requestAnimationFrame = function(callback, element) {
- var currTime = new Date().getTime();
- var timeToCall = Math.max(, 16.7 - (currTime - lastTime));
- var id = window.setTimeout(function() {
- callback(currTime + timeToCall);
- }, timeToCall);
- lastTime = currTime + timeToCall;
- return id;
- };
- }
- if (!window.cancelAnimationFrame) {
- window.cancelAnimationFrame = function(id) {
- clearTimeout(id);
- };
- }
- }());
- //时间戳获取的兼容处理
- function nowtime() {
- if (typeof performance !== 'undefined' && performance.now) {
- return performance.now();
- }
- return Date.now ? Date.now() : (new Date()).getTime();
- }
3.为了便于测试,布局html文件如下:
- <!DOCTYPE html>
- <html lang="en">
- <head>
- <meta charset="UTF-8">
- <title>测试动画库</title>
- <style>
- .mydiv {
- width: 300px;
- height: 200px;
- background-color: pink;
- position: absolute;
- top: 100px;
- left: 100px;
- }
- </style>
- </head>
- <body>
- <div class="mydiv" id="mydiv"></div>
- </body>
- </html>
三、动画库animation的具体实现
1.仅考虑实现功能1:即
知道动画A和动画B的发生顺序(如A先发生,B后发生),能够按照代码撰写顺序实现动画A结束时,动画B调用
方法一:利用动画结束时,执行回调的思路,代码如下:
- //实现动画库(暂不使用promise)
- var Animate = {
- init: function(el) {
- this.el = typeof el === 'string' ? document.querySelector(el) : el;
- this.timer = null;
- return this;
- },
- initAnim: function(props, option) {
- this.propChange = {};
- this.duration = (option && option.duration) || ;
- this.easing = (option && option.easing) || tween.Linear;
- for (var prop in props) {
- this.propChange[prop] = {};
- this.propChange[prop]['to'] = props[prop];
- this.propChange[prop]['from'] = getStyle(this.el, prop);
- }
- return this;
- },
- stop: function() {
- clearTimeout(this.timer);
- this.timer = null;
- return this;
- },
- play: function(callback) {
- var startTime = ;
- var self = this;
- if (this.timer) {
- this.stop();
- }
- function step() {
- if (!startTime) {
- startTime = nowtime();
- }
- var passedTime = Math.min(nowtime() - startTime, self.duration);
- console.log('passedTime:' + passedTime + ',duration:' + self.duration);
- for (var prop in self.propChange) {
- var target = self.easing(passedTime, self.propChange[prop]['from'], self.propChange[prop]['to'] - self.propChange[prop]['from'], self.duration);
- setStyle(self.el, prop, target);
- }
- if (passedTime >= self.duration) {
- self.stop();
- if (callback) {
- callback.call(self);
- }
- } else {
- this.timer = setTimeout(step, / );
- }
- }
- this.timer = setTimeout(step, / );
- },
- runAnim: function(props, option, callback) {
- this.initAnim(props, option);
- this.play(callback);
- }
- };
调用代码如下:
- <script type="text/javascript">
- //测试animate.js
- //利用回调来实现顺序调用
- var div = document.getElementById('mydiv');
- var anim = Object.create(Animate);
- anim.init(div);
- anim.runAnim({
- width:
- }, {
- duration:
- }, function() {
- anim.runAnim({
- height:
- }, {
- duration:
- });
- });
经过测试,上述代码能够实现,长度变为500之后,高度再变为500.即实现了功能1.
但是,如果两个动画发生的先后顺序实现并不知道,如点击按钮1使得长度变为500,紧接着点击按钮2使得高度变为500,后者反过来。总之哪个按钮先按下并不知情。这种情况,上面的方法就不适用了。程序永远只执行最后一个动画事件,因为一旦进入动画执行函数play,就首先将上一个函数的timer进行了清空。
方法二:如果只是单纯的实现功能,除了动画完成执行回调的思路外,自然而然可以考虑到将回调的写法改进为promise的写法,此外下面的代码还使用requestAnimation替代了setTimeout.具体如下:
- //实现动画库
- //1.使用requestAnimationFrame
- //2.引入promise
- var Animate = {
- init: function(el) {
- this.el = typeof el === 'string' ? document.querySelector(el) : el;
- this.reqId = null;
- return this;
- },
- initAnim: function(props, option) {
- this.propChange = {};
- this.duration = (option && option.duration) || ;
- this.easing = (option && option.easing) || tween.Linear;
- for (var prop in props) {
- this.propChange[prop] = {};
- this.propChange[prop]['to'] = props[prop];
- this.propChange[prop]['from'] = getStyle(this.el, prop);
- }
- return this;
- },
- stop: function() {
- if (this.reqId) {
- cancelAnimationFrame(this.reqId);
- }
- this.reqId = null;
- return this;
- },
- play: function() {
- console.log('进入动画:');
- var startTime = ;
- var self = this;
- if (this.reqId) {
- this.stop();
- }
- return new Promise((resolve, reject) => {
- function step(timestamp) {
- if (!startTime) {
- startTime = timestamp;
- }
- var passedTime = Math.min(timestamp - startTime, self.duration);
- console.log('passedTime:' + passedTime + ',duration:' + self.duration);
- for (var prop in self.propChange) {
- var target = self.easing(passedTime, self.propChange[prop]['from'], self.propChange[prop]['to'] - self.propChange[prop]['from'], self.duration);
- setStyle(self.el, prop, target);
- }
- if (passedTime >= self.duration) {
- self.stop();
- resolve();
- } else {
- this.reqId = requestAnimationFrame(step);
- }
- }
- this.reqId = requestAnimationFrame(step);
- this.cancel = function() {
- self.stop();
- reject('cancel');
- };
- });
- },
- runAnim: function(props, option) {
- this.initAnim(props, option);
- return this.play();
- }
- };
调用方法如下:
1.可以使用promise的then方法:
- var div = document.getElementById('mydiv');
- var anim = Object.create(Animate);
- anim.init(div);
- anim.runAnim({width:},{duration:}).then(function(){
- return anim.runAnim({height:},{duration:});
- }).then(function(){
- console.log('end');
- });
2.当然也可以使用ES7新引入的async,await方法(目前chrome浏览器已经支持)
- var div = document.getElementById('mydiv');
- var anim = Object.create(Animate);
- anim.init(div);
- async function run() {
- var a = await anim.runAnim({
- width: ,
- opacity: .
- }, {
- duration:
- });
- var b = await anim.runAnim({
- height:
- }, {
- duration:
- });
- }
- run();
这种方法同样存在一样的弊端,即只适用于动画顺序实现知道的情形。
2.考虑功能2的情形,即动画发生顺序实现无法预知的情况下,在一个动画进行过程中触发另一个不会引发冲突,而是根据触发顺序依次执行。
实现思路:既然是依次,就容易想到队列,同时需要设置标志位running,保证在动画进行过程中,不会触发出队事件。
具体如下:
- //实现动画库
- //改进:利用requestAnimationFrame替代setTimeout
- var Animate = {
- init: function(el) {
- this.el = typeof el === 'string' ? document.querySelector(el) : el;
- this.queue = [];
- this.running = false;
- this.reqId = null;
- return this;
- },
- initAnim: function(props, option) {
- this.propChange = {};
- this.duration = (option && option.duration) || ;
- this.easing = (option && option.easing) || tween.Linear;
- for (var prop in props) {
- this.propChange[prop] = {};
- this.propChange[prop]['to'] = props[prop];
- this.propChange[prop]['from'] = getStyle(this.el, prop);
- }
- return this;
- },
- stop: function() {
- this.running = false;
- if (this.reqId) {
- cancelAnimationFrame(this.reqId);
- }
- this.reqId = null;
- return this;
- },
- play: function() {
- this.running = true;
- console.log('进入动画:' + this.running);
- var startTime = ;
- var self = this;
- if (this.reqId) {
- this.stop();
- }
- function step(timestamp) {
- if (!startTime) {
- startTime = timestamp;
- }
- var passedTime = Math.min(timestamp - startTime, self.duration);
- console.log('passedTime:' + passedTime + ',duration:' + self.duration);
- for (var prop in self.propChange) {
- var target = self.easing(passedTime, self.propChange[prop]['from'], self.propChange[prop]['to'] - self.propChange[prop]['from'], self.duration);
- setStyle(self.el, prop, target);
- }
- if (passedTime >= self.duration) {
- self.stop();
- //播放队列当中的下一组动画
- self.dequeue();
- } else {
- this.reqId = requestAnimationFrame(step, / );
- }
- }
- this.reqId = requestAnimationFrame(step, / );
- },
- enqueue: function(props, option) {
- this.queue.push(() => {
- this.initAnim.call(this, props, option);
- this.play.call(this);
- });
- return this;
- },
- hasNext: function() {
- return this.queue.length > ;
- },
- dequeue: function(props) {
- //console.log('length', this.queue.length);
- if (!this.running && this.hasNext()) {
- if (props) {
- for (var prop in props) {
- console.log(prop + '出队成功');
- }
- }
- //console.log('length',this.queue.length);
- this.queue.shift().call(this);
- }
- return this;
- },
- runAnim: function(props, option) {
- this.enqueue(props, option);
- //传入参数props仅仅是为了调试打印,即使不传也不影响功能
- this.dequeue(props);
- //setTimeout(this.dequeue.bind(this), 0);
- }
- };
测试方法如下:
- //测试animate2.js
- //使用requeustAnimationFrame代替settimeout实现动画库
- var div = document.getElementById('mydiv');
- var anim = Object.create(Animate);
- anim.init(div);
- anim.runAnim({
- width: ,
- opacity: .
- }, {
- duration:
- });
- anim.runAnim({
- height:
- }, {
- duration:
- });
2,考虑能否将promise与队列结合起来,于是有了下面的代码:
- //实现动画库
- //1.使用requestAnimationFrame
- //2.引入promise
- var Animate = {
- init: function(el) {
- this.el = typeof el === 'string' ? document.querySelector(el) : el;
- this.reqId = null;
- this.queue = [];
- this.running = false;
- return this;
- },
- initAnim: function(props, option) {
- this.propChange = {};
- this.duration = (option && option.duration) || ;
- this.easing = (option && option.easing) || tween.Linear;
- for (var prop in props) {
- this.propChange[prop] = {};
- this.propChange[prop]['to'] = props[prop];
- this.propChange[prop]['from'] = getStyle(this.el, prop);
- }
- return this;
- },
- stop: function() {
- if (this.reqId) {
- cancelAnimationFrame(this.reqId);
- }
- this.running = false;
- this.reqId = null;
- return this;
- },
- play: function() {
- this.running = true;
- console.log('进入动画:' + this.running);
- var startTime = ;
- var self = this;
- if (this.reqId) {
- this.stop();
- }
- return new Promise((resolve, reject) => {
- function step(timestamp) {
- if (!startTime) {
- startTime = timestamp;
- }
- var passedTime = Math.min(timestamp - startTime, self.duration);
- console.log('passedTime:' + passedTime + ',duration:' + self.duration);
- for (var prop in self.propChange) {
- var target = self.easing(passedTime, self.propChange[prop]['from'], self.propChange[prop]['to'] - self.propChange[prop]['from'], self.duration);
- setStyle(self.el, prop, target);
- }
- if (passedTime >= self.duration) {
- self.stop();
- self.dequeue();
- resolve();
- } else {
- this.reqId = requestAnimationFrame(step);
- }
- }
- this.reqId = requestAnimationFrame(step);
- this.cancel = function() {
- self.stop();
- reject('cancel');
- };
- });
- },
- hasNext: function() {
- return this.queue.length > ;
- },
- enqueue: function(props, option) {
- this.queue.push(() => {
- this.initAnim(props, option);
- return this.play();
- });
- },
- dequeue: function(callback) {
- var prom;
- if (!this.running && this.hasNext()) {
- prom = this.queue.shift().call(this);
- }
- if (callback) {
- return prom.then(() => {
- callback.call(this);
- });
- } else {
- return prom;
- }
- },
- runAnim(props, option, callback) {
- this.enqueue(props, option);
- this.dequeue(callback);
- }
- };
不过感觉这么做意义不是特别大。动画队列中的每一个元素是个函数,该函数返回一个promise,貌似看起来是为给动画队列中每一个动画结束的时候添加回调增加了可能,经过如下测试:
- var div = document.getElementById('mydiv');
- var anim = Object.create(Animate);
- anim.init(div);
- anim.runAnim({
- width:
- }, {
- duration:
- }, function() {
- console.log();
- });
- anim.runAnim({
- height:
- }, {
如果回调是个同步代码,如上面的console.log(1),那么该打印语句在宽度变为500动画结束后立即执行。
但如果回调是个异步代码,如下:
- var div = document.getElementById('mydiv');
- var anim = Object.create(Animate);
- anim.init(div);
- anim.runAnim({
- width:
- }, {
- duration:
- }, function() {
- anim.runAnim({
- opacity: .
- });
- });
- anim.runAnim({
- height:
- }, {
- duration:
- });
发现透明度的变化,实在长度变为500,并且高度变为500的动画结束之后,才执行。
总结:
1.回调与promise的关系无需多说,通过上面的代码发现二者和队列貌似也有某种联系。转念一想,貌似jquery中的defer,promise就是回调和队列结合实现的
2.上面的代码库远不完善,很多因素没有考虑,诸如多元素动画,css3动画等等。希望后续有时间能够多多优化。
二、封装javascript动画库2
参照jQuery队列设计方法,不是通过变量running判定动画是否正在执行,而是通过队列队首元素run来控制,此外还支持:
1)预定义动画序列;
2)直接到达动画最后一帧;
3)动画反转;
4)预定义动画效果。
工具类util.js
- //获取元素属性
- //返回元素对应的属性值(不包含单位)
- //考虑的特殊情况包括:
- //1.透明度,值为小数,如0.2
- //2.颜色,值的表示法有rgb,16进制表示法(缩写,不缩写。两种形式)
- //3.transform属性,包括 [ "translateZ", "scale", "scaleX", "scaleY", "translateX", "translateY", "scaleZ", "skewX", "skewY", "rotateX", "rotateY", "rotateZ" ]
- //transfrom属性中,不考虑matrix,translate(30,40),translate3d等复合写法
- // 上面的功能尚未实现,等有时间补上
- (function(window) {
- var transformPropNames = ["translateZ", "scale", "scaleX", "scaleY", "translateX", "translateY", "scaleZ", "skewX", "skewY", "rotateX", "rotateY", "rotateZ"];
- window.getStyle = function(dom, prop) {
- var tmp = window.getComputedStyle ? window.getComputedStyle(dom, null)[prop] : dom.currentStyle[prop];
- return prop === 'opacity' ? parseFloat(tmp, ) : parseInt(tmp, );
- };
- //设置元素属性
- window.setStyle = function(dom, prop, value) {
- if (prop === 'opacity') {
- dom.style.filter = '(opacity(' + parseFloat(value * ) + '))';
- dom.style.opacity = value;
- return;
- }
- dom.style[prop] = parseInt(value, ) + 'px';
- };
- })(window);
- //requestAnimationFrame的兼容处理
- (function() {
- var lastTime = ;
- var vendors = ['webkit', 'moz'];
- for (var x = ; x < vendors.length && !window.requestAnimationFrame; ++x) {
- window.requestAnimationFrame = window[vendors[x] + 'RequestAnimationFrame'];
- window.cancelAnimationFrame = window[vendors[x] + 'CancelAnimationFrame'] ||
- window[vendors[x] + 'CancelRequestAnimationFrame'];
- }
- if (!window.requestAnimationFrame) {
- window.requestAnimationFrame = function(callback, element) {
- var currTime = new Date().getTime();
- var timeToCall = Math.max(, 16.7 - (currTime - lastTime));
- var id = window.setTimeout(function() {
- callback(currTime + timeToCall);
- }, timeToCall);
- lastTime = currTime + timeToCall;
- return id;
- };
- }
- if (!window.cancelAnimationFrame) {
- window.cancelAnimationFrame = function(id) {
- clearTimeout(id);
- };
- }
- }());
- //时间戳获取的兼容处理
- function nowtime() {
- if (typeof performance !== 'undefined' && performance.now) {
- return performance.now();
- }
- return Date.now ? Date.now() : (new Date()).getTime();
- }
util.js
缓动效果:tween.js
- /**
- *Tween 缓动相关
- */
- var tween = {
- Linear: function(t, b, c, d) {
- return c * t / d + b;
- },
- Quad: {
- easeIn: function(t, b, c, d) {
- return c * (t /= d) * t + b;
- },
- easeOut: function(t, b, c, d) {
- return -c * (t /= d) * (t - ) + b;
- },
- easeInOut: function(t, b, c, d) {
- if ((t /= d / ) < ) return c / * t * t + b;
- return -c / * ((--t) * (t - ) - ) + b;
- }
- },
- Cubic: {
- easeIn: function(t, b, c, d) {
- return c * (t /= d) * t * t + b;
- },
- easeOut: function(t, b, c, d) {
- return c * ((t = t / d - ) * t * t + ) + b;
- },
- easeInOut: function(t, b, c, d) {
- if ((t /= d / ) < ) return c / * t * t * t + b;
- return c / * ((t -= ) * t * t + ) + b;
- }
- },
- Quart: {
- easeIn: function(t, b, c, d) {
- return c * (t /= d) * t * t * t + b;
- },
- easeOut: function(t, b, c, d) {
- return -c * ((t = t / d - ) * t * t * t - ) + b;
- },
- easeInOut: function(t, b, c, d) {
- if ((t /= d / ) < ) return c / * t * t * t * t + b;
- return -c / * ((t -= ) * t * t * t - ) + b;
- }
- },
- Quint: {
- easeIn: function(t, b, c, d) {
- return c * (t /= d) * t * t * t * t + b;
- },
- easeOut: function(t, b, c, d) {
- return c * ((t = t / d - ) * t * t * t * t + ) + b;
- },
- easeInOut: function(t, b, c, d) {
- if ((t /= d / ) < ) return c / * t * t * t * t * t + b;
- return c / * ((t -= ) * t * t * t * t + ) + b;
- }
- },
- Sine: {
- easeIn: function(t, b, c, d) {
- return -c * Math.cos(t / d * (Math.PI / )) + c + b;
- },
- easeOut: function(t, b, c, d) {
- return c * Math.sin(t / d * (Math.PI / )) + b;
- },
- easeInOut: function(t, b, c, d) {
- return -c / * (Math.cos(Math.PI * t / d) - ) + b;
- }
- },
- Expo: {
- easeIn: function(t, b, c, d) {
- return (t == ) ? b : c * Math.pow(, * (t / d - )) + b;
- },
- easeOut: function(t, b, c, d) {
- return (t == d) ? b + c : c * (-Math.pow(, - * t / d) + ) + b;
- },
- easeInOut: function(t, b, c, d) {
- if (t == ) return b;
- if (t == d) return b + c;
- if ((t /= d / ) < ) return c / * Math.pow(, * (t - )) + b;
- return c / * (-Math.pow(, - * --t) + ) + b;
- }
- },
- Circ: {
- easeIn: function(t, b, c, d) {
- return -c * (Math.sqrt( - (t /= d) * t) - ) + b;
- },
- easeOut: function(t, b, c, d) {
- return c * Math.sqrt( - (t = t / d - ) * t) + b;
- },
- easeInOut: function(t, b, c, d) {
- if ((t /= d / ) < ) return -c / * (Math.sqrt( - t * t) - ) + b;
- return c / * (Math.sqrt( - (t -= ) * t) + ) + b;
- }
- },
- Elastic: {
- easeIn: function(t, b, c, d, a, p) {
- if (t == ) return b;
- if ((t /= d) == ) return b + c;
- if (!p) p = d * .;
- if (!a || a < Math.abs(c)) {
- a = c;
- var s = p / ;
- } else var s = p / ( * Math.PI) * Math.asin(c / a);
- return -(a * Math.pow(, * (t -= )) * Math.sin((t * d - s) * ( * Math.PI) / p)) + b;
- },
- easeOut: function(t, b, c, d, a, p) {
- if (t == ) return b;
- if ((t /= d) == ) return b + c;
- if (!p) p = d * .;
- if (!a || a < Math.abs(c)) {
- a = c;
- var s = p / ;
- } else var s = p / ( * Math.PI) * Math.asin(c / a);
- return (a * Math.pow(, - * t) * Math.sin((t * d - s) * ( * Math.PI) / p) + c + b);
- },
- easeInOut: function(t, b, c, d, a, p) {
- if (t == ) return b;
- if ((t /= d / ) == ) return b + c;
- if (!p) p = d * (. * 1.5);
- if (!a || a < Math.abs(c)) {
- a = c;
- var s = p / ;
- } else var s = p / ( * Math.PI) * Math.asin(c / a);
- if (t < ) return -. * (a * Math.pow(, * (t -= )) * Math.sin((t * d - s) * ( * Math.PI) / p)) + b;
- return a * Math.pow(, - * (t -= )) * Math.sin((t * d - s) * ( * Math.PI) / p) * . + c + b;
- }
- },
- Back: {
- easeIn: function(t, b, c, d, s) {
- if (s == undefined) s = 1.70158;
- return c * (t /= d) * t * ((s + ) * t - s) + b;
- },
- easeOut: function(t, b, c, d, s) {
- if (s == undefined) s = 1.70158;
- return c * ((t = t / d - ) * t * ((s + ) * t + s) + ) + b;
- },
- easeInOut: function(t, b, c, d, s) {
- if (s == undefined) s = 1.70158;
- if ((t /= d / ) < ) return c / * (t * t * (((s *= (1.525)) + ) * t - s)) + b;
- return c / * ((t -= ) * t * (((s *= (1.525)) + ) * t + s) + ) + b;
- }
- },
- Bounce: {
- easeIn: function(t, b, c, d) {
- return c - Tween.Bounce.easeOut(d - t, , c, d) + b;
- },
- easeOut: function(t, b, c, d) {
- if ((t /= d) < ( / 2.75)) {
- return c * (7.5625 * t * t) + b;
- } else if (t < ( / 2.75)) {
- return c * (7.5625 * (t -= (1.5 / 2.75)) * t + .) + b;
- } else if (t < (2.5 / 2.75)) {
- return c * (7.5625 * (t -= (2.25 / 2.75)) * t + .) + b;
- } else {
- return c * (7.5625 * (t -= (2.625 / 2.75)) * t + .) + b;
- }
- },
- easeInOut: function(t, b, c, d) {
- if (t < d / ) return Tween.Bounce.easeIn(t * , , c, d) * . + b;
- else return Tween.Bounce.easeOut(t * - d, , c, d) * . + c * . + b;
- }
- }
- };
tween.js
具体实现animation.js
- var Animate = {
- init: function(el) {
- this.dom = typeof el === 'string' ? document.querySelector(el) : el;
- // console.log(this.dom);
- this.queue = [];
- this.isRuning = false;
- this.reqId = null;
- this.toEnd = false;
- },
- initAnim: function(props, opts) {
- this.propchanges = {};
- this.duration = (opts && opts.duration) || ;
- this.easing = (opts && opts.easing) || tween.Linear;
- //为了实现reverse,需要initProps来记录变化之前的数值
- this.initprops = {};
- // 可以使用数组同时指定开始值和结束值,也可以仅仅指定结束值
- for (var prop in props) {
- this.propchanges[prop] = {};
- if (Array.isArray(props[prop])) {
- this.propchanges[prop]['from'] = this.initprops[prop] = props[prop][];
- this.propchanges[prop]['to'] = props[prop][];
- } else {
- this.propchanges[prop]['from'] = this.initprops[prop] = getStyle(this.dom, prop);
- this.propchanges[prop]['to'] = props[prop];
- }
- }
- return this;
- },
- stop: function() {
- this.isRuning = false;
- if (this.reqId) {
- cancelAnimationFrame(this.reqId);
- this.reqId = null;
- }
- return this;
- },
- play: function(opts) {
- console.log('opts', opts);
- this.isRuning = true;
- var self = this;
- var startTime;
- function tick(timestamp) {
- var curTime = timestamp || nowtime();
- if (!startTime) {
- startTime = curTime;
- }
- // console.log('passedTime', curTime - startTime);
- var passedTime = Math.min(curTime - startTime, self.duration);
- // 实现finish功能,直接到达动画最终状态
- if (self.toEnd) {
- passedTime = self.duration;
- }
- for (var prop in self.propchanges) {
- var curValue = self.easing(passedTime, self.propchanges[prop]['from'], self.propchanges[prop]['to'] - self.propchanges[prop]['from'], self.duration);
- console.log(prop + ':' + passedTime, curValue);
- setStyle(self.dom, prop, curValue);
- }
- if (passedTime >= self.duration) {
- //动画停止
- self.stop(); //在stop中将isRunning置为了false
- // startTime = 0;
- //下一个动画出队
- self.dequeue();
- if (opts.next) {
- opts.next.call(null);
- }
- } else if (self.isRuning) {
- self.reqId = requestAnimationFrame(tick);
- }
- //必须将判断放在else里面
- //否则经过试验,链式调用时,除了第一个动画外,其他动画会出现问题
- //这是因为,虽然stop中将isRunning置为了false
- //但是接下来的dequeue执行play,又马上将isRunning置为了true
- // if (self.isRuning) {
- // self.reqId = requestAnimationFrame(tick);
- // }
- }
- tick();
- return this;
- },
- // 如果当前有动画正在执行,那么动画队列的首个元素一定是'run'
- // 动画函数出队之后,开始执行前,立即在队列头部添加一个'run'元素,代表动画函数正在执行
- // 只有当对应动画函数执行完之后,才会调用出队操作,原队首的'run'元素才可以出队
- // 如果动画函数执行完毕,调用出队操作之后,动画队列中还有下一个动画函数,下一个动画函数出队后,执行之前,依旧将队列头部置为'run',重复上述操作
- // 如果动画函数执行完毕,调用出队操作之后,动画队列中没有其他动画函数,那么队首的‘run’元素出队之后,队列为空
- // 首次入队时,动画队列的首个元素不是'run',动画立即出队执行
- //
- enqueue: function(fn) {
- this.queue.push(fn);
- if (this.queue[] !== 'run') {
- this.dequeue();
- }
- },
- //上一个版本使用isRuning来控制出队执行的时机,这里运用队首的'run'来控制,isRunning的一一貌似不大
- dequeue: function() {
- while (this.queue.length) {
- var curItem = this.queue.shift();
- if (typeof curItem === 'function') {
- curItem.call(this); //这是个异步操作
- this.queue.unshift('run');
- break;
- }
- }
- },
- // 对外接口:开始动画的入口函数
- animate: function(props, opts) {
- // console.log(typeof this.queue);
- this.enqueue(() => {
- this.initAnim(props, opts);
- this.play(opts);
- });
- return this;
- },
- // 对外接口,直接到达动画的最终状态
- finish: function() {
- this.toEnd = true;
- return this;
- },
- // 对外接口:恢复到最初状态
- reverse: function() {
- if (!this.initprops) {
- alert('尚未调用任何动画,不能反转!');
- }
- this.animate(this.initprops);
- return this;
- },
- //
- runsequence: function(sequence) {
- let reSequence = sequence.reverse();
- reSequence.forEach((curItem, index) => {
- if (index >= ) {
- prevItem = reSequence[index - ];
- curItem.o.next = function() {
- var anim = Object.create(Animate);
- anim.init(prevItem.e);
- anim.animate(prevItem.p, prevItem.o);
- };
- }
- });
- var firstItem = reSequence[reSequence.length - ];
- var firstAnim = Object.create(Animate);
- firstAnim.init(firstItem.e);
- firstAnim.animate(firstItem.p, firstItem.o);
- },
- };
myanimation.js
预定义动画和预定义动画序列
- // 实现一些自定义动画
- ;
- (function(window) {
- const Animate = window.Animate;
- if (!Animate) {
- console.log('请首先引入myanimate.js');
- return;
- }
- const effects = {
- "transition.slideUpIn": {
- defaultDuration: ,
- calls: [
- [{ opacity: [, ], translateY: [, ] }]
- ]
- },
- "transition.slideUpOut": {
- defaultDuration: ,
- calls: [
- [{ opacity: [, ], translateY: - }]
- ],
- reset: { translateY: }
- },
- "transition.slideDownIn": {
- defaultDuration: ,
- calls: [
- [{ opacity: [, ], translateY: [, -] }]
- ]
- },
- "transition.slideDownOut": {
- defaultDuration: ,
- calls: [
- [{ opacity: [, ], translateY: }]
- ],
- reset: { translateY: }
- },
- "transition.slideLeftIn": {
- defaultDuration: ,
- calls: [
- [{ opacity: [, ], translateX: [, -] }]
- ]
- },
- "transition.slideLeftOut": {
- defaultDuration: ,
- calls: [
- [{ opacity: [, ], translateX: - }]
- ],
- reset: { translateX: }
- },
- "transition.slideRightIn": {
- defaultDuration: ,
- calls: [
- [{ opacity: [, ], translateX: [, ] }]
- ]
- },
- "transition.slideRightOut": {
- defaultDuration: ,
- calls: [
- [{ opacity: [, ], translateX: , translateZ: }]
- ],
- reset: { translateX: }
- },
- "callout.pulse": {
- defaultDuration: ,
- calls: [
- [{ scaleX: 1.1 }, 0.50],
- [{ scaleX: }, 0.50]
- ]
- },
- 'test': {
- defaultDuration: ,
- calls: [
- [{ left: , opacity: 0.1 }, 0.5],
- [{ opacity: }, 0.5]
- ]
- }
- };
- Animate.runEffect = function(effectName) {
- let curEffect = effects[effectName];
- if (!curEffect) {
- return;
- }
- let sequence = [];
- let defaultDuration = curEffect.defaultDuration;
- curEffect.calls.forEach((item, index) => {
- let propMap = item[];
- let duration = item[] ? item[] * defaultDuration : defaultDuration;
- let options = item[] || {};
- options.duration = duration;
- sequence.push({
- e: this.dom,
- p: propMap,
- o: options
- });
- });
- Animate.runsequence(sequence);
- };
- })(window);
myanimation.effect.js
测试代码:
- <!DOCTYPE html>
- <html lang="en">
- <head>
- <meta charset="UTF-8">
- <title>测试动画库</title>
- <style type="text/css">
- .main {
- padding: 50px;
- position: relative;
- }
- .btn-wrapper {
- padding: 15px ;
- }
- .mydiv {
- margin: 20px ;
- width: 300px;
- height: 200px;
- background-color: pink;
- position: relative;
- top: ;
- left: ;
- }
- </style>
- </head>
- <body>
- <div class="main">
- <div class="mydiv" id="mydiv"></div>
- <div id="btn-wrapper">
- <button id="chainBtn">链式调用</button>
- </div>
- <div class="mydiv" id="mydiv-reverse"></div>
- <div id="btn-wrapper">
- <button id="reverseBtn">reverse调用</button>
- </div>
- <div class="mydiv" id="mydiv-predefine1"></div>
- <div class="mydiv" id="mydiv-predefine2"></div>
- <div id="btn-wrapper">
- <button id="predefineBtn">预定义动画队列</button>
- </div>
- <div class="mydiv" id="mydiv-effect"></div>
- <div id="btn-wrapper">
- <button id="effectBtn">预定义动画</button>
- </div>
- </div>
- <script src="./util.js"></script>
- <script src="./tween.js"></script>
- <script src="./myanimate.js"></script>
- <script src="./myanimate.effect.js"></script>
- <script type="text/javascript">
- // 链式调用
- document.querySelector('#chainBtn').addEventListener('click', function(e) {
- var div = document.getElementById('mydiv');
- var anim = Object.create(Animate);
- anim.init(div);
- anim.animate({
- opacity: 0.2
- }).animate({
- left:
- });
- //测试停止动画,stop函数
- // setTimeout(function() {
- // anim.stop();
- // }, 500);
- //测试直接到达动画的最终状态,finish函数
- //如果是链式调用,到达所有动画的最终状态
- //如果只想到达当前动画的最终状态,只需要稍微修改,在stop中重置toEnd=false即可
- // setTimeout(function() {
- // anim.finish();
- // }, 500);
- });
- //reverse调用
- document.querySelector('#reverseBtn').addEventListener('click', function(e) {
- var div = document.getElementById('mydiv-reverse');
- var anim = Object.create(Animate);
- anim.init(div);
- anim.animate({
- left:
- }).reverse();
- });
- //预定义动画测试
- document.querySelector('#predefineBtn').addEventListener('click', function(e) {
- var anims = [{
- e: '#mydiv-predefine1',
- p: {
- left:
- },
- o: {
- duration:
- }
- }, {
- e: '#mydiv-predefine2',
- p: {
- left: ,
- opacity: 0.3
- },
- o: {
- duration:
- }
- }];
- //不需要新建一个实例,直接在Animate上调用即可
- Animate.runsequence(anims);
- });
- //预定义动画测试
- document.querySelector('#effectBtn').addEventListener('click', function(e) {
- var anim = Object.create(Animate);
- anim.init('#mydiv-effect');
- anim.runEffect('test');
- });
- </script>
- </body>
- </html>
index.html
附上一份jQuery动画部分的源代码
- var fxNow, timerId,
- rfxtypes = /^(?:toggle|show|hide)$/,
- rfxnum = new RegExp( "^(?:([+-])=|)(" + core_pnum + ")([a-z%]*)$", "i" ),
- rrun = /queueHooks$/,
- animationPrefilters = [ defaultPrefilter ],
- tweeners = {
- "*": [function( prop, value ) {
- var tween = this.createTween( prop, value ),
- target = tween.cur(),
- parts = rfxnum.exec( value ),
- unit = parts && parts[ 3 ] || ( jQuery.cssNumber[ prop ] ? "" : "px" ),
- // Starting value computation is required for potential unit mismatches
- start = ( jQuery.cssNumber[ prop ] || unit !== "px" && +target ) &&
- rfxnum.exec( jQuery.css( tween.elem, prop ) ),
- scale = 1,
- maxIterations = 20;
- if ( start && start[ 3 ] !== unit ) {
- // Trust units reported by jQuery.css
- unit = unit || start[ 3 ];
- // Make sure we update the tween properties later on
- parts = parts || [];
- // Iteratively approximate from a nonzero starting point
- start = +target || 1;
- do {
- // If previous iteration zeroed out, double until we get *something*
- // Use a string for doubling factor so we don't accidentally see scale as unchanged below
- scale = scale || ".5";
- // Adjust and apply
- start = start / scale;
- jQuery.style( tween.elem, prop, start + unit );
- // Update scale, tolerating zero or NaN from tween.cur()
- // And breaking the loop if scale is unchanged or perfect, or if we've just had enough
- } while ( scale !== (scale = tween.cur() / target) && scale !== 1 && --maxIterations );
- }
- // Update tween properties
- if ( parts ) {
- start = tween.start = +start || +target || 0;
- tween.unit = unit;
- // If a +=/-= token was provided, we're doing a relative animation
- tween.end = parts[ 1 ] ?
- start + ( parts[ 1 ] + 1 ) * parts[ 2 ] :
- +parts[ 2 ];
- }
- return tween;
- }]
- };
- // Animations created synchronously will run synchronously
- function createFxNow() {
- setTimeout(function() {
- fxNow = undefined;
- });
- return ( fxNow = jQuery.now() );
- }
- function createTween( value, prop, animation ) {
- var tween,
- collection = ( tweeners[ prop ] || [] ).concat( tweeners[ "*" ] ),
- index = 0,
- length = collection.length;
- for ( ; index < length; index++ ) {
- if ( (tween = collection[ index ].call( animation, prop, value )) ) {
- // we're done with this property
- return tween;
- }
- }
- }
- function Animation( elem, properties, options ) {
- var result,
- stopped,
- index = 0,
- length = animationPrefilters.length,
- deferred = jQuery.Deferred().always( function() {
- // don't match elem in the :animated selector
- delete tick.elem;
- }),
- tick = function() {
- if ( stopped ) {
- return false;
- }
- var currentTime = fxNow || createFxNow(),
- remaining = Math.max( 0, animation.startTime + animation.duration - currentTime ),
- // archaic crash bug won't allow us to use 1 - ( 0.5 || 0 ) (#12497)
- temp = remaining / animation.duration || 0,
- percent = 1 - temp,
- index = 0,
- length = animation.tweens.length;
- for ( ; index < length ; index++ ) {
- animation.tweens[ index ].run( percent );
- }
- deferred.notifyWith( elem, [ animation, percent, remaining ]);
- if ( percent < 1 && length ) {
- return remaining;
- } else {
- deferred.resolveWith( elem, [ animation ] );
- return false;
- }
- },
- animation = deferred.promise({
- elem: elem,
- props: jQuery.extend( {}, properties ),
- opts: jQuery.extend( true, { specialEasing: {} }, options ),
- originalProperties: properties,
- originalOptions: options,
- startTime: fxNow || createFxNow(),
- duration: options.duration,
- tweens: [],
- createTween: function( prop, end ) {
- var tween = jQuery.Tween( elem, animation.opts, prop, end,
- animation.opts.specialEasing[ prop ] || animation.opts.easing );
- animation.tweens.push( tween );
- return tween;
- },
- stop: function( gotoEnd ) {
- var index = 0,
- // if we are going to the end, we want to run all the tweens
- // otherwise we skip this part
- length = gotoEnd ? animation.tweens.length : 0;
- if ( stopped ) {
- return this;
- }
- stopped = true;
- for ( ; index < length ; index++ ) {
- animation.tweens[ index ].run( 1 );
- }
- // resolve when we played the last frame
- // otherwise, reject
- if ( gotoEnd ) {
- deferred.resolveWith( elem, [ animation, gotoEnd ] );
- } else {
- deferred.rejectWith( elem, [ animation, gotoEnd ] );
- }
- return this;
- }
- }),
- props = animation.props;
- propFilter( props, animation.opts.specialEasing );
- for ( ; index < length ; index++ ) {
- result = animationPrefilters[ index ].call( animation, elem, props, animation.opts );
- if ( result ) {
- return result;
- }
- }
- jQuery.map( props, createTween, animation );
- if ( jQuery.isFunction( animation.opts.start ) ) {
- animation.opts.start.call( elem, animation );
- }
- jQuery.fx.timer(
- jQuery.extend( tick, {
- elem: elem,
- anim: animation,
- queue: animation.opts.queue
- })
- );
- // attach callbacks from options
- return animation.progress( animation.opts.progress )
- .done( animation.opts.done, animation.opts.complete )
- .fail( animation.opts.fail )
- .always( animation.opts.always );
- }
- function propFilter( props, specialEasing ) {
- var index, name, easing, value, hooks;
- // camelCase, specialEasing and expand cssHook pass
- for ( index in props ) {
- name = jQuery.camelCase( index );
- easing = specialEasing[ name ];
- value = props[ index ];
- if ( jQuery.isArray( value ) ) {
- easing = value[ 1 ];
- value = props[ index ] = value[ 0 ];
- }
- if ( index !== name ) {
- props[ name ] = value;
- delete props[ index ];
- }
- hooks = jQuery.cssHooks[ name ];
- if ( hooks && "expand" in hooks ) {
- value = hooks.expand( value );
- delete props[ name ];
- // not quite $.extend, this wont overwrite keys already present.
- // also - reusing 'index' from above because we have the correct "name"
- for ( index in value ) {
- if ( !( index in props ) ) {
- props[ index ] = value[ index ];
- specialEasing[ index ] = easing;
- }
- }
- } else {
- specialEasing[ name ] = easing;
- }
- }
- }
- jQuery.Animation = jQuery.extend( Animation, {
- tweener: function( props, callback ) {
- if ( jQuery.isFunction( props ) ) {
- callback = props;
- props = [ "*" ];
- } else {
- props = props.split(" ");
- }
- var prop,
- index = 0,
- length = props.length;
- for ( ; index < length ; index++ ) {
- prop = props[ index ];
- tweeners[ prop ] = tweeners[ prop ] || [];
- tweeners[ prop ].unshift( callback );
- }
- },
- prefilter: function( callback, prepend ) {
- if ( prepend ) {
- animationPrefilters.unshift( callback );
- } else {
- animationPrefilters.push( callback );
- }
- }
- });
- function defaultPrefilter( elem, props, opts ) {
- /* jshint validthis: true */
- var prop, value, toggle, tween, hooks, oldfire,
- anim = this,
- orig = {},
- style = elem.style,
- hidden = elem.nodeType && isHidden( elem ),
- dataShow = data_priv.get( elem, "fxshow" );
- // handle queue: false promises
- if ( !opts.queue ) {
- hooks = jQuery._queueHooks( elem, "fx" );
- if ( hooks.unqueued == null ) {
- hooks.unqueued = 0;
- oldfire = hooks.empty.fire;
- hooks.empty.fire = function() {
- if ( !hooks.unqueued ) {
- oldfire();
- }
- };
- }
- hooks.unqueued++;
- anim.always(function() {
- // doing this makes sure that the complete handler will be called
- // before this completes
- anim.always(function() {
- hooks.unqueued--;
- if ( !jQuery.queue( elem, "fx" ).length ) {
- hooks.empty.fire();
- }
- });
- });
- }
- // height/width overflow pass
- if ( elem.nodeType === 1 && ( "height" in props || "width" in props ) ) {
- // Make sure that nothing sneaks out
- // Record all 3 overflow attributes because IE9-10 do not
- // change the overflow attribute when overflowX and
- // overflowY are set to the same value
- opts.overflow = [ style.overflow, style.overflowX, style.overflowY ];
- // Set display property to inline-block for height/width
- // animations on inline elements that are having width/height animated
- if ( jQuery.css( elem, "display" ) === "inline" &&
- jQuery.css( elem, "float" ) === "none" ) {
- style.display = "inline-block";
- }
- }
- if ( opts.overflow ) {
- style.overflow = "hidden";
- anim.always(function() {
- style.overflow = opts.overflow[ 0 ];
- style.overflowX = opts.overflow[ 1 ];
- style.overflowY = opts.overflow[ 2 ];
- });
- }
- // show/hide pass
- for ( prop in props ) {
- value = props[ prop ];
- if ( rfxtypes.exec( value ) ) {
- delete props[ prop ];
- toggle = toggle || value === "toggle";
- if ( value === ( hidden ? "hide" : "show" ) ) {
- // If there is dataShow left over from a stopped hide or show and we are going to proceed with show, we should pretend to be hidden
- if ( value === "show" && dataShow && dataShow[ prop ] !== undefined ) {
- hidden = true;
- } else {
- continue;
- }
- }
- orig[ prop ] = dataShow && dataShow[ prop ] || jQuery.style( elem, prop );
- }
- }
- if ( !jQuery.isEmptyObject( orig ) ) {
- if ( dataShow ) {
- if ( "hidden" in dataShow ) {
- hidden = dataShow.hidden;
- }
- } else {
- dataShow = data_priv.access( elem, "fxshow", {} );
- }
- // store state if its toggle - enables .stop().toggle() to "reverse"
- if ( toggle ) {
- dataShow.hidden = !hidden;
- }
- if ( hidden ) {
- jQuery( elem ).show();
- } else {
- anim.done(function() {
- jQuery( elem ).hide();
- });
- }
- anim.done(function() {
- var prop;
- data_priv.remove( elem, "fxshow" );
- for ( prop in orig ) {
- jQuery.style( elem, prop, orig[ prop ] );
- }
- });
- for ( prop in orig ) {
- tween = createTween( hidden ? dataShow[ prop ] : 0, prop, anim );
- if ( !( prop in dataShow ) ) {
- dataShow[ prop ] = tween.start;
- if ( hidden ) {
- tween.end = tween.start;
- tween.start = prop === "width" || prop === "height" ? 1 : 0;
- }
- }
- }
- }
- }
- function Tween( elem, options, prop, end, easing ) {
- return new Tween.prototype.init( elem, options, prop, end, easing );
- }
- jQuery.Tween = Tween;
- Tween.prototype = {
- constructor: Tween,
- init: function( elem, options, prop, end, easing, unit ) {
- this.elem = elem;
- this.prop = prop;
- this.easing = easing || "swing";
- this.options = options;
- this.start = this.now = this.cur();
- this.end = end;
- this.unit = unit || ( jQuery.cssNumber[ prop ] ? "" : "px" );
- },
- cur: function() {
- var hooks = Tween.propHooks[ this.prop ];
- return hooks && hooks.get ?
- hooks.get( this ) :
- Tween.propHooks._default.get( this );
- },
- run: function( percent ) {
- var eased,
- hooks = Tween.propHooks[ this.prop ];
- if ( this.options.duration ) {
- this.pos = eased = jQuery.easing[ this.easing ](
- percent, this.options.duration * percent, 0, 1, this.options.duration
- );
- } else {
- this.pos = eased = percent;
- }
- this.now = ( this.end - this.start ) * eased + this.start;
- if ( this.options.step ) {
- this.options.step.call( this.elem, this.now, this );
- }
- if ( hooks && hooks.set ) {
- hooks.set( this );
- } else {
- Tween.propHooks._default.set( this );
- }
- return this;
- }
- };
- Tween.prototype.init.prototype = Tween.prototype;
- Tween.propHooks = {
- _default: {
- get: function( tween ) {
- var result;
- if ( tween.elem[ tween.prop ] != null &&
- (!tween.elem.style || tween.elem.style[ tween.prop ] == null) ) {
- return tween.elem[ tween.prop ];
- }
- // passing an empty string as a 3rd parameter to .css will automatically
- // attempt a parseFloat and fallback to a string if the parse fails
- // so, simple values such as "10px" are parsed to Float.
- // complex values such as "rotate(1rad)" are returned as is.
- result = jQuery.css( tween.elem, tween.prop, "" );
- // Empty strings, null, undefined and "auto" are converted to 0.
- return !result || result === "auto" ? 0 : result;
- },
- set: function( tween ) {
- // use step hook for back compat - use cssHook if its there - use .style if its
- // available and use plain properties where available
- if ( jQuery.fx.step[ tween.prop ] ) {
- jQuery.fx.step[ tween.prop ]( tween );
- } else if ( tween.elem.style && ( tween.elem.style[ jQuery.cssProps[ tween.prop ] ] != null || jQuery.cssHooks[ tween.prop ] ) ) {
- jQuery.style( tween.elem, tween.prop, tween.now + tween.unit );
- } else {
- tween.elem[ tween.prop ] = tween.now;
- }
- }
- }
- };
- // Support: IE9
- // Panic based approach to setting things on disconnected nodes
- Tween.propHooks.scrollTop = Tween.propHooks.scrollLeft = {
- set: function( tween ) {
- if ( tween.elem.nodeType && tween.elem.parentNode ) {
- tween.elem[ tween.prop ] = tween.now;
- }
- }
- };
- jQuery.each([ "toggle", "show", "hide" ], function( i, name ) {
- var cssFn = jQuery.fn[ name ];
- jQuery.fn[ name ] = function( speed, easing, callback ) {
- return speed == null || typeof speed === "boolean" ?
- cssFn.apply( this, arguments ) :
- this.animate( genFx( name, true ), speed, easing, callback );
- };
- });
- jQuery.fn.extend({
- fadeTo: function( speed, to, easing, callback ) {
- // show any hidden elements after setting opacity to 0
- return this.filter( isHidden ).css( "opacity", 0 ).show()
- // animate to the value specified
- .end().animate({ opacity: to }, speed, easing, callback );
- },
- animate: function( prop, speed, easing, callback ) {
- var empty = jQuery.isEmptyObject( prop ),
- optall = jQuery.speed( speed, easing, callback ),
- doAnimation = function() {
- // Operate on a copy of prop so per-property easing won't be lost
- var anim = Animation( this, jQuery.extend( {}, prop ), optall );
- // Empty animations, or finishing resolves immediately
- if ( empty || data_priv.get( this, "finish" ) ) {
- anim.stop( true );
- }
- };
- doAnimation.finish = doAnimation;
- return empty || optall.queue === false ?
- this.each( doAnimation ) :
- this.queue( optall.queue, doAnimation );
- },
- stop: function( type, clearQueue, gotoEnd ) {
- var stopQueue = function( hooks ) {
- var stop = hooks.stop;
- delete hooks.stop;
- stop( gotoEnd );
- };
- if ( typeof type !== "string" ) {
- gotoEnd = clearQueue;
- clearQueue = type;
- type = undefined;
- }
- if ( clearQueue && type !== false ) {
- this.queue( type || "fx", [] );
- }
- return this.each(function() {
- var dequeue = true,
- index = type != null && type + "queueHooks",
- timers = jQuery.timers,
- data = data_priv.get( this );
- if ( index ) {
- if ( data[ index ] && data[ index ].stop ) {
- stopQueue( data[ index ] );
- }
- } else {
- for ( index in data ) {
- if ( data[ index ] && data[ index ].stop && rrun.test( index ) ) {
- stopQueue( data[ index ] );
- }
- }
- }
- for ( index = timers.length; index--; ) {
- if ( timers[ index ].elem === this && (type == null || timers[ index ].queue === type) ) {
- timers[ index ].anim.stop( gotoEnd );
- dequeue = false;
- timers.splice( index, 1 );
- }
- }
- // start the next in the queue if the last step wasn't forced
- // timers currently will call their complete callbacks, which will dequeue
- // but only if they were gotoEnd
- if ( dequeue || !gotoEnd ) {
- jQuery.dequeue( this, type );
- }
- });
- },
- finish: function( type ) {
- if ( type !== false ) {
- type = type || "fx";
- }
- return this.each(function() {
- var index,
- data = data_priv.get( this ),
- queue = data[ type + "queue" ],
- hooks = data[ type + "queueHooks" ],
- timers = jQuery.timers,
- length = queue ? queue.length : 0;
- // enable finishing flag on private data
- data.finish = true;
- // empty the queue first
- jQuery.queue( this, type, [] );
- if ( hooks && hooks.stop ) {
- hooks.stop.call( this, true );
- }
- // look for any active animations, and finish them
- for ( index = timers.length; index--; ) {
- if ( timers[ index ].elem === this && timers[ index ].queue === type ) {
- timers[ index ].anim.stop( true );
- timers.splice( index, 1 );
- }
- }
- // look for any animations in the old queue and finish them
- for ( index = 0; index < length; index++ ) {
- if ( queue[ index ] && queue[ index ].finish ) {
- queue[ index ].finish.call( this );
- }
- }
- // turn off finishing flag
- delete data.finish;
- });
- }
- });
- // Generate parameters to create a standard animation
- function genFx( type, includeWidth ) {
- var which,
- attrs = { height: type },
- i = 0;
- // if we include width, step value is 1 to do all cssExpand values,
- // if we don't include width, step value is 2 to skip over Left and Right
- includeWidth = includeWidth? 1 : 0;
- for( ; i < 4 ; i += 2 - includeWidth ) {
- which = cssExpand[ i ];
- attrs[ "margin" + which ] = attrs[ "padding" + which ] = type;
- }
- if ( includeWidth ) {
- attrs.opacity = attrs.width = type;
- }
- return attrs;
- }
- // Generate shortcuts for custom animations
- jQuery.each({
- slideDown: genFx("show"),
- slideUp: genFx("hide"),
- slideToggle: genFx("toggle"),
- fadeIn: { opacity: "show" },
- fadeOut: { opacity: "hide" },
- fadeToggle: { opacity: "toggle" }
- }, function( name, props ) {
- jQuery.fn[ name ] = function( speed, easing, callback ) {
- return this.animate( props, speed, easing, callback );
- };
- });
- jQuery.speed = function( speed, easing, fn ) {
- var opt = speed && typeof speed === "object" ? jQuery.extend( {}, speed ) : {
- complete: fn || !fn && easing ||
- jQuery.isFunction( speed ) && speed,
- duration: speed,
- easing: fn && easing || easing && !jQuery.isFunction( easing ) && easing
- };
- opt.duration = jQuery.fx.off ? 0 : typeof opt.duration === "number" ? opt.duration :
- opt.duration in jQuery.fx.speeds ? jQuery.fx.speeds[ opt.duration ] : jQuery.fx.speeds._default;
- // normalize opt.queue - true/undefined/null -> "fx"
- if ( opt.queue == null || opt.queue === true ) {
- opt.queue = "fx";
- }
- // Queueing
- opt.old = opt.complete;
- opt.complete = function() {
- if ( jQuery.isFunction( opt.old ) ) {
- opt.old.call( this );
- }
- if ( opt.queue ) {
- jQuery.dequeue( this, opt.queue );
- }
- };
- return opt;
- };
- jQuery.easing = {
- linear: function( p ) {
- return p;
- },
- swing: function( p ) {
- return 0.5 - Math.cos( p*Math.PI ) / 2;
- }
- };
- jQuery.timers = [];
- jQuery.fx = Tween.prototype.init;
- jQuery.fx.tick = function() {
- var timer,
- timers = jQuery.timers,
- i = 0;
- fxNow = jQuery.now();
- for ( ; i < timers.length; i++ ) {
- timer = timers[ i ];
- // Checks the timer has not already been removed
- if ( !timer() && timers[ i ] === timer ) {
- timers.splice( i--, 1 );
- }
- }
- if ( !timers.length ) {
- jQuery.fx.stop();
- }
- fxNow = undefined;
- };
- jQuery.fx.timer = function( timer ) {
- if ( timer() && jQuery.timers.push( timer ) ) {
- jQuery.fx.start();
- }
- };
- jQuery.fx.interval = 13;
- jQuery.fx.start = function() {
- if ( !timerId ) {
- timerId = setInterval( jQuery.fx.tick, jQuery.fx.interval );
- }
- };
- jQuery.fx.stop = function() {
- clearInterval( timerId );
- timerId = null;
- };
- jQuery.fx.speeds = {
- slow: 600,
- fast: 200,
- // Default speed
- _default: 400
- };
- // Back Compat <1.8 extension point
- jQuery.fx.step = {};
- if ( jQuery.expr && jQuery.expr.filters ) {
- jQuery.expr.filters.animated = function( elem ) {
- return jQuery.grep(jQuery.timers, function( fn ) {
- return elem === fn.elem;
- }).length;
- };
- }
- jQuery.fn.offset = function( options ) {
- if ( arguments.length ) {
- return options === undefined ?
- this :
- this.each(function( i ) {
- jQuery.offset.setOffset( this, options, i );
- });
- }
- var docElem, win,
- elem = this[ 0 ],
- box = { top: 0, left: 0 },
- doc = elem && elem.ownerDocument;
- if ( !doc ) {
- return;
- }
- docElem = doc.documentElement;
- // Make sure it's not a disconnected DOM node
- if ( !jQuery.contains( docElem, elem ) ) {
- return box;
- }
- // If we don't have gBCR, just use 0,0 rather than error
- // BlackBerry 5, iOS 3 (original iPhone)
- if ( typeof elem.getBoundingClientRect !== core_strundefined ) {
- box = elem.getBoundingClientRect();
- }
- win = getWindow( doc );
- return {
- top: box.top + win.pageYOffset - docElem.clientTop,
- left: box.left + win.pageXOffset - docElem.clientLeft
- };
- };
- jQuery.offset = {
- setOffset: function( elem, options, i ) {
- var curPosition, curLeft, curCSSTop, curTop, curOffset, curCSSLeft, calculatePosition,
- position = jQuery.css( elem, "position" ),
- curElem = jQuery( elem ),
- props = {};
- // Set position first, in-case top/left are set even on static elem
- if ( position === "static" ) {
- elem.style.position = "relative";
- }
- curOffset = curElem.offset();
- curCSSTop = jQuery.css( elem, "top" );
- curCSSLeft = jQuery.css( elem, "left" );
- calculatePosition = ( position === "absolute" || position === "fixed" ) && ( curCSSTop + curCSSLeft ).indexOf("auto") > -1;
- // Need to be able to calculate position if either top or left is auto and position is either absolute or fixed
- if ( calculatePosition ) {
- curPosition = curElem.position();
- curTop = curPosition.top;
- curLeft = curPosition.left;
- } else {
- curTop = parseFloat( curCSSTop ) || 0;
- curLeft = parseFloat( curCSSLeft ) || 0;
- }
- if ( jQuery.isFunction( options ) ) {
- options = options.call( elem, i, curOffset );
- }
- if ( options.top != null ) {
- props.top = ( options.top - curOffset.top ) + curTop;
- }
- if ( options.left != null ) {
- props.left = ( options.left - curOffset.left ) + curLeft;
- }
- if ( "using" in options ) {
- options.using.call( elem, props );
- } else {
- curElem.css( props );
- }
- }
- };
- jQuery.fn.extend({
- position: function() {
- if ( !this[ 0 ] ) {
- return;
- }
- var offsetParent, offset,
- elem = this[ 0 ],
- parentOffset = { top: 0, left: 0 };
- // Fixed elements are offset from window (parentOffset = {top:0, left: 0}, because it is it's only offset parent
- if ( jQuery.css( elem, "position" ) === "fixed" ) {
- // We assume that getBoundingClientRect is available when computed position is fixed
- offset = elem.getBoundingClientRect();
- } else {
- // Get *real* offsetParent
- offsetParent = this.offsetParent();
- // Get correct offsets
- offset = this.offset();
- if ( !jQuery.nodeName( offsetParent[ 0 ], "html" ) ) {
- parentOffset = offsetParent.offset();
- }
- // Add offsetParent borders
- parentOffset.top += jQuery.css( offsetParent[ 0 ], "borderTopWidth", true );
- parentOffset.left += jQuery.css( offsetParent[ 0 ], "borderLeftWidth", true );
- }
- // Subtract parent offsets and element margins
- return {
- top: offset.top - parentOffset.top - jQuery.css( elem, "marginTop", true ),
- left: offset.left - parentOffset.left - jQuery.css( elem, "marginLeft", true )
- };
- },
- offsetParent: function() {
- return this.map(function() {
- var offsetParent = this.offsetParent || docElem;
- while ( offsetParent && ( !jQuery.nodeName( offsetParent, "html" ) && jQuery.css( offsetParent, "position") === "static" ) ) {
- offsetParent = offsetParent.offsetParent;
- }
- return offsetParent || docElem;
- });
- }
- });
- // Create scrollLeft and scrollTop methods
- jQuery.each( {scrollLeft: "pageXOffset", scrollTop: "pageYOffset"}, function( method, prop ) {
- var top = "pageYOffset" === prop;
- jQuery.fn[ method ] = function( val ) {
- return jQuery.access( this, function( elem, method, val ) {
- var win = getWindow( elem );
- if ( val === undefined ) {
- return win ? win[ prop ] : elem[ method ];
- }
- if ( win ) {
- win.scrollTo(
- !top ? val : window.pageXOffset,
- top ? val : window.pageYOffset
- );
- } else {
- elem[ method ] = val;
- }
- }, method, val, arguments.length, null );
- };
- });
- function getWindow( elem ) {
- return jQuery.isWindow( elem ) ? elem : elem.nodeType === 9 && elem.defaultView;
- }
- // Create innerHeight, innerWidth, height, width, outerHeight and outerWidth methods
- jQuery.each( { Height: "height", Width: "width" }, function( name, type ) {
- jQuery.each( { padding: "inner" + name, content: type, "": "outer" + name }, function( defaultExtra, funcName ) {
- // margin is only for outerHeight, outerWidth
- jQuery.fn[ funcName ] = function( margin, value ) {
- var chainable = arguments.length && ( defaultExtra || typeof margin !== "boolean" ),
- extra = defaultExtra || ( margin === true || value === true ? "margin" : "border" );
- return jQuery.access( this, function( elem, type, value ) {
- var doc;
- if ( jQuery.isWindow( elem ) ) {
- // As of 5/8/2012 this will yield incorrect results for Mobile Safari, but there
- // isn't a whole lot we can do. See pull request at this URL for discussion:
- // https://github.com/jquery/jquery/pull/764
- return elem.document.documentElement[ "client" + name ];
- }
- // Get document width or height
- if ( elem.nodeType === 9 ) {
- doc = elem.documentElement;
- // Either scroll[Width/Height] or offset[Width/Height] or client[Width/Height],
- // whichever is greatest
- return Math.max(
- elem.body[ "scroll" + name ], doc[ "scroll" + name ],
- elem.body[ "offset" + name ], doc[ "offset" + name ],
- doc[ "client" + name ]
- );
- }
- return value === undefined ?
- // Get width or height on the element, requesting but not forcing parseFloat
- jQuery.css( elem, type, extra ) :
- // Set width or height on the element
- jQuery.style( elem, type, value, extra );
- }, type, chainable ? margin : undefined, chainable, null );
- };
- });
- });
jQuery动画部分源码
原生javascript封装动画库的更多相关文章
- 原生JavaScript 封装ajax
原生JavaScript 封装ajax function myajax(options){ //新建一个局部对象 用来存放用户输入的各种参数 var opt={ type:options.type ...
- 原生JavaScript封装的jsonp跨域请求
原生JavaScript封装的jsonp跨域请求 <!DOCTYPE html> <html lang="en"> <head> <met ...
- 原生javascript封装ajax和jsonp
在我们请求数据时,完成页面跨域,利用原生JS封装的ajax和jsonp: <!DOCTYPE html> <html lang="en"> <head ...
- 原生javascript封装的函数
1.javascript 加载的函数 window.onload = function(){} 2.封装的id函数 function $(id) { return document.getElemen ...
- 原生JavaScript封装Ajax
第一次开个人技术博客了,发的第一篇技术文章,欢迎指点…… 欢迎访问本人的独立博客:蓝克比尔 Ajax的实现主要分为四部分: 1.创建Ajax对象 // 创建ajax对象 var xhr = null; ...
- 2019年10个最受欢迎的JavaScript动画库!
摘要: 非常炫酷的动画库! 原文:值得看看,2019 年 11 个受欢迎的 JavaScript 动画库! 作者:前端小智 Fundebug经授权转载,版权归原作者所有. 1. Three.js 超过 ...
- Snabbt.js – 极简的 JavaScript 动画库
Snabbt.js 是一个简约的 JavaScript 动画库.它会平移,旋转,缩放,倾斜和调整你的元素.通过矩阵乘法运算,变换等可以任何你想要的方式进行组合.最终的结果通过 CSS3 变换矩阵设置. ...
- 10个最好的 JavaScript 动画库和开发框架
虽然 CSS3 动画功能能够让我们以简单轻松的方式实现动画效果,但是浏览器兼容性问题让人头疼.不过不用担心,我们还有另外的武器——JavaScript,它同样可以帮助你实现各种各样的动画效果,而且借助 ...
- JavaScript 动画库和开发框架
1. Tween JS TweenJS 是一个简单的 JavaScript 补间动画库.能够很好的和 EaselJS 库集成,但也不依赖或特定于它.它支持渐变的数字对象属性和 CSS 样式属性.API ...
随机推荐
- C# 批量设置窗体中控件状态的方法
在开发中常遇到当点击某个按钮的时候,禁用文本框或按钮的的状态,以防止误操作,下面的代码是我已批量设置指定控件中的按钮状态的代码,同理可以延伸出很多操作. /// <summary> /// ...
- Maven错误:警告Classpath entry org.eclipse.m2e.MAVEN2_CLASSPATH_CONTAINER will not be exported or published
该错误是在我将一个普通的由maven管理的java项目变为javaweb项目后出现的,由警告可以看出是说maven的类路径容器不会被导出或发布(即通过maven管理的依赖不会被导出或发布),那么我们用 ...
- bootstrap-table的简单使用
先上效果图: 第一步:引用bootstrap-table的样式和js. @Styles.Render("~/assets/css/bootstrap.css") @Styles.R ...
- CUDA中使用多维数组
今天想起一个问题,看到的绝大多数CUDA代码都是使用的一维数组,是否可以在CUDA中使用一维数组,这是一个问题,想了各种问题,各种被77的错误状态码和段错误折磨,最后发现有一个cudaMallocMa ...
- 算法---Face_Recognition配置实战篇
python人脸识别库Face_Recognition-实操篇 @WP20190307 ================================目 录===================== ...
- Objective-C语法总结收集
PART1--详解Objective-C语法快速参考 一.XCode.Objective-C.Cocoa说的是几样东西? 答案:三样东西. XCode:你可以把它看成是一个开发环境,就好像Visual ...
- VMware厚置备延迟置零,厚置备置零,精简置备详解
1.厚置备延迟置零(zeroed thick) 以默认的厚格式创建虚拟磁盘.创建过程中为虚拟磁盘分配所需空间.创建时不会擦除物理设备上保留的任何数据,但是以后从虚拟机首次执行写操作时会按需要将其置零. ...
- golang mysql 模糊查询
db.SqlDB.Query("SELECT id,name FROM test_table where title name like CONCAT('%',?,'%');", ...
- object xml
http://stackoverflow.com/questions/17739330/xmlserializer-convert-c-sharp-object-to-xml-string http: ...
- lnmp配置
yum源切换 下载wegt工具 yum install -y wget 备份 mv /etc/yum.repos.d/CentOS-Base.repo /etc/yum.repos.d/CentOS- ...