在 Jquery 时代,实现轮播的基本思路是第一帧与最后一帧使用相同的元素,播放至最后一帧的时候,不使用动画效果切换到第一帧,形成轮播的效果。
但还有别外一种思路,不需要额外复制元素,更多是的通过样式控制。让我们分析下一个完整轮播所经历的过程与所处状态,如下图。

上面中,center 代表可视区,left 与 right 代表隐藏区(使用样式使用元素停放在不同区域,所以left center right
也代表三种样式)。我们将一个元素放置于 right 隐藏区,分析他在一个轮播的生命周期内的状态变化。
- Entering (入场动画),第一帧,元素从 right 隐藏区进入 center 可视区(从
right
样式切换到center
样式)。 - Leaving (出场动画),第二帧,元素从 center 可视区进入 left 隐藏区(从
center
样式切换到left
样式)。 - Resetting (还原),第三帧,元素从 left 隐藏区进入 right 隐藏区(从
left
样式切换right
样式),为下一次轮播做准备。这一帧用户不可见,所以不需要动画效果。
上面三个步骤,就是一个轮播的生命周期。
在实际业务中,一般需要三个或以上的元素才能构建出完善的轮播效果。我们以三个元素为例,分析在一个动画帧中,元素在三个区域的分部情况。假设我们有三个图片元素:img[0] img[1] img[2]
,以及一个外部指针ptr
,指向应该被显示的元素,也就是应该在 center 可视区的元素,下面列表给出了当ptr
指定不同下标时,各元素的样式使用情况。
- ptr -> img[0]:
img[2].left img[0].center img[1].right
- ptr -> img[1]:
img[0].left img[1].center img[2].right
- ptr -> img[3]:
img[1].left img[2].center img[0].right
总结起来,当前元素使用 center 样式,当前元素的上一个元素使用 left 样式,其它元素使用right 样式。使用定时器,不断切换ptr
的指向,然后样式加入适当的transition
属性,就形成了最终的轮播效果。
以下是使用 React 实现轮播的主要逻辑代码,使用 getClassName
计算元素的样式。使用闭包与取余循环生成数组下标makeInde
。使用 setTimeou
递归的方式实现定时器,这种实现方式不仅可以保证两帧之间的间隔不小于1000ms,而且可以减小内存溢出的概率。
演示地址://laichuanfeng.com/demo/carousel/,Github地址:https://github.com/codelegant/blog-demo/tree/master/react-carousel
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 |
clashttp://laichuanfeng.com/demo/carousel/s Carousel extends Component { state = { ptr: 0 }; timeout = null;//轮播定时器 static url = 'http://laichuanfeng.com/demo/carousel/'; static imgs = [ 'carousel_1.jpg', 'carousel_2.jpg', 'carousel_3.jpg', 'carousel_4.jpg', 'carousel_5.jpg' ]; /** * 循环输出索引 * @param startIndex {Number} 开始的索引 * @param interval {Number} 间隔 * @param length {Number} 循环长度 */ static makeIndex = ({ startIndex, interval, length }) => { let index = startIndex; return () => { index += interval; return index % length; }; }; /** * 定时器,切换指针指向 * @param startIndex {Number} 轮播开始时的元素索引 */ cyclePlay = startIndex => { const index = Carousel.makeIndex({ startIndex, interval: 1, length: Carousel.imgs.length }); const interval = 3000; this.setState({ ptr: startIndex }); if (this.timeout) clearTimeout(this.timeout); this.timeout = setTimeout(function timer() { this.setState({ ptr: index() }); this.timeout = setTimeout(timer.bind(this), interval); }.bind(this), interval); }; /** * 计算当前元素的样式 * @param index {Number} 元素的索引 */ getClassName = index => { const { ptr } = this.state; const length = Carousel.imgs.length; return index === ptr ? 'center' : index === (ptr + length - 1) % length ? 'left' : 'right'; }; componentDidMount() { this.cyclePlay(0); } render() { return ( <div className="content"> <ul className="carousel"> { Carousel.imgs.map((img, index) => <li key={index} className={this.getClassName(index)} style={{ background: `url(${Carousel.url}${img})` }} ></li> ) } </ul> </div> ); }; } |