使用Vue制作图片轮播组件思路详解
之前一直都没有认真的写过一个组件。以前在写业务代码的过程中,都是用的别人封装好的组件,这次尝试着写了一个图片轮播组件,虽然比不上知名的轮播组件,但它的功能基本完整,而且在写这个组件的过程中,学的东西也很多,在这里也给大家分享出来,如有疏漏,欢迎指正!
在制作这个组件之前,笔者google了不少关于轮播的文章,发现实现一个轮播的思路虽然各有不同,但是大的逻辑其实差不多,本文主要依据慕课网上焦点轮播图特效这节课,不过慕课网主要用原生JS写,而笔者则用Vue进行了重构,并且进行了一点修改。完成后的组件效果图如下:
一、理清思路,理解需求和原理
1. 要写一个什么样的轮播?
- 在点击右侧箭头时,图片向左滑动到下一张;点击左侧箭头时,图片向右滑到下一张
- 点击下面的小圆点,滑到对应的图片,相应小圆点的样式也发生改变
- 要有过渡效果,要缓缓滑动过去
- 当鼠标hover到图片上时,轮播暂停,当鼠标leave时,轮播继续
- 自动播放功能
- 无限滚动,即在滚动到最后一张时,再点击下一张时会继续向左滑动到第一张,而不是整个拉到第一张,这里有点难
2. 理解无限轮播的原理
我们先看下原理图:
图中红线区域即是我们看到的图片,这个轮播 只展示5张图片 ,但是在它的首尾各还有两张图片,在图1前面放置了图5,在图5后面放置了图1,之所以这么做,是为了做无限滚动。 无限滚动的原理在于:当整个图向左侧滚动到右边的图5时,会继续向前走到图1,在完全显示出图1后,会以肉眼看不到的速度向右侧拉回到最左边的图1。 这样,即使再向左侧滑动看到的就是图2了。
如下图:在最后的图1完成过渡完全显示出来后,再将整个列表瞬间向右拉到左侧的图1。另一张边界图图5的滚动也是,不过方向相反。
二、先让图片切换起来
1. 布局和准备
<template> <div id="slider"> <div class="window"> // window上图中红线框 <ul class="container" :style="containerStyle"> //注意这里的:style //这是图片列表,排成一排 <li> //列表最前面的辅助图,它和图5一样,用于无限滚动 <img :src="/UploadFiles/2021-04-02/sliders[sliders.length - 1].img">好了,布局大概就是这样,效果图如下:
上面的代码已经做了注释,有几个点在这里再提一下:
- window是红线框, 宽度为600px ,它不会动,移动的是包裹着图片的container,它的移动方式用 :style="containerStyle" ,这是一个计算属性,用 transform:translate3d(${this.distance, 0, 0}) 来控制左右移动
- data里的 distance 和 currentIndex 是关键, distance 控制着移动的距离,默认是-600,显示7张图片中的第二张,也就是图1。 currentIndex 是window显示的图片的索引,这里默认是1,也是7张图片中第2张。
- 需要展示的只有5张图片,但是在图1前了一张图5、在图5后面放了一张图1来做无限滚动,原理前面说过了
- 当点击右侧的箭头,container向左移动, distance 会越来越小;当点击左侧的箭头,container向右移动, distance 会越来越大,方向不要弄错
2. 图片切换
我们在左侧和右侧的箭头上添加点击事件:
<ul class="direction"> <li class="left" @click="move(600, 1)"> <svg class="icon" width="30px" height="30.00px" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg"><path fill="#ffffff" d="M481.233 904c8.189 0 16.379-3.124 22.628-9.372 12.496-12.497 12.496-32.759 0-45.256L166.488 512l337.373-337.373c12.496-12.497 12.496-32.758 0-45.255-12.498-12.497-32.758-12.497-45.256 0l-360 360c-12.496 12.497-12.496 32.758 0 45.255l360 360c6.249 6.249 14.439 9.373 22.628 9.373z" /></svg> </li> <li class="right" @click="move(600, -1)"> <svg class="icon" width="30px" height="30.00px" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg"><path fill="#ffffff" d="M557.179 904c-8.189 0-16.379-3.124-22.628-9.372-12.496-12.497-12.496-32.759 0-45.256L871.924 512 534.551 174.627c-12.496-12.497-12.496-32.758 0-45.255 12.498-12.497 32.758-12.497 45.256 0l360 360c12.496 12.497 12.496 32.758 0 45.255l-360 360c-6.249 6.249-14.439 9.373-22.628 9.373z" /></svg> </li> </ul> ...... methods:{ move(offset, direction) { this.distance += this.distance * direction if (this.distance < -3000) this.distance = -600 if (this.distance > -600) this.distance = -3000 } }
解释下上面的代码:点击左侧或者右侧的箭头,调用move函数,move接收偏移量offset和方向direction两个参数。direction只传两个值,1表示container向右移动,-1表示container向左移动;偏移量是600,也就是一张图片的宽度。如果移动到7张图片的最后一张,就把container拉到7张图片里的第二张;如果移动到7张图片里第一张,就把container拉到7张图片里的第5张。
效果:
可以看到,图片切换效果已经出来了,但是下面的小圆点没有跟着变换。接下来我们把这个效果加上。从上面的html代码可以看到, :class="{dotted: i === (currentIndex - 1)}" ,小圆点的切换效果和data里的currentIndex值相关,我们只要随着图片切换变动currentIndex值就可以了。
修改move方法里的代码:
......
move(offset, direction) { direction === -1 "text-align: center">可以看到,小圆点的切换效果已经出来了。
三、过渡动画
上面的代码已经实现了切换,但是没有动画效果,显的非常生硬,接下来就是给每个图片的切换过程添加过渡效果。
这个轮播组件笔者并没有使用Vue自带的class钩子,也没有直接使用css的transition属性,而是用慕课网原作者讲的setTimeout方法加递归来实现。
其实我也试过使用Vue的钩子,但是总有一些小问题解决不掉;比如下面找到的这个例子:例子
这个例子在过渡的边界上有一些问题,我也遇到了,而且还是时有时无。而如果使用css的transition过渡方法,在处理边界的无限滚动上总会在chrome浏览器上有一下闪动,即使添加了 -webkit-transform-style:preserve-3d; 和 -webkit-backface-visibility:hidden 也还是没用,而且要配合transition的 transitionend 事件对于IE浏览器的支持也不怎么好。
如果大家有看到更好的办法,请在评论中留言哦~
下面我们来写这个过渡效果,主要是改写:
methods:{ move(offset, direction) { direction === -1 "htmlcode">methods:{ move(offset, direction) { direction === -1 "text-align: center">
四、简单节流一下
写到这里,效果是出来了,但是会有一点问题,如果多次快速点击,就会有可能出现下面这种情况:
出现这种情况的原因很简单,因为是使用定时器过渡,所以连续快速点击就会出现错乱,简单节流一下就好了: 在过渡完成之前点击箭头无效,其实就是设了一个闸,第一次点击把闸打开,在闸再次打开之前,让一部分代码无法执行,然后再在恰当的时机把闸打开。
我们把这个闸设在move函数里:
move(offset, direction) { if (!this.transitionEnd) return //这里是闸 this.transitionEnd = false //开闸以后再把闸关上 direction === -1 "htmlcode">animate(des, direc) { if (this.temp) { window.clearInterval(this.temp) this.temp = null } this.temp = window.setInterval(() => { if ((direc === -1 && des < this.distance) || (direc === 1 && des > this.distance)) { this.distance += 30 * direc } else { this.transitionEnd = true //闸再次打开 window.clearInterval(this.temp) this.distance = des if (des < -3000) this.distance = -600 if (des > -600) this.distance = -3000 } }, 20) }这下快速点击就没有之前的那个问题了:
五、点击小圆点实现图片过渡切换
到目前为止的代码:
<template> <div id="slider"> <div class="window"> <ul class="container" :style="containerStyle"> <li> <img :src="/UploadFiles/2021-04-02/sliders[sliders.length - 1].img"><ul class="dots"> <li v-for="(dot, i) in sliders" :key="i" :class="{dotted: i === (currentIndex-1)}" @click = jump(i+1)> </li> </ul>在点击小圆点的时候我们调用 jump 函数,并将索引 i+1 传给它。 这里需要特别注意,小圆点的索引和图片对应的索引不一致,图片共7张,而5个小圆点对应的是图片中中间的5张,所以我们才传 i+1 。
jump(index) { const direction = index - this.currentIndex >= 0 "htmlcode">direction === -1 "text-align: center">所以我们需要控制一下速度,让滑动一张图片耗费的时间和滑动多张图片耗费的时间一样,给move和animate函数添加一个speed参数,还要再算一下:
上一篇:JS中的回调函数实例浅析jump(index) { const direction = index - this.currentIndex >= 0 "color: #ff0000">六、自动播放与暂停前面的写的差不多了,到这里就非常简单了,写一个函数play:
play() { if (this.timer) { window.clearInterval(this.timer) this.timer = null } this.timer = window.setInterval(() => { this.move(600, -1, this.speed) }, 4000) }除了初始化以后自动播放,还要通过mouseover和mouseleave来控制暂停与播放:
stop() { window.clearInterval(this.timer) this.timer = null }七、 两处小坑
1. window.onblur 和 window.onfocus
写到这里,基本功能都差不多了。但是如果把页面切换到别的页面,导致轮播图所在页面失焦,过一段时间再切回来会发现轮播狂转。原因是页面失焦以后,setInterval停止运行,但是如果切回来就会一次性把该走的一次性走完。解决的方法也很简单,当页面失焦时停止轮播,页面聚焦时开始轮播。
window.onblur = function() { this.stop() }.bind(this) window.onfocus = function() { this.play() }.bind(this)2. window.setInterval() 小坑
当定时器 window.setInterval() 在多个异步回调中使用时,就有可能在某种机率下开启多个执行队列, 所以为了保险起见,不仅应该在该清除时清除定时器,还要在每次使用之前也清除一遍 。
八、用props简单写两个对外接口
props: { initialSpeed: { type: Number, default: 30 }, initialInterval: { type: Number, default: 4 } }, data() { ...... speed: this.initialSpeed }, computed:{ interval() { return this.initialInterval * 1000 } }然后再在相应的地方修改下就可以了。
完整的代码如下:
<template> <div id="slider"> <div class="window" @mouseover="stop" @mouseleave="play"> <ul class="container" :style="containerStyle"> <li> <img :src="/UploadFiles/2021-04-02/sliders[sliders.length - 1].img">github地址九、结语
大概写完了这个组件,发现其实还有许多地方可以优化, this.distance 和 this.currentIndex 耦合性很高,完全可以通过计算属性连到一起。还有过渡方式,用定时器的方法还是有些生硬,没有发挥出Vue的优势来。不过,第一个组件算是写完了,也费了一番力气。
下一篇:JS实现为动态添加的元素增加事件功能示例【基于事件委托】