起因:
公司每个月都要做推广活动,很多推广活动都需要抽奖,但是以前的抽奖的特效太简单,于是美工看到京东的年会抽奖机,我就不得不走向逆向仿制的道路上,经过三天的攻克,终于实现了抽奖效果。
分析:
水果抽奖机的动画效果是三个轮播图进行滚动到指定的位置。虽然是三个,但是只要破解一个,将其他动画依次延迟执行就可以了。
分析其中的一个轮播动画,就会发现就是我们平常写的轮播图,只不多这个轮播可以自己进行轮播,并且由快到慢,是一个减速运动,最后停止到指定的轮播图中。
本来想用swiper.js进行轮播的实现,但是swiper在loop模式和freeMode模式下有bug,不得不自力更生。
轮播很简单,无非是通过定时器不断的改变的位置,但是要注意要把第一张图片复制为最后一张图,这是为了让轮播更流畅,否则轮播的就会显得很尴尬,具体原理类似于摄影的视觉停留。但是使用setTimeout和setInterval定时器可以达到效果,但是W3C中推出了requestAnimateFrame这样更优秀的浏览器的定时效果。具体用法自定百度。
轮播解决以后要解决轮播的速度,推荐看一下tween.js的源码,或者其他人的分析,我这里使用的linear,就是匀速运动,之所以不适用easeInOut使用为它只针对一次轮播,会发现这样的轮播效果 —快平慢–快平慢—快平慢—…..,所以只能自己控制速度。怎沫控制速度哪?先看我的linear函数:
linear: function(t, b, c, d) {
/*
*tween是ImagesLoop的原型属性,表示图片容器运动曲线函数
*@param number t 当前的时间
*@param number b 当前的初始值
*@param number c 当前的改变的值
*@param number d 当前的改变值所用的时间
*@return object object 运动曲线函数组合成的对象
*/
return t*(c/d)+b;
}
其实公式很简单就是(时间*速度+初始值=当前的位置),而速度就是(改变的距离/所需的时间)。具体到这次的轮播就是(改变的距离=显示最后一张图片是top的值,而所需时间就是自定义时间段),所以我们可以改变自定义的时间段达到改变速度的目的。整体时间曲线是先加速到平缓再到减速,为了达到这个效果,我们制定轮播15次,时间段为500,前5次时间段每次*0.8,中间5次不变,最后5次*1.8。例如:
easeInOut: function(count = 0, duration = 10) {
/*
*easeInOut方法通过控制完成每次轮播的时间来控制轮播的速度
*@param number defalutCount 默认轮播的次数
*@param number count 当前轮播的次数
*/
let percent = parseInt(this.DEFALUT_COUNT / 3);
if (count == (this.DEFALUT_COUNT - 1)) {
this.slideToIndex();
return duration;
}
if (count < percent) {
return duration * 0.9;
} else if (count < (2 * percent)) {
return duration;
} else if (count < this.DEFALUT_COUNT) {
return duration * 1.5;
}
}
最后要解决的问题是,滚动到指定位置。例如滚动到第5张图,意味着轮播容器最后top等于前4张高度之和乘以-1加上‘px’。因为我的每次的初始值是从0开始的(top=0),所以最后一次轮播我将top值调整到我的指定图片所需的top值。
结论:顺利完成水果机。
代码:
function ImagesLoop(obj) {
if (!this.empty(obj)
|| !this.empty(obj.slideObjs)
|| !this.empty(obj.slideWrap)) {
return ;
}
this.interval = obj.interval || 200;
this.slideObjs = obj.slideObjs || {};
this.slideWrap = obj.slideWrap || {};
this.DEFALUT_COUNT = obj.defaultCount || 15;
this.slideObjNum = this.slideObjs.length;
this.totalHeight = this.getTotalHeight(this.slideObjs.slice(0,(this.slideObjs.length - 2)));
this.index = obj.index || 3;
this.stopAnimate = false;
this.print('ImagesLoop', {
slideObjs:this.slideObjs,
slideWrap:this.slideWrap,
totalHeight:this.totalHeight
});
}
ImagesLoop.prototype = {
empty: function(param) { //检测参数是否为空
if (!param) {
throw new Error(param + 'Parameters can\'t empty!');
return false;
}
return true;
},
getTotalHeight: function(slideObjs = []) { //获取所有slide对象的所有高度的总和
let totalHeight = 0;
slideObjs.forEach((item) => {
totalHeight += item.height;
});
return totalHeight;
},
print: function(fnName = 'fn', obj = {}) { //打印所有的参数
console.group(fnName);
Object.keys(obj).forEach((key) => {
console.log(key, obj[key]);
});
console.groupEnd('end'+fnName);
},
linear: function(t, b, c, d) {
/*
*tween是ImagesLoop的原型属性,表示图片容器运动曲线函数
*@param number t 当前的时间
*@param number b 当前的初始值
*@param number c 当前的改变的值
*@param number d 当前的改变值所用的时间
*@return object object 运动曲线函数组合成的对象
*/
return t*(c/d)+b;
},
compatibleRequestAnimationFrame: function() {
/*
*compatibleRequestAnimationFrame方法通过对requestAnimationFrame进行兼容性处理
*/
if (!window.requestAnimationFrame) {
requestAnimationFrame = function(fn) {
setTimeout(fn, 17);
};
}
if(!window.requestAnimationFrame){
let lastTime = 0;
window.requestAnimationFrame = function(callback){
let currTime = new Date().getTime();
let timeToCall = Math.max(0,16.7-(currTime - lastTime));
let timer = window.setTimeout(function(){
callback(currTime + timeToCall);
},timeToCall);
lastTime = currTime + timeToCall;
return timer;
}
}
if (!window.cancelAnimationFrame) {
window.cancelAnimationFrame = function(timer) {
clearTimeout(timer);
};
}
window.requestAnimationFrame = window.requestAnimationFrame
|| window.mozRequestAnimationFrame
|| window.webkitRequestAnimationFrame
|| window.msRequestAnimationFrame
|| window.oRequestAnimationFrame;
window.cancelAnimationFrame = window.cancelAnimationFrame
|| window.mozCancelAnimationFrame;
},
slideToIndex: function() {
/*
*slideToIndex方法指定轮播滑动到指定的图片
*@param void
*@return void
*/
let _slideObjs = this.slideObjs.slice(0, this.index);
this.totalHeight = this.getTotalHeight(_slideObjs);
this.stopAnimate = true;
},
animate: function() {
/*
*animate方法执行轮播的动画
*@param void
*@return void
*/
let _self = this,
timer = null;
this.compatibleRequestAnimationFrame();
cancelAnimationFrame(timer);
let duration = 400,
startTime = new Date().getTime(),
count = 0,
position = 0,
lastSlideObjHeight = _self.slideObjs[(_self.slideObjs.length - 1)].height;
timer = requestAnimationFrame(function fn(timestamp) {
let currentTime = new Date().getTime(),
currentDuration = currentTime - startTime;
if (currentDuration >= duration) {
position = position - _self.totalHeight;
if (_self.stopAnimate) {
position = _self.totalHeight;
}
startTime = currentTime;
++count;
duration = _self.easeInOut(count, duration);
} else {
position = _self.linear(currentDuration, 0, _self.totalHeight, duration);
}
if (count >= _self.DEFALUT_COUNT) {
_self.slideWrap.style.top = -1 * _self.totalHeight + 'px';
cancelAnimationFrame(timer);
timer = null;
return;
}
_self.slideWrap.style.top = -1 * position + 'px';
timer = requestAnimationFrame(fn);
});
},
easeInOut: function(count = 0, duration = 10) {
/*
*easeInOut方法通过控制完成每次轮播的时间来控制轮播的速度
*@param number defalutCount 默认轮播的次数
*@param number count 当前轮播的次数
*/
let percent = parseInt(this.DEFALUT_COUNT / 3);
if (count == (this.DEFALUT_COUNT - 1)) {
this.slideToIndex();
return duration;
}
if (count < percent) {
return duration * 0.9;
} else if (count < (2 * percent)) {
return duration;
} else if (count < this.DEFALUT_COUNT) {
return duration * 1.5;
}
},
init: function() {
/*
*init方法用于当做启动动画
*@param void
*@return void
*/
this.animate();
}
}
vue代码:
export default {
data() {
return {
images: [],
loopSlideObjs: [],
loopContainer: {},
loopWrapObjs: [],
animateStyle: {},
cssRule: {},
loopWrapNums: 3
}
},
created() {
this.init();
},
mounted() {
this.initDomData();
},
methods: {
init() {
this.initData();
},
initData() { //舒适化静态数据和对象
this.initImagesData();
},
initImagesData() { //初始化图片数据
this.images = [
{'src': '../../static/imgs/waterfull/hy1.jpeg'},
{'src': '../../static/imgs/waterfull/hy2.jpg'},
{'src': '../../static/imgs/waterfull/hy3.jpg'},
{'src': '../../static/imgs/waterfull/hy4.jpg'},
{'src': '../../static/imgs/waterfull/hy5.jpg'},
{'src': '../../static/imgs/waterfull/hy6.jpg'},
{'src': '../../static/imgs/waterfull/hy7.jpg'},
{'src': '../../static/imgs/waterfull/hy8.jpg'},
{'src': '../../static/imgs/waterfull/hy9.jpg'},
{'src': '../../static/imgs/waterfull/hy10.jpg'},
{'src': '../../static/imgs/waterfull/hy1.jpeg'}
];
},
initDomData() { //初始化dom数据
this.initSlidesDomData();
this.initLoopImagesAnimate(0);
this.compatibleRequestAnimationFrame();
},
initSlidesDomData() { //初始化轮播的每个slide的dom数据
let liObjs = document.querySelectorAll('.loop-container .loop-wrap.loop-wrap0 .loop-slide') || this.$refs.loopSlides;
this.loopWrapObjs = document.querySelectorAll('.loop-container .loop-wrap') || this.$refs.loopWrap;
let obj = {},
item = {};
this.loopContainer.totalHeight = 0;
for (let i = 0, len = liObjs.length; i < len; i++) {
item = liObjs[i];
obj.index = i;
obj.height = item.offsetHeight;
this.loopContainer.totalHeight += obj.height;
this.loopSlideObjs.push(obj);
obj = {};
item = {};
}
this.print('initDomData', {
liObjs: liObjs,
loopWrapObjs:this.loopWrapObjs,
loopSlideObjs: this.thisloopSlideObjs,
loopContainer: this.loopContainer
});
obj = null;
item = null;
liObjs = null;
},
initLoopImagesAnimate(index) { //初始化轮播的动画
let _self = this;
this.startLoopImagesAnimate();
},
compatibleRequestAnimationFrame: function() {
/*
*compatibleRequestAnimationFrame方法通过对requestAnimationFrame进行兼容性处理
*/
if (!window.requestAnimationFrame) {
requestAnimationFrame = function(fn) {
setTimeout(fn, 17);
};
}
if(!window.requestAnimationFrame){
let lastTime = 0;
window.requestAnimationFrame = function(callback){
let currTime = new Date().getTime();
let timeToCall = Math.max(0,16.7-(currTime - lastTime));
let timer = window.setTimeout(function(){
callback(currTime + timeToCall);
},timeToCall);
lastTime = currTime + timeToCall;
return timer;
}
}
if (!window.cancelAnimationFrame) {
window.cancelAnimationFrame = function(timer) {
clearTimeout(timer);
};
}
window.requestAnimationFrame = window.requestAnimationFrame
|| window.mozRequestAnimationFrame
|| window.webkitRequestAnimationFrame
|| window.msRequestAnimationFrame
|| window.oRequestAnimationFrame;
window.cancelAnimationFrame = window.cancelAnimationFrame
|| window.mozCancelAnimationFrame;
},
startLoopImagesAnimate: function() { //开始轮播图片动画
let _self = this,
num = this.loopWrapObjs.length,
count = 0,
duration = 1000,
startTime = new Date().getTime(),
timer = null;
timer = requestAnimationFrame(function fn() {
let currentTime = new Date().getTime(),
currentDuration = currentTime - startTime;
if (count >= num) {
cancelAnimationFrame(timer);
timer = null;
return ;
}
if (currentDuration >= duration) {
_self.initImagesLoopObj(_self.loopWrapObjs[count]);
startTime = currentTime;
++count;
}
timer = requestAnimationFrame(fn);
});
},
initImagesLoopObj: function(loopWrapObj) { //初始化轮播图片的对象
let imagesLoop = new ImagesLoop({
interval: 100,
slideObjs: this.loopSlideObjs,
slideWrap: loopWrapObj,
index: 6
});
imagesLoop.init();
},
print: function(fnName = 'fn', obj = {}) { //打印所有的参数
console.group(fnName);
Object.keys(obj).forEach((key) => {
console.log(key, obj[key]);
});
console.groupEnd('end'+fnName);
}
}
}