React封装公共组件:轮播图(2)
上一篇文章中,我们介绍了如何实现轮播图的无缝滚动
这一篇文章将会介绍如何实现自动播放,以及如何将自动播放和手指滑动这两个事件进行隔离
自动播放
假设自动播放的顺序为:图片无限向左滚动
那么当我们发现 this.current 指向第二组最后一张图片时,也应该让它瞬间移动到第一组的最后一张图片
直接看代码:
autoPlaySlider = () => {
clearInterval(this.intervalID)
this.intervalID = setInterval(() => {
if(this.current === this.dotLength*2 -1){
this.current = this.dotLength - 1
this.translatex = this.current * -this.imgWidth
this.list.current.style.transition = 'none'
this.list.current.style.transform = `translateX(${this.translatex}px)`
clearTimeout(this.timerID)
this.timerID = setTimeout(() => {
this.current++
this.translatex = this.current * -this.imgWidth
this.list.current.style.transition = '0.4s'
this.list.current.style.transform = `translateX(${this.translatex}px)`
this.formatDots()
}, 0);
}else{
this.current++
this.translatex = this.current * -this.imgWidth
this.list.current.style.transition = '0.4s'
this.list.current.style.transform = `translateX(${this.translatex}px)`
this.formatDots()
}
}, 1500);
}
当 this.current 指向第二组最后一张图片时,必须要做2次transform的动作
第一次:从“第二组最后一张” -> “第一组最后一张”
第二次:从“第一组最后一张” -> “第二组第一张”
为了将这两个动作完全隔离,我们用了一个计时器timerID,将 第二次的滚动动作 延迟执行
如果不这样做,第二次的动作将会覆盖第一次的动作,导致动画效果出错,变成“向右滚动”
兼容自动播放和手指触摸
我们的第一个目标是:当手指开始滑动图片时,停止自动播放
当 touchStart 被触发时,清空页面上所有与自动播放相关的计时器
然后给这个“进程”上一个锁,将this.touching设置为true
并在touchend 结束时,将 this.touching 改为 false
我们的第二个目标是:当手指停止滑动图片后,过一段时间,重新开始自动播放
当touchend结束时,开启一个名为 waitForAutoPlay 的计时器,3s之后,执行autoPlaySlider函数
并在autoPlaySlider函数开头,根据 this.touching的值 多加一层判断
autoPlaySlider = () => {
if(this.touching){
return
}else{
...
}
}
最后,别忘了在组件卸载之前,清空页面上的所有计时器
componentWillUnmount() {
clearInterval(this.intervalID)
clearTimeout(this.timerID)
clearTimeout(this.waitForAutoPlay)
}
完整代码
Slider.js的完整代码如下
import React, {Component, Fragment} from 'react'
import {
SliderWrapper,
SliderList,
SliderDot
} from './style'
class Slider extends Component {
constructor(props) {
super(props)
this.wrap = React.createRef()
this.list = React.createRef()
this.dot = React.createRef()
this.dotLength = 0
this.startPoint = {}
this.distance = {}
this.current = 0
this.translatex = 0
this.startOffset = 0
this.imgWidth = this.props.imgWidth
this.threshold = 0.2 //滑动切换阈值
this.touching = false
this.isMove = false
this.intervalID = null
this.timerID = null
}
render() {
return (
<Fragment>
<SliderWrapper
ref={this.wrap}
width={this.props.imgWidth}
onTouchStart={this.handleTouchStart}
onTouchMove={this.handleTouchMove}
onTouchEnd={this.handleTouchEnd}
>
<SliderList ref={this.list} >
{
this.props.imgList.map((item,index) => {
return (
<li key={index}><img src={item} width={this.props.imgWidth}/></li>
)
})
}
</SliderList>
<SliderDot ref={this.dot}>
{
this.props.imgList.map((item,index) => {
return (
<li key={index}
></li>
)
})
}
</SliderDot>
</SliderWrapper>
</Fragment>
)
}
componentDidMount() {
this.initSlider()
}
initSlider = () => {
this.dotLength = this.dot.current.childNodes.length
this.list.current.innerHTML+=this.list.current.innerHTML
this.dot.current.childNodes[0].classList.add('active')
this.autoPlaySlider()
}
autoPlaySlider = () => {
if(this.touching){
return
}else{
clearInterval(this.intervalID)
this.intervalID = setInterval(() => {
if(this.current === this.dotLength*2 -1){
this.current = this.dotLength - 1
this.translatex = this.current * -this.imgWidth
this.list.current.style.transition = 'none'
this.list.current.style.transform = `translateX(${this.translatex}px)`
clearTimeout(this.timerID)
this.timerID = setTimeout(() => {
this.current++
this.translatex = this.current * -this.imgWidth
this.list.current.style.transition = '0.4s'
this.list.current.style.transform = `translateX(${this.translatex}px)`
this.formatDots()
}, 0);
}else{
this.current++
this.translatex = this.current * -this.imgWidth
this.list.current.style.transition = '0.4s'
this.list.current.style.transform = `translateX(${this.translatex}px)`
this.formatDots()
}
}, 1500);
}
}
handleTouchStart = (ev) => {
clearTimeout(this.timerID)
clearInterval(this.intervalID)
clearTimeout(this.waitForAutoPlay)
this.touching = true
let touch = ev.changedTouches[0]
this.startPoint = {
x: touch.pageX,
y: touch.pageY
}
if(this.current === 0){
this.current = this.dotLength
}else if(this.current === this.dotLength*2 -1){
this.current = this.dotLength -1
}
this.translatex = this.current * -this.imgWidth
this.startOffset = this.translatex
this.list.current.style.transition = 'none'
this.list.current.style.transform = `translateX(${this.translatex}px)`
}
handleTouchMove = (ev) => {
let touch = ev.changedTouches[0]
this.distance = {
x: touch.pageX - this.startPoint.x,
y: touch.pageY - this.startPoint.y
}
this.translatex = this.startOffset + this.distance.x
this.list.current.style.transform = `translateX(${this.translatex}px)`
}
handleTouchEnd = (ev) => {
if(Math.abs(this.distance.x) > this.imgWidth * this.threshold){
if(this.distance.x>0){
this.current--
}else{
this.current++
}
}
this.translatex = this.current * -this.imgWidth
this.list.current.style.transition = '0.3s'
this.list.current.style.transform = `translateX(${this.translatex}px)`
this.formatDots()
this.touching = false
this.waitForAutoPlay = setTimeout(() => {
if(!this.touching){
this.autoPlaySlider()
}
}, 3000);
}
formatDots() {
Array.from(this.dot.current.childNodes).forEach((item,index) => {
item.classList.remove('active')
if(index === (this.current%this.dotLength)){
item.classList.add('active')
}
})
}
componentWillUnmount() {
clearInterval(this.intervalID)
clearTimeout(this.timerID)
clearTimeout(this.waitForAutoPlay)
}
}
export default Slider