如何设计一个轮播图组件

您所在的位置:网站首页 轮播图模块名称怎么填 如何设计一个轮播图组件

如何设计一个轮播图组件

2024-07-15 21:04| 来源: 网络整理| 查看: 265

1. 轮播图基本原理

轮播图(Carousel),在 Antd 中被称为走马灯,可能是前端开发者最常见的组件之一了,不管是在 PC 端还是在移动端我们总能见到他的身影.

那么我们通常是如何使用轮播图的呢?Antd 的代码如下:

1 2 3 4

问题是我们在Carousel中放入了四组div为什么一次只显示一组呢?

图中被红框圈住的为可视区域,可视区域的位置是固定的,我们只需要移动后面div的位置就可以做到1 2 3 4四个子组件轮播的效果,那么子组件2目前在可视区域是可以被看到的,1 3 4应该被隐藏,这就需要我们设置overflow 属性为 hidden来隐藏非可视区域的子组件

复制查看动图: https://images2015.cnblogs.com/ 因此就比较明显了,我们设计一个可视窗口组件Frame,然后将四个 div共同放入幻灯片组合组件SlideList中,并用SlideItem分别将 div包裹起来,实际代码应该是这样的:

1 2 3 4

我们不断利用translateX来改变SlideList的位置来达到轮播效果,如下图所示,每次轮播的触发都是通过改变transform: translateX()来操作的

2. 轮播图基础实现

搞清楚基本原理那么实现起来相对容易了,我们以移动端的实现为例,来实现一个基础的移动端轮播图.

首先我们要确定可视窗口的宽度,因为我们需要这个宽度来计算出SlideList的长度(SlideList的长度通常是可视窗口的倍数,比如要放三张图片,那么SlideList应该为可视窗口的至少3倍),不然我们无法通过translateX来移动它. 我们通过getBoundingClientRect来获取可视区域真实的长度,SlideList的长度那么为:

slideListWidth = (len + 2) * width(len 为传入子组件的数量,width 为可视区域宽度)

至于为什么要+2后面会提到.

/** * 设置轮播区域尺寸 * @param x */ private setSize(x?: number) { const { width } = this.frameRef.current!.getBoundingClientRect() const len = React.Children.count(this.props.children) const total = len + 2 this.setState({ slideItemWidth: width, slideListWidth: total * width, total, translateX: -width * this.state.currentIndex, startPositionX: x !== undefined ? x : 0, }) }

获取到了总长度之后如何实现轮播呢?我们需要根据用户反馈来触发轮播,在移动端通常是通过手指滑动来触发轮播,这就需要三个事件onTouchStart onTouchMove onTouchEnd.

onTouchStart顾名思义是在手指触摸到屏幕时触发的事件,在这个事件里我们只需要记录下手指触摸屏幕的横轴坐标 x 即可,因为我们会通过其横向滑动的距离大小来判断是否触发轮播

/** * 处理触摸起始时的事件 * * @private * @param {React.TouchEvent} e * @memberof Carousel */ private onTouchStart(e: React.TouchEvent) { clearInterval(this.autoPlayTimer) // 获取起始的横轴坐标 const { x } = getPosition(e) this.setSize(x) this.setState({ startPositionX: x, }) }

onTouchMove顾名思义是处于滑动状态下的事件,此事件在onTouchStart触发后,onTouchEnd触发前,在这个事件中我们主要做两件事,一件事是判断滑动方向,因为用户可能向左或者向右滑动,另一件事是让轮播图跟随手指移动,这是必要的用户反馈.

/** * 当触摸滑动时处理事件 * * @private * @param {React.TouchEvent} e * @memberof Carousel */ private onTouchMove(e: React.TouchEvent) { const { slideItemWidth, currentIndex, startPositionX } = this.state const { x } = getPosition(e) const deltaX = x - startPositionX // 判断滑动方向 const direction = deltaX > 0 ? 'right' : 'left' this.setState({ direction, moveDeltaX: deltaX, // 改变translateX来达到轮播组件跟随手指移动的效果 translateX: -(slideItemWidth * currentIndex) + deltaX, }) }

onTouchEnd顾名思义是滑动完毕时触发的事件,在此事件中我们主要做一个件事情,就是判断是否触发轮播,我们会设置一个阈值threshold,当滑动距离超过这个阈值时才会触发轮播,毕竟没有阈值的话用户稍微触碰轮播图就造成轮播,误操作会造成很差的用户体验.

1 /** 2 * 滑动结束处理的事件 3 * 4 * @private 5 * @memberof Carousel 6 */ 7 private onTouchEnd() { 8 this.autoPlay() 9 const { moveDeltaX, slideItemWidth, direction } = this.state 10 const threshold = slideItemWidth * THRESHOLD_PERCENTAGE 11 // 判断是否轮播 12 const moveToNext = Math.abs(moveDeltaX) > threshold 13 14 if (moveToNext) { 15 // 如果轮播触发那么进行轮播操作 16 this.handleSwipe(direction!) 17 } else { 18 // 轮播不触发,那么轮播图回到原位 19 this.handleMisoperation() 20 } 21 } 3. 轮播图的动画效果

我们常见的轮播图肯定不是生硬的切换,一般在轮播中会有一个渐变或者缓动的动画,这就需要我们加入动画效果.

我们制作动画通常有两个选择,一个是用 css3自带的动画效果,另一个是用浏览器提供的requestAnimationFrame API

孰优孰劣?css3简单易用上手快,兼容性好,requestAnimationFrame 灵活性更高,能实现 css3实现不了的动画,比如众多缓动动画 css3都束手无策,因此我们毫无疑问地选择了requestAnimationFrame.

双方对比请看张鑫旭大神的CSS3动画那么强,requestAnimationFrame还有毛线用?

想用requestAnimationFrame实现缓动效果就需要特定的缓动函数,下面就是典型的缓动函数

type tweenFunction = (t: number, b: number, _c: number, d: number) => number const easeInOutQuad: tweenFunction = (t, b, _c, d) => { const c = _c - b; if ((t /= d / 2) < 1) { return c / 2 * t * t + b; } else { return -c / 2 * ((--t) * (t - 2) - 1) + b; } }

缓动函数接收四个参数,分别是:

t: 时间 b:初始位置 _c:结束的位置 d:速度

通过这个函数我们能算出每一帧轮播图所在的位置, 如下:

在获取每一帧对应的位置后,我们需要用requestAnimationFrame不断递归调用依次移动位置,我们不断调用animation函数是其触发函数体内的this.setState({ translateX: tweenQueue[0], })来达到移动轮播图位置的目的,此时将这数组内的30个位置依次快速执行就是一个缓动动画效果.

1 /** 2 * 递归调用,根据轨迹运动 3 * 4 * @private 5 * @param {number[]} tweenQueue 6 * @param {number} newIndex 7 * @memberof Carousel 8 */ 9 private animation(tweenQueue: number[], newIndex: number) { 10 if (tweenQueue.length < 1) { 11 this.handleOperationEnd(newIndex) 12 return 13 } 14 this.setState({ 15 translateX: tweenQueue[0], 16 }) 17 tweenQueue.shift() 18 this.rafId = requestAnimationFrame(() => this.animation(tweenQueue, newIndex)) 19 }

但是我们发现了一个问题,当我们移动轮播图到最后的时候,动画出现了问题,当我们向左滑动最后一个轮播图div4时,这种情况下应该是图片向左滑动,然后第一张轮播图div1进入可视区域,但是反常的是图片快速向右滑动div1出现在可是区域…

因为我们此时将位置4设置为了位置1,这样才能达到不断循环的目的,但是也造成了这个副作用,图片行为与用户行为产生了相悖的情况(用户向左划动,图片向右走).

目前业界的普遍做法是将图片首尾相连,例如图片1前面连接一个图片4,图片4后跟着一个图片1,这就是为什么之前计算长度时要+2

slideListWidth = (len + 2) * width(len 为传入子组件的数量,width 为可视区域宽度)

当我们移动图片4时就不会出现上述向左滑图片却向右滑的情况,因为真实情况是:

图片4 -- 滑动为 -> 伪图片1 也就是位置 5 变成了位置 6

当动画结束之后,我们迅速把伪图片1的位置设置为真图片1,这其实是个障眼法,也就是说动画执行过程中实际上是图片4到伪图片1的过程,当结束后我们偷偷把伪图片1换成真图片1,因为两个图一模一样,所以这个转换的过程用户根本看不出来…

如此一来我们就可以实现无缝切换的轮播图了

4. 改进方向

我们实现了轮播图的基本功能,但是其通用性依然存在缺陷:

提示点的自定义: 我的实现是一个小点,而 antd 是用的条,这个地方完全可以将 dom 结构的决定权交给开发者. 方向的自定义: 本轮播图只有水平方向的实现,其实也可以有纵向轮播 多张轮播:除了单张轮播也可以多张轮播

以上都是可以对轮播图进行拓展的方向,相关的还有性能优化方面

我们的具体代码中有一个相关实现,我们的轮播图其实是有自动轮播功能的,但是很多时候页面并不在用户的可视页面中,我们可以根据是否页面被隐藏来取消定时器终止自动播放.

选自:https://zhuanlan.zhihu.com/p/72091681



【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3