使用 React 實現一個輪播元件

阿城發表於2016-02-19

tip

React 剛出來不久,元件還比較少,不像 jquery 那樣已經有很多現成的外掛了,當是就自己寫了一個基於 React 的輪播元件,當然只是一個小 demo,剛剛有用 es6 的語法重新改了改,就想著寫一個小教程給新手,如何實現一個 React 的小元件。
先放上倉庫地址,可以先 clone 來看看程式碼:https://github.com/TongchengQiu/react-slider
react-slider 是一個圖片輪播的元件,支援的配置有 圖片(必須好不好,要不然輪播毛)、輪播圖片的速度、是否自動輪播、自動輪播的時候滑鼠放上去是否暫停、自動輪播速度、是否需要前後箭頭、是否需要 dot (我不知道怎麼表述好,反正意思你懂)。

第一步 需求

首先,寫一個元件必須先考慮改元件的需求有哪些,支援的配置需要哪些。
如上已經說了改元件的需求:

  • 輪播的圖片

  • 配置輪播圖片切換的速度

  • 可配置是否自動輪播

  • 可配置自動輪播的時候滑鼠放上去是否暫停

  • 可配置自動輪播的速度

  • 可配置是否需要前後箭頭

  • 可配置是否需要 dot (我不知道怎麼表述好,反正意思你懂)

這一步先到此為止~~~

第二步 構建專案

這裡我們是使用 React 框架,當然也是用它的好搭檔 webpack 來構建自動化流程咯~?
不懂 webpack 的配置可以看我的部落格關於 webpack 使用的文章,謝謝~?
這是專案開發目錄的檔案結構:

src
├── Slider              #Slider元件
|   |
|   ├──SliderItem          
|   |   ├──SliderItem.jsx
|   |   └──SliderItem.scss
|   |
|   ├──SliderDots        
|   |   ├──SliderItem.jsx
|   |   └──SliderItem.scss
|   |
|   ├──SliderArrows         
|   |   ├──SliderItem.jsx
|   |   └──SliderItem.scss
|   |
|   ├──Slider.jsx         
|   |
|   └──Slider.scss
|
├──images              #存放demo用的圖片檔案
|
└── index.js           #demo的入口檔案

看目錄結構我們應該明白了,我們主要關注 Slider 資料夾。

第三步 基於需求的開發

這裡我們開發元件的模式是按照需求驅動型,回到第一步的需求,我們先不管元件內程式碼,先寫出我們想怎麼樣配置使用元件:

// index.js
import React from `react`;
import { render } from `react-dom`;
import Slider from `./Slider/Slider`;

const IMAGE_DATA = [
  {
    src: require(`./images/demo1.jpg`),
    alt: `images-1`,
  },
  {
    src: require(`./images/demo2.jpg`),
    alt: `images-2`,
  },
  {
    src: require(`./images/demo3.jpg`),
    alt: `images-3`,
  },
];

render(
  <Slider
    items={IMAGE_DATA}
    speed={1.2}
    delay={2.1}
    pause={true}
    autoplay={true}
    dots={true}
    arrows={true}
  />,
  document.getElementById(`root`)
);

可以看到,在使用 Slider 元件的時候,根據需求,我們可以傳入這些屬性來配置元件。
一個 items 陣列,決定了需要輪播的內容,items 裡面每個元素都是一個物件,有 src 和 alt 屬性,分別是輪播圖片的 src 地址和 alt 內容;
speed 是圖片切換的時候的速度時間,需要配置一個 number 型別的資料,決定時間是幾秒;
autoplay 是配置是否需要自動輪播,是一個布林值;
delay 是在需要自動輪播的時候,每張圖片停留的時間,一個 number 值;
pause 是在需要自動輪播的時候,滑鼠停留在圖片上,是否暫停輪播,是一個布林值;
dots 是配置是否需要輪播下面的小點;
arrows 是配置是否需要輪播的前後箭頭;

第四步 編寫元件

首先我們來考慮一下需要多少個元件,最外層的 Slider 元件是毋庸置疑的了。
根據需求我們可以分出一個 SliderItem 元件,一個 SliderDots,一個 SliderArrows 元件,分別是 輪播每個圖片的item,輪播下面dots的元件,左右箭頭元件。
我們先來編寫每個小元件,元件是對外封閉獨立的,所以它只需要對外暴漏屬性的配置即可。

SliderItem

因為 SliderItem 是展示輪播圖片的每個內容的,所以它需要的屬性有,src 和 alt,因為之前我們已經把每個輪播項寫成一個物件了,所以把 src 和 alt 作為屬性放在 item,我們只需要一個 item 屬性即可;它還需要決定它在父元件中大小,因為在未使用元件的時候我們是不知道輪播項的數目的,所以它的寬度需要根據父元件傳給它count(圖片數目)來計算。
所以它需要的屬性有:

  • item (有 src 和 alt 屬性)

  • count (輪播項總數目,計算每個輪播項的寬度)

import React, { Component } from `react`;

export default class SliderItem extends Component {
  constructor(props) {
    super(props);
  }

  render() {
    let { count, item } = this.props;
    let width = 100 / count + `%`;
    return (
      <li className="slider-item" style={{width: width}}>
        <img src={item.src} alt={item.alt} />
      </li>
    );
  }
}

let width = 100 / count + `%`; 即是計算它佔父元件的寬度百分比。

SliderDots

對於 SliderDots 元件,我們需要一個 count(輪播項總數目)來決定顯示幾個 dot,還需要一個 nowLocal 屬性來判斷哪個 dot 對應當前顯示的輪播項,點選每個 dot 的是否需要一個回撥函式 turn 來做出響應。
所以它需要的屬性有:

  • count(輪播項總數目)

  • nowLocal(當前的輪播項)

  • turn(點選 dot 回撥函式)

import React, { Component } from `react`;

export default class SliderDots extends Component {
  constructor(props) {
    super(props);
  }

  handleDotClick(i) {
    var option = i - this.props.nowLocal;
    this.props.turn(option);
  }

  render() {
    let dotNodes = [];
    let { count, nowLocal } = this.props;
    for(let i = 0; i < count; i++) {
      dotNodes[i] = (
        <span
          key={`dot` + i}
          className={"slider-dot" + (i === this.props.nowLocal?" slider-dot-selected":"")}
          onClick={this.handleDotClick.bind(this, i)}>
        </span>
      );
    }
    return (
      <div className="slider-dots-wrap">
        {dotNodes}
      </div>
    );
  }
}

程式碼可以看出,我們用 for 迴圈根據 count 來決定了需要多少個 dot,然後在每個 dot 繫結函式傳入一個 i 值,並且如果這個 dot 對於當前顯示的輪播項,就多加一個class slider-dot-selected。
每個 dot click 繫結的函式還需要計算需要向前或者向後移動多少個輪播項,然後回撥 turn 函式。

SliderArrows

對於 SliderArrows 元件,我們只需要一個 turn 函式作出回撥。
廢話不多少,程式碼如下:

import React, { Component } from `react`;

export default class SliderArrows extends Component {
  constructor(props) {
    super(props);
  }

  handleArrowClick(option) {
    this.props.turn(option);
  }

  render() {
    return (
      <div className="slider-arrows-wrap">
        <span
          className="slider-arrow slider-arrow-left"
          onClick={this.handleArrowClick.bind(this, -1)}>
          &lt;
        </span>
        <span
          className="slider-arrow slider-arrow-right"
          onClick={this.handleArrowClick.bind(this, 1)}>
          &gt;
        </span>
      </div>
    );
  }
}

這個元件下面有兩個箭頭,分別是向前和向後,回撥的 turn 是 1 和 -1。

Slider 元件

import React, { Component } from `react`;

require(`./Slider.scss`);

import SliderItem from `./SliderItem/SliderItem`;
import SliderDots from `./SliderDots/SliderDots`;
import SliderArrows from `./SliderArrows/SliderArrows`;

export default class Slider extends Component {
  constructor(props) {
    super(props);
    this.state = {
      nowLocal: 0,
    };
  }

  // 向前向後多少
  turn(n) {
    var _n = this.state.nowLocal + n;
    if(_n < 0) {
      _n = _n + this.props.items.length;
    }
    if(_n >= this.props.items.length) {
      _n = _n - this.props.items.length;
    }
    this.setState({nowLocal: _n});
  }

  // 開始自動輪播
  goPlay() {
    if(this.props.autoplay) {
      this.autoPlayFlag = setInterval(() => {
        this.turn(1);
      }, this.props.delay * 1000);
    }
  }

  // 暫停自動輪播
  pausePlay() {
    clearInterval(this.autoPlayFlag);
  }

  componentDidMount() {
    this.goPlay();
  }

  render() {
    let count = this.props.items.length;

    let itemNodes = this.props.items.map((item, idx) => {
      return <SliderItem item={item} count={count} key={`item` + idx} />;
    });

    let arrowsNode = <SliderArrows turn={this.turn.bind(this)}/>;

    let dotsNode = <SliderDots turn={this.turn.bind(this)} count={count} nowLocal={this.state.nowLocal} />;

    return (
      <div
        className="slider"
        onMouseOver={this.props.pause?this.pausePlay.bind(this):null} onMouseOut={this.props.pause?this.goPlay.bind(this):null}>
          <ul style={{
              left: -100 * this.state.nowLocal + "%",
              transitionDuration: this.props.speed + "s",
              width: this.props.items.length * 100 + "%"
            }}>
              {itemNodes}
          </ul>
          {this.props.arrows?arrowsNode:null}
          {this.props.dots?dotsNode:null}
        </div>
      );
  }
}

Slider.defaultProps = {
  speed: 1,
  delay: 2,
  pause: true,
  autoplay: true,
  dots: true,
  arrows: true,
  items: [],
};
Slider.autoPlayFlag = null;
  • 在這裡我們先是 import 依賴,以及 需要的 子元件。

  • Slider 有一個狀態 nowLocal,是表明當前輪播的第幾項。

  • 前面一直講 turn 函式,我們就先分析一下這個函式:

    turn(n) {
      console.log();
      var _n = this.state.nowLocal + n;
      if(_n < 0) {
        _n = _n + this.props.items.length;
      }
      if(_n >= this.props.items.length) {
        _n = _n - this.props.items.length;
      }
      this.setState({nowLocal: _n});
    }

它傳入一個 引數 n ,決定向前或者向後移動多少個輪播項,向前和向後分別對於 - 和 +。
turn 函式內,宣告一個 _n 變數表示下一個輪播的第幾項。
如果 _n 小於 0 ,那當然是不行的,所以就會讓 _n 變成最後一項;
如果 _n 大於 items 的長度 ,那當然也是不行的,所以就會讓 _n 變成第一項;
最後改變狀態 nowLocal 等於 _n

  • 下面是一個開始自動輪播的函式 goPlay:

    goPlay() {
      if(this.props.autoplay) {
        this.autoPlayFlag = setInterval(() => {
          this.turn(1);
        }, this.props.delay * 1000);
      }
    }

如果 autoplay 是 true,則執行 setInterval 來自動呼叫 this.turn(1) 向前移動輪播項,this.props.delay * 1000就是根據配置的 delay 來決定多久移動一次。
這裡我們需要一個 autoPlayFlag 引數來儲存 setInterval 的回撥,用來在滑鼠停放在圖片上停止自動輪播,我們把這個 autoPlayFlag 作為元件的一個屬性來儲存。

Slider.autoPlayFlag = null;

然後在元件初始化 componentDidMount 的時候呼叫這個函式:

componentDidMount() {
  this.goPlay();
}
  • 我們還需要一個 pausePlay 函式來暫停自動輪播。

    pausePlay() {
      clearInterval(this.autoPlayFlag);
    }
  • 最後就是 render 了,根據屬性配置哪些函式和元件需要~

  • 預設屬性,這個是需要的,在使用屬性的時候如果忘了一些配置項也不會出錯。

    Slider.defaultProps = {
      speed: 1,
      delay: 2,
      pause: true,
      autoplay: true,
      dots: true,
      arrows: true,
      items: [],
    };

樣式

什麼,你告訴我顯示在頁面上的都是什麼鬼?
來寫 scss 吧:

* {
  margin: 0;
  padding: 0;
}
.slider {
  overflow: hidden;
  width: 100%;
  position: relative;

  &>ul {
    height: auto;
    overflow: hidden;
    position: relative;
    left: 0;
    transition: left 1s;
  }

  .slider-item {
    display: inline-block;
    height: auto;

    &>img {
      display: block;
      height: auto;
      width: 100%;
    }
  }

  .slider-arrow {
    display: inline-block;
    color: #fff;
    font-size: 50px;
    position: absolute;
    top: 50%;
    margin-top: -50px;
    z-index: 100;
    padding: 20px;
    cursor: pointer;
    font-weight: bold;

    &:hover {
      background: rgba(0,0,0,.2);
    }

    &.slider-arrow-right {
      right: 0;
    }
    &.slider-arrow-left {
      left: 0;
    }
  }

  .slider-dots-wrap {
    z-index: 99;
    text-align: center;
    width: 100%;
    position: absolute;
    bottom: 0;

    .slider-dot {
      display: inline-block;
      width: 6px;
      height: 6px;
      border: 3px solid #ccc;
      margin: 6px;
      cursor: pointer;
      border-radius: 20px;

      &:hover {
        border: 3px solid #868686;
      }

      &.slider-dot-selected {
        background: #ccc;
      }
    }
  }
}

這裡不多說了,樣式就這樣~
有疑問可以 call me。

謝謝謝謝,謝謝各位客官光看 ~ ? ? 啊哈 ?

? ? ? ? ?

文章原文:使用 React 實現一個輪播元件
我的部落格地址:qiutc.me
歡迎來吐槽!!!

相關文章