Android 旋转木马轮播,ReactJs写旋转木马轮播图

备注:最近工作需要,要用react实现旋转木马的轮播图效果,在网上查了查没有相似的案例,只有用react实现的简单的轮播图,还有就是用jQuery实现的旋转木马轮播图,在参考了这两个实现方式的基础上,我用react转化了这种实现,过程真的很纠结,但是还是做出来了,效果还可以。

效果图:

9a4f2144a0ea

Paste_Image.png

实现思路分析

1.每张图片(li节点)的布局

{

this.props.imgArray.map(function(item,index){

return

%7Bitem%7D;

}.bind(this))

}

主要就是renderstyle函数在控制他们的排列:

renderstyle(index) {

const { number, width, imgWidth, scale, vertical, height } = this.props.lunboObject;

const middleIndex = Math.floor(number / 2);

const btnWidth = (width-imgWidth) / 2;

const gap = btnWidth/middleIndex;

let Imgleft;

let ImgTop;

let Imgscale;

let zIndex;

let opacity;

if(index <= middleIndex){

// 右侧图片

Imgscale = Math.pow(scale, (index));

Imgleft = width - (middleIndex-index)*gap - imgWidth*Imgscale;

zIndex=middleIndex+1 - index;

opacity=1/++index;

}else if(index > middleIndex){

// 左侧图片

Imgscale = Math.pow(scale, (number-index));

Imgleft = (index-(middleIndex+1))*gap;

zIndex = index-middleIndex;

opacity = 1 - middleIndex/index;

}

switch(vertical){

case 'bottom':

ImgTop = parseInt(height - height*Imgscale);

break;

case 'center':

ImgTop = parseInt((height - height*Imgscale)/2);

break;

default:

ImgTop = parseInt((height - height*Imgscale)/2);

}

return {

width: parseInt(imgWidth*Imgscale),

height: parseInt(height*Imgscale),

left:parseInt(Imgleft),

zIndex:zIndex,

opacity:opacity,

top:ImgTop

}

}

要想实现3D的效果,需要同时控制每张图片的6个属性来回变化,下面分析他们的计算过程(先布局):

index关系:

9a4f2144a0ea

Paste_Image.png

实际上要分为左右两部分实现:

右侧:

Imgscale = Math.pow(scale, (index));

Imgleft = width - (middleIndex-index)*gap - imgWidth*Imgscale;

zIndex=middleIndex+1 - index;

opacity=1/++index;

图片的Zindex和opacity需要认真考虑一下如何设置,其他的都好说,就是数学关系。

zIndex实际上最中间的是3,向右依次递减;

opacity中间是1,向右依次是1/2,1/3 ... ;

左侧:

// 左侧图片

Imgscale = Math.pow(scale, (number-index));

Imgleft = (index-(middleIndex+1))*gap;

zIndex = index-middleIndex;

opacity = 1 - middleIndex/index;

左侧唯一麻烦的是,opacity的设置,我是需找的数学规律,只要让左侧的opacity呈现1,1/2,1/3 ... 即可。

2.点击箭头让他动起来

如果是一般的轮播图就好办了,直接控制ul的left值就可以,不用直接操作dom,而这种轮播图是不断控制每一个li,让他的状态变化到上一个或者下一个li的状态,用state控制变量的方式实在是不会,所以还是操作的dom。

首先,组件挂载后,获取dom对象,组成数组:

componentDidMount() {

for(let i=0;i

this.itemsArr.push(findDOMNode(this.refs['items'+(i)]));

};

this.autoPlay();

}

然后,比如点击左侧按钮的时候,遍历dom数组,让当前的li的状态变为他的上一个(prev)li的状态,处理一下临界的问题。

this.itemsArr.forEach((item, index) => {

let self = item;

let next = this.itemsArr[index+1];

if(index == (len-1)){

next = this.itemsArr[0];

}

this.rotateStyle(self, next);

})

rotateStyle这个函数是控制他运动的,

rotateStyle(self, next) {

const { left, top, width, height, zIndex, opacity } = next.style;

this.animate(self, {left:left,width:width,height:height,zIndex:zIndex,opacity: opacity,top:top}, this.props.lunboObject.tweenString, () => {

++this.LOOPNUM ;

});

}

animate是封装的缓动函数,这个不重要,就不详细讲了。

点击右侧按钮的时候原理类似,就不再赘述。

3.自己动起来

这个就没啥可说的了,就是设置定时器不断触发右击箭头函数,鼠标移入清除定时器,鼠标移出,开启定时器即可。

4.小圆点跟着点亮

维护一个全局的state变量,activeIndex,每次dom运动的话就会变化这个值,然后控制点是否点亮。

5点击某个小圆点,让他运动到当前位置

这是难点!

代码如下:

// 点击小圆点动

gotoDotView() {

if(this.state.dotsIndex == this.state.activeIndex){

return ;

}else{

let len = this.itemsArr.length;

// 运动到小圆点指示的位置

if(this.state.dotsIndex - this.state.activeIndex > 0){

// 如果点击在右侧 向左运动

const dotsDiff = this.state.dotsIndex - this.state.activeIndex;

this.setState({

activeIndex: this.state.activeIndex + dotsDiff

})

this.itemsArr.forEach((item, index) => {

let self = item;

let nextIndex = Number.parseInt(index-dotsDiff);

if(nextIndex < 0){

nextIndex = nextIndex+len;

}

let next = this.itemsArr[nextIndex];

this.rotateStyle(self, next);

})

}else{

// 如果点击在左侧

const dotsDiff = this.state.activeIndex - this.state.dotsIndex;

this.setState({

activeIndex: this.state.activeIndex - dotsDiff

})

this.itemsArr.forEach((item, index) => {

let self = item;

let prevIndex = Number.parseInt(index+dotsDiff);

if(prevIndex >= len){

prevIndex = prevIndex-len;

}

let prev = this.itemsArr[prevIndex];

this.rotateStyle(self, prev);

})

}

}

}

这里要分为两种情况,点击点在当前活动点的右侧,或者左侧。然后记录当前两个点之间的差值,这个时候,遍历每个dom,当前的item要变为计算完差值后的item的状态,并且考虑临界值的处理,我也说不清楚,具体还是看代码吧。

6.关于缓动函数

看代码:

/*

* animate函数是动画封装函数

* @para0 elem参数就是运动的对象

* @para1 targetJSON参数就是运动的终点状态,可以写px,也可以不写px

* @para2 time是运动总时间,毫秒为单位

* @para3 tweenString缓冲描述词,比如"Linear"

* @para4 callback是回调函数,可选

*/

animate(elem , targetJSON , tweenString , callback){

// 缓冲描述词集合

const Tween = {

Linear: (t, b, c, d) => {

return c * t / d + b;

},

//二次的

QuadEaseIn: (t, b, c, d) => {

return c * (t /= d) * t + b;

},

QuadEaseOut: (t, b, c, d) => {

return -c * (t /= d) * (t - 2) + b;

},

QuadEaseInOut: (t, b, c, d) => {

if ((t /= d / 2) < 1) return c / 2 * t * t + b;

return -c / 2 * ((--t) * (t - 2) - 1) + b;

},

//三次的

CubicEaseIn: (t, b, c, d) => {

return c * (t /= d) * t * t + b;

},

CubicEaseOut: (t, b, c, d) => {

return c * ((t = t / d - 1) * t * t + 1) + b;

},

CubicEaseInOut: (t, b, c, d) => {

if ((t /= d / 2) < 1) return c / 2 * t * t * t + b;

return c / 2 * ((t -= 2) * t * t + 2) + b;

},

//四次的

QuartEaseIn: (t, b, c, d) => {

return c * (t /= d) * t * t * t + b;

},

QuartEaseOut: (t, b, c, d) => {

return -c * ((t = t / d - 1) * t * t * t - 1) + b;

},

QuartEaseInOut: (t, b, c, d) => {

if ((t /= d / 2) < 1) return c / 2 * t * t * t * t + b;

return -c / 2 * ((t -= 2) * t * t * t - 2) + b;

}

};

let interval = 15;

let time = 300;

//初始状态,放在origninalJSON里面

let originalJSON = {};

//变化的多少,放在deltaJSON里面

let deltaJSON = {};

for(let k in targetJSON){

originalJSON[k] = parseFloat(elem.style[k]);

//把每个targetJSON中的值都去掉px

targetJSON[k] = parseFloat(targetJSON[k]);

//变化量JSON

deltaJSON[k] = targetJSON[k] - originalJSON[k];

}

//总执行函数次数:

let maxFrameNumber = time / interval;

//当前帧编号

let frameNumber = 0;

//这是一个临时变量一会儿用

let tween;

//定时器

let timer = setInterval(() => {

//要让所有的属性发生变化

for(let k in originalJSON){

// tween就表示这一帧应该在的位置:

tween = Tween[tweenString](frameNumber , originalJSON[k] , deltaJSON[k] , maxFrameNumber);

//根据是不是opacity来设置单位

if(k != "opacity"){

elem.style[k] = tween + "px";

}else{

elem.style[k] = tween;

}

}

//计数器

frameNumber++;

if(frameNumber == maxFrameNumber){

for(let k in targetJSON){

if(k == "opacity" || k == "zIndex"){

elem.style[k] = targetJSON[k];

}else{

elem.style[k] = targetJSON[k] + "px";

}

}

clearInterval(timer);

//拿掉是否在动属性,设为false

callback && callback();

}

},interval);

}

实际上这个封装也不难,主要在Tween的理解上,每个缓动函数接收四个参数,分别为:当前帧编号,初始值,结束值,结束帧编号。

在一个就是注意opacity和zIndex要淡出处理一下就可以了。

最后说一下,这个轮播是可以根据实际情况进行个化配置,

lunboObject: {

"width":995,//幻灯片的宽度

"height":335,//幻灯片的高度

"imgWidth":690,//幻灯片第一帧的宽度

"interval": 2000,//幻灯片滚动的间隔时间

"scale":0.85, //记录显示比例关系

"number":5,

"autoPlay":true,

"vertical":"top", // center或者bottom,居中对齐或底部对齐

"tweenString":"QuadEaseIn" // 运动方式,缓冲曲线

}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值