React完美實現可配置轉盤

最近在作一些h5活動的需求,發現用到轉盤的機會很大。react

代碼已經開源,感興趣的同窗可查看react-turnplategit

先上效果

額,由於gif壓縮了,因此畫質有點。。。呵呵,不要緊的

需求

因而將轉盤組件化,具體的需求是:github

  1. 傳入一個獎品數組.
  2. 可以在點擊轉的按鈕時候作一些判斷是否能夠開轉.
  3. 開轉後有一個回調,用於請求獎品返回.
  4. 轉動結束/中獎回調.
  5. 轉動按鈕,背景圖可配置. 具體props爲
params type desc
image_spin string spin button
background_1 string background_1
background_2 string background_2
prizeList array [{icon:'imageurl',name:'prize1',id:1},{icon:'imageurl',name:'prize1',id:2}]
award object award should be null first,after request back return an object like prizelist[0]
canStartRotate bool control the turnplate should startRotate
onTryRotate func trigger after click the rotate button,should do some check stuff and if it's ok,set canStartRotate to be true then the turnplate start rotating,meanwhile request for the award and after the request finish,set the award
rotateFinish func

實現思路

轉盤外圈的閃爍

這裏主要是兩張背景圖不斷替換,經過定時器,不斷替換background造成閃爍的效果.canvas

//外面閃閃發光的東東
  _outDiscFlash() {
    const { background_1, background_2 } = this.props;
    this.outShowImg1 = !this.outShowImg1;
    if (this.outShowImg1) {
      this.refs.turnplateBorder.style.backgroundImage = `url(${background_1})`;
    } else {
      this.refs.turnplateBorder.style.backgroundImage = `url(${background_2})`;
    }

    this._flashTimer = setTimeout(this._outDiscFlash, this.outDiskDiffTimer);
  }
  _initFlash() {
    const { background_1 } = this.props;
    this.outDiskDiffTimer = 100;
    this.outShowImg1 = true;
    this._flashTimer = null;
    this.refs.turnplateBorder.style.backgroundImage = `url(${background_1})`;
  }
複製代碼

轉盤的獎品繪製

1.首先是根據傳進來的獎品數組個數,用canvas來畫扇形填充。用devicePixelRatio是爲了適配手機。數組

draw() {
    const { prizeList } = this.props;

    let rotateDeg = 360 / prizeList.length / 2 + 90, // 扇形迴轉角度
      ctx;

    const canvas = document.getElementById("canvas");
    if (!canvas.getContext) {
      return;
    }
    // 獲取繪圖上下文
    ctx = canvas.getContext("2d");
    for (let i = 0; i < prizeList.length; i++) {
      // 保存當前狀態
      ctx.save();
      // 開始一條新路徑
      ctx.beginPath();
      // 位移到圓心,下面須要圍繞圓心旋轉
      ctx.translate(105 * this.devicePixelRatio, 105 * this.devicePixelRatio);
      // 從(0, 0)座標開始定義一條新的子路徑
      ctx.moveTo(0, 0);
      // 旋轉弧度,需將角度轉換爲弧度,使用 degrees * Math.PI/180 公式進行計算。
      ctx.rotate((((360 / prizeList.length) * i - rotateDeg) * Math.PI) / 180);
      // 繪製圓弧
      ctx.arc(
        0,
        0,
        105 * this.devicePixelRatio,
        0,
        (2 * Math.PI) / prizeList.length,
        false
      );

      // 顏色間隔
      if (i % 2 == 0) {
        ctx.fillStyle = "#FFEAB0";
      } else {
        ctx.fillStyle = "#ffffff";
      }

      // 填充扇形
      ctx.fill();
      // 繪製邊框
      ctx.lineWidth = 0.5;
      ctx.strokeStyle = "#e4370e";
      ctx.stroke();

      // 恢復前一個狀態
      ctx.restore();
    }
  }
複製代碼

2.其次是將產品填充,作一個rotate。bash

_getTurnPrizeList() {
    const { prizeList } = this.props;
    const turnplateList = [];
    for (let i = 0; i < prizeList.length; i++) {
      const turnplateItem = (
        <li className="turnplate-item" key={i}>
          <div style={{ transform: `rotate(${i / prizeList.length}turn)` }}>
            <div>{prizeList[i].name}</div>
            <img src={prizeList[i].icon} />
          </div>
        </li>
      );
      turnplateList.push(turnplateItem);
    }
    return <ul className="turnplate-list">{turnplateList}</ul>;
  }
複製代碼

轉盤的旋轉控制

首先,可以在點擊轉的按鈕時候作一些判斷是否能夠開轉,使用變量canStartRotate來控制。當canStartRotate爲true後,一直旋轉,直到傳進來的award不爲空,每次transitionEnd判斷award的狀態,不爲空就結束旋轉,回調rotateFinish。app

UNSAFE_componentWillReceiveProps(nextProps, nextState) {
    if (this.props.prizeList.length != nextProps.prizeList.length) {
      this.draw();
    }
    //若是在請求,還沒返回結果,就先轉着
    if (
      !this.props.canStartRotate &&
      nextProps.canStartRotate &&
      !nextProps.award
    ) {
      this._initFlash();
      this._outDiscFlash();
      this._justRotate();
    }
    if (!this.props.award && nextProps.award) {
      this.setState({ award: nextProps.award });
    }
  }
  _justRotate() {
    const container = document.getElementById("turnplate");
    const rotateDeg = 360 * 3;
    this.setState({
      lastRotateDeg: rotateDeg + this.state.lastRotateDeg,
      rotating: true,
      justRotate: true
    });
    container.style.transform =
      "rotate(" + (rotateDeg + this.state.lastRotateDeg) + "deg)";
  }
  
  finishRotate() {
    const { rotateFinish } = this.props;
    const { award, justRotate } = this.state;
    //若是獎品來了,而且不是justRotate
    if (award && !justRotate) {
      clearTimeout(this._flashTimer);
      this.setState({ rotating: false });
      rotateFinish(award);
    }
    //若是獎品來了,是justRotate,就開始抽
    else if (award && justRotate) {
      this._lottery();
    } else {
      //不然就繼續等吧兄弟
      this._justRotate();
    }
  }
  
   <div
        className="turnplate-wrapper"
        id="turnplate"
        onTransitionEnd={this.finishRotate.bind(this)}
    >
複製代碼

每次transition結束的時候都查看award是否已經傳入,可看到finishRotate有3種狀況的判斷組件化

待實現的功能

  1. 轉盤大小可配置
  2. 轉盤每一個扇形顏色可配置 ....

這些以後都會在react-turnplate完善。ui

此處只是提供了一種思路,拋磚引玉。this

相關文章
相關標籤/搜索