使用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個就分開展現

設置爲三行的時候:

感受全篇太多文字,很囉嗦,可是也算是一個小總結吧,小菜雞寫出來仍是有點興奮的,你們能夠拿出紙和筆在紙上畫一畫示意圖,很快就能夠得出關係,而後慢慢推倒出來啦!

悄咪咪放個以前寫的小文章連接

相關文章
相關標籤/搜索