使用React制做一个滑动轮播展现组件

江湖规矩~无图无真相,先上图!

这是一个展览列表的滑动轮播组件,点击左右按钮就能够进行左右滑动,每次能够滑动指定的数量,以及能够指定展现多少行的内容。

本人前端小菜鸟,大佬们轻喷呜呜呜javascript

下面来分享一下代码

由于本人采用的是React + Typescript来作项目的,你们若是有看不懂也不要紧的!换成普通的html跟js也差很少的。css

使用

<Slider cityList={cityList} row={2} step={2} />
复制代码

组件代码

import React, { Component, ComponentType } from 'react'
import { Icon } from 'antd'

import './index.scss'

interface IProps {
  cityList: Array<number>,
  row: number,
  step: number
}

interface IStates {
  cityContainerWidth: number,
  cityWrapWidth: number,
  cityWrapTranslateX: number
}

class Slider extends Component<IProps, IStates> {
  constructor(props: IProps) {
    super(props)
    this.state = {
      cityContainerWidth: 0,
      cityWrapWidth: 0,
      cityWrapTranslateX: 0
    }
  }

  componentDidMount(): void {
    const { cityList, row } = this.props
    const cityWrapWidth: number = cityList.length > 12 ? Math.ceil(cityList.length / row) * 220 : 1320
    const cityWrapDom: HTMLElement | null = document.getElementById('city__wrap') as HTMLElement
    const cityContainerDom: HTMLElement | null = document.getElementById('city__container') as HTMLElement
    const cityContainerWidth: number = cityContainerDom.offsetWidth
    cityWrapDom && (cityWrapDom.style.width = `${cityWrapWidth}px`)

    this.setState({
      cityContainerWidth,
      cityWrapWidth
    })
  }

  handleArrowClick(direction: string): void {
    const { step } = this.props
    const { cityContainerWidth, cityWrapWidth, cityWrapTranslateX } = this.state
    const cityWrapDom: HTMLElement | null = document.getElementById('city__wrap') as HTMLElement
    /* 步长 */
    const translateStep: number = 220 * step
    const translateDistance: number = translateStep * (direction === 'left' ? 1 : -1)

    let newTranslateX: number = cityWrapTranslateX
    /* 相对移动距离 */
    const relativeTranslateX: number = cityContainerWidth - cityWrapTranslateX
    const isLeftEnd: boolean = relativeTranslateX <= cityContainerWidth
    const isLeftOverflow: boolean = relativeTranslateX - translateDistance <= cityContainerWidth
    const isRightEnd: boolean = relativeTranslateX + 10 >= cityWrapWidth // 这个10是表明右边距的10个像素,加上10隐藏
    const isRightOverflow: boolean = relativeTranslateX - translateDistance >= cityWrapWidth

    /* 点击左箭头 */
    if (translateDistance > 0) {
      /* 是否到达左边尽头 */
      if (isLeftEnd) return

      if (isLeftOverflow) {
        /* 超出范围,则滑动恰好到达左边末尾的距离 */
        newTranslateX = 0
      } else {
        /* 未超出范围,滑动距离直接与步长相加 */
        newTranslateX += translateDistance
      }

    } else if (translateDistance < 0) {
      /* 是否到达右边尽头 */
      if (isRightEnd) return

      if (isRightOverflow) {
        /* 超出范围,则滑动恰好到达右边末尾的距离 */
        newTranslateX += relativeTranslateX + 10 - cityWrapWidth
      } else {
        /* 未超出范围,滑动距离直接与步长相加 */
        newTranslateX += translateDistance
      }
    }

    const transformString: string = `translateX(${newTranslateX}px)`
    cityWrapDom && (cityWrapDom.style.transform = transformString)
    this.setState({
      cityWrapTranslateX: newTranslateX
    })
  }

  render() {
    const { cityList } = this.props
    return (
      <div className="city" >
        <div className="city__title">我是一个轮播图</div>
        <div className="city__container" id="city__container">
          <div className="city__arrow" onClick={() => this.handleArrowClick('left')}>
            <Icon className="icon" type="left" />
          </div>
          <div className="city__arrow city__arrow--right" onClick={() => this.handleArrowClick('right')}>
            <Icon className="icon" type="right" />
          </div>
          <div className="city__wrap" id="city__wrap">
            {cityList.map(item => (
              <div className="city__item" key={item}>{item}</div>
            ))}
          </div>
        </div>
      </div>
    )
  }
}

export default Slider as ComponentType<IProps>
复制代码

样式文件(采用了sass来进行样式编写)

.city {
  &__container {
    position: relative;
    overflow: hidden;
    width: 1200px;
    padding-top: 10px;

    &::-webkit-scrollbar {
      width: 15px;
      height: 15px;
    }

    &::-webkit-scrollbar-track {
      border-radius: 20px;
      background: #e7e7e7;
    }

    &::-webkit-scrollbar-thumb {
      background: #66a6ff;
      background-image: linear-gradient(120deg, #89a4fe 0%, #66a6ff 100%);
      border-radius: 20px;
    }
  }

  &__title {
    margin-top: 30px;
    color: #333;
    font-size: 24px;
    font-weight: bold;
  }

  &__arrow {
    position: absolute;
    display: flex;
    justify-content: center;
    align-items: center;
    top: 50%;
    width: 50px;
    height: 100px;
    background: rgba(0, 0, 0, 0.7);
    transform: translateY(-50%);
    transition: all .3s ease;
    z-index: 2;
    opacity: .5;
    cursor: pointer;

    &--right {
      right: 0;
    }

    .icon {
      color: #fff;
      font-size: 30px;
    }

    &:hover{
      opacity: 1;
    }
  }

  &__wrap {
    transition: all .3s ease-in-out;
  }

  &__item {
    float: left;
    width: 210px;
    height: 90px;
    margin: 0 10px 10px 0;
    color: #fff;
    font-size: 40px;
    font-weight: bold;
    line-height: 90px;
    text-align: center;
    // background: url(https://static.zhipin.com/zhipin-geek/v98/web/geek/images/city_101010100.png) no-repeat;
    // background-size: cover;
    background-image: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
  }
}
复制代码

最后是这里出现的itemList

这是一个将要用于展现的数据列表。这里咱们能够用函数生成一个展现用的列表,你们若是有须要的话能够将其换成访问接口获取的列表...html

getCityList(): Promise<Array<number>> {
    return new Promise(async (resolve, reject) => {
      const length: number = 15
      const cityList: Array<number> = Array.from({ length }, (_: unknown, index: number): number => index + 1)
      resolve(cityList)
    })
  }
复制代码

咱们在这里生成了一个长度为15的天然数数组[1,2,3,4,5,6,7,8,9,10,11,12,13,14,15]前端

代码分析

虽然代码中都有注释,可是仍是简要的分享一下吧~java

步骤固然是: 获取须要展现的数据列表 -> 给滑动轮播组件传入列表 -> 接受后根据列表的长度、须要展现的行数来对容器设置动态宽度react

在接下来就是重头戏啦!!!web

关于处理容器如何滑动的地方我但是下了很多功夫,咱们一点一点来看吧数组

  1. 监听点击箭头按钮的函数
    handleArrowClick(direction),这个函数的意思是,点击左箭头的时候,direction就是left,反之这是right
<div className="city__arrow" onClick={() => this.handleArrowClick('left')}>
    <Icon className="icon" type="left" />
  </div>
  <div className="city__arrow city__arrow--right" onClick={() => this.handleArrowClick('right')}>
    <Icon className="icon" type="right" />
  </div>
复制代码

分别是左右箭头,点击左箭头就给函数传入一个字符串表明方向sass

  1. 定义步长
/* 步长 */
const translateStep: number = 220 * step
const translateDistance: number = translateStep * (direction === 'left' ? 1 : -1)
复制代码

这个很简单,就是根据父组件传入的step步长(每次点击滑动多少个元素),以及点击按钮时传入的方向。若是是左,则移动的距离是-步长,在这里咱们用的是transform: translateX的属性来进行滑动,正轴是向右,因此若是要想展现的内容向右移动,则须要将移动的距离设置为负数,这样相对移动起来,才能达到完美想要的效果,如今不懂不要紧,先继续日后看一看,bash

  1. 得到移动距离,以及判断是否超出等状况
    划重点!!!划重点!!!划重点!!!

首先咱们第一步先设置state,里面定义一个变量,来表示已经滑动的距离,初始为0

/* 已经滑动的距离 */
this.state = {
  cityWrapTranslateX: 0
}
复制代码

而后咱们获取一下滑动组件的外容器跟内容器的宽度,若是不懂的话能够百度一下轮播图的实现原理,简单说明一下的话就是说

外容器就是可见的部分,而内容器就是一个部分会隐藏的,而后根据外容器的大小,就能够吧内容器里面的内容展现出来,就是图中重叠的部分啦,想象一下~内容器在左右移动的时候,咱们见到的外容器中的东西是否是也在跟着移动了嘞~

好,不懂的能够去百度找一下更为详细的轮播图原理!俺先继续往下分析

/* 相对移动距离 */
const relativeTranslateX: number = cityContainerWidth - cityWrapTranslateX
复制代码

而后咱们经过获取到来的外容器的大小cityContainerWidth以及已经滑动的距离cityWrapTranslateX经过二者相减就能够获得相对移动距离啦,这是什么意思呢,咱们先继续下去讲一下各类边界条件你们就懂啦!

const isLeftEnd: boolean = relativeTranslateX <= cityContainerWidth
const isLeftOverflow: boolean = relativeTranslateX - translateDistance <= cityContainerWidth
const isRightEnd: boolean = relativeTranslateX + 10 >= cityWrapWidth // 这个10是表明右边距的10个像素,加上10隐藏
const isRightOverflow: boolean = relativeTranslateX - translateDistance >= cityWrapWidth
复制代码

这四个变量分别是:是否已经到达左边尽头(不能再点击了!)、点击按钮左边是否会溢出(就是滑动过头了!)、是否到达右边尽头、点击按钮右边是否会溢出

咱们就主要分析后两个就好,后两个就大同小异啦(由于喜欢右边)
咱们先假设外容器的宽度为1200,内容器的宽度为2000

  • isRightEnd
/* 相对移动距离 */
const relativeTranslateX: number = cityContainerWidth - cityWrapTranslateX
const isRightEnd: boolean = relativeTranslateX + 10 >= cityWrapWidth // 这个10是表明右边距的10个像素,加上10隐藏
复制代码

咱们先忽略这个10,由于每一个元素有外边距10px,因此咱们加上,咱们大可先忽略不计

为何是这样呢,假设如今咱们的移动距离为0,那么相对距离就是外容器的宽度了就是1200,那么内容器为2000,小于内容器

接下来假设咱们向右移动800px,那么cityWrapTranslateX就是-800(总之记住内容向左就是负数!)这时候咱们相对移动距离就是1200-(-800)就是2000,跟内容器同样了

看懂了吗!这样就至关于外容器向右移动了800px,由于前面也说了,外容器是控制咱们的可视部分,因此外容器向右,等于内容也向右,就对了!

接下来在看这个const isRightEnd: boolean = relativeTranslateX + 10 >= cityWrapWidth是否是就明白了,若是大于等于,就表明到尽头了,再次点击就直接return不执行就好

  • isRightOverflow
    这个意思就是点击的时候会不会超出容器的范围,这样是为了不产生滑动过分产生多余空白的问题,例子以下:

由于滑动过分而致使右边产生大量空白

因此咱们这里判断一下相对移动距离是否是超出内容器的宽度,也就是说超出展现内容的宽度

const relativeTranslateX: number = cityContainerWidth - cityWrapTranslateX
const isRightOverflow: boolean = relativeTranslateX - translateDistance >= cityWrapWidth
复制代码

咱们看一下图

就是如今滑动的地方,已经只剩下300px就到尽头了,可是咱们设置的每次滑动600px,那这时候,咱们相对移动距离就是1200 - (-500)就是1700,接着relativeTranslateX - translateDistance就是1700 - (-600)就是2300, 比2000大,就表明将要溢出,溢出了300px那么多,

咱们这时候就定义一个变量newTranslateX来表示新的滑动距离。默认是上一次滑动的距离

let newTranslateX: number = cityWrapTranslateX
复制代码

若是是将要溢出的话,咱们就设置为上一次的滑动距离加上相对滑动距离再减去内容器的宽度,这样说可能看不懂,那咱们就来算一算

newTranslateX = 500 (已经滑动了500)
relativeTranslateX = 1200
cityWrapWidth = 2000

newTranslateX = 500 + 1200 - 2000 = -300
这样是否是就表明向右移动300个像素了呢!,这样就刚恰好到达末尾啦!

newTranslateX += relativeTranslateX - cityWrapWidth
复制代码

最后咱们再根据各类状况设置最新滑动距离就能够了,设置完滑动的距离,就能够改变样式啦

/* 点击左箭头 */
if (translateDistance > 0) {
  /* 是否到达左边尽头 */
  if (isLeftEnd) return

  if (isLeftOverflow) {
    /* 超出范围,则滑动恰好到达左边末尾的距离 */
    newTranslateX = 0
  } else {
    /* 未超出范围,滑动距离直接与步长相加 */
    newTranslateX += translateDistance
  }

} else if (translateDistance < 0) {
  /* 是否到达右边尽头 */
  if (isRightEnd) return

  if (isRightOverflow) {
    /* 超出范围,则滑动恰好到达右边末尾的距离 */
    newTranslateX += relativeTranslateX + 10 - cityWrapWidth
  } else {
    /* 未超出范围,滑动距离直接与步长相加 */
    newTranslateX += translateDistance
  }
}

const transformString: string = `translateX(${newTranslateX}px)`
cityWrapDom && (cityWrapDom.style.transform = transformString)
this.setState({
  cityWrapTranslateX: newTranslateX
})
复制代码
  1. 咱们会根据传进来的行数来进行数据划分
const { cityList, row } = this.props
const cityWrapWidth: number = cityList.length > 12 ? Math.ceil(cityList.length / row) * 220 : 1320
const cityWrapDom: HTMLElement | null = document.getElementById('city__wrap') as HTMLElement
cityWrapDom && (cityWrapDom.style.width = `${cityWrapWidth}px`)
复制代码

这里就是简单的判断多少个元素,咱们默认显示12个,两行,一行6个这样子,不满12个就分开展现

设置为三行的时候:

感受全篇太多文字,很啰嗦,可是也算是一个小总结吧,小菜鸡写出来仍是有点兴奋的,你们能够拿出纸和笔在纸上画一画示意图,很快就能够得出关系,而后慢慢推倒出来啦!

悄咪咪放个以前写的小文章连接

相关文章
相关标签/搜索