原生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 ...
随机推荐
- java8【一、lambda表达式语法】
特点 lambda表达式允许将函数作为方法的参数 lambda表达式更加简洁 特征 可选类型声明:不需要声明参数类型,编译器可以统一识别参数值. 可选的参数圆括号:一个参数无需定义圆括号,但多个参数需 ...
- springMVC接受json类型数据
springMVC接受json格式的数据很简单 使用@RequestBody 注解,标识从请求的body中取值 服务端示例代码 @RequestMapping(value = "/t4&qu ...
- Unity 用脚本给EventTrigger添加各种事件
using System.Collections; using System.Collections.Generic; using UnityEngine; using UnityEngine.Eve ...
- c# redis密码验证笔记
参考博客https://www.cnblogs.com/qukaicheng/p/7514168.html写的 安装教程https://www.redis.net.cn/tutorial/3503.h ...
- 6. Java基本数据类型
Java 基本数据类型 变量就是申请内存来存储值.也就是说,当创建变量的时候,需要在内存中申请空间. 内存管理系统根据变量的类型为变量分配存储空间,分配的空间只能用来储存该类型数据. 因此,通过定义不 ...
- sketch最强切图工具Sketch Measure
https://www.inpandora.com/sketch-measure.html https://www.jianshu.com/p/c11ae88e6b1d
- 无法删除登录名 '***',因为该用户当前正处于登录状态。 (Microsoft SQL Server,错误: 15434)
问题描述: 当删除数据库用户时,有时会得到这样的提示: 无法删除登录名 '***',因为该用户当前正处于登录状态. (Microsoft SQL Server,错误: 15434) 解决办法: 1.首 ...
- 手把手封装axios
大佬链接: https://juejin.im/post/5b55c118f265da0f6f1aa354 一.axios的封装 在vue项目中,和后台交互获取数据这块,我们通常使用的是axios库, ...
- 客户想要的 vs 客户实际预算:漫画解读软件开发模式
转自:http://blog.jobbole.com/113230/ 1913 年,美利坚工业之神——亨利福特,发明了世界上第一条流水线,汽车工业从此进入了大规模生产的时代.丰田公司提出的丰田生产系统 ...
- Java秒杀实战 (七)安全优化
转自:https://blog.csdn.net/qq_41305266/article/details/81174782 一.隐藏秒杀地址 思路:秒杀开始前,先去请求接口获取秒杀地址 1.接口改造, ...