js 实现9宫格抽奖(react)

4,208 阅读3分钟

最近工作中有个9宫格抽奖的需求,一开始没啥思绪,网上查阅一番,自己又修改了一下,希望与大家交流分享。

先看一下效果图

整理一下思路 利用flex布局形成9宫格,每个格子的序号为0~8,创建一个记录当前指针的变量,点击抽奖,指正转动,当index等于变量的时候让当前格子与其他格子样式不一致,当请求后台有结果时,让指针等于结果值。

上图中黑色数组代表奖品元素排列的顺序,其中4为抽奖按钮,红色数字则为指针转动的路线。

上代码:

首先定义几个变量

constructor (props) {
    super(props)

    this.state = {
      timer1: '',         // 定时器1 快速旋转
      timer2: '',         // 定时器2 慢速旋转
      prizeIndex: 0,      // 转动指针
      stopIndex: null,    // 抽中奖品
      arrNum: [0, 1, 2, 5, 8, 7, 6, 3],   // 转动顺序
      luckList: [],       // 奖品集合
      isRolling: false   // 是否正在抽奖
    }

    // 这边绑定是必要的,这样 `this` 才能在回调函数中使用
    this.drawPrize = this.drawPrize.bind(this);
    this.move = this.move.bind(this);
    this.lowSpeed = this.lowSpeed.bind(this);
  }

然后根据数据写DOM渲染的条件

因为后台返回数据只有8条奖品,所以再获取奖品集合时对数据做一些改变,这个根据实际情况而定

res.data.dtls.splice(4, 0, {prizeName: "抽奖按钮", type: 0});

这里利用type属性对奖品和抽奖按钮做区分

renderLi = () => {
    let {luckList} = this.state;
    return luckList.map((list, index) => {
        if (list.type !== 0) {
            return <div key={index} className={list.chose === 1 ? 'chose box_prize' : 'box_prize'}>
                    {list.type===1||list.type===2?
                        <img alt="" src={require('./css/redpocket.png')}/> :
                      list.type===3?
                      <img alt="" src={require('./css/integral.png')}/> : ""
                    }
                    <div className="name">{list.prizeName}</div>
                  </div>
        } else {
            return <div key={index} className='box_prize click' onClick={this.drawPrize}></div>
        }

    })
  }
<div className="luck_turntable">
  {this.renderLi()}
</div>

点击抽奖按钮运行函数drawPrize

// 点击抽奖 执行快速转动,2秒后执行慢速转动
  async drawPrize() {
    if (!this.state.isRolling) {
        this.setState({
            prizeIndex: 0,        
            stopIndex: null,
            isRolling: true,
            timer1: setInterval(this.move, 100)
        });
        setTimeout(() => {   //转一圈半之后降速
            clearInterval(this.state.timer1); 
            this.lowSpeed()   
        }, 2000)
    }
}

慢速转动

lowSpeed() {//慢速转动
    // 先清除快速转动的定时器
    clearInterval(this.state.timer1);
    
    // 请求接口,获取转动结果
    api.lotteryId(this.state.lotteryId, token).then(res => {
      if(res.resultCode === '0') {
        let stopIndex = null
        // 对转动结果做处理
        switch (res.data) {
          case 4:
            stopIndex = 3
            break;
          case 7:
            stopIndex = 4
            break;
          case 6:
            stopIndex = 5
            break;
          case 5:
            stopIndex = 6
            break;
          case 3:
            stopIndex = 7
            break;
          case 0:
            stopIndex = 0
            break;
          case 1:
            stopIndex = 1
            break;
          case 2:
            stopIndex = 2
            break;
          default:
            stopIndex = null
            break
          }
        // 得到结果再起调用move函数,速度降低
        this.setState({
          stopIndex: stopIndex,
          timer2: setInterval(this.move, 300)
        });
      }
  }

执行转动函数move

move() {//转动
    let luckList = this.state.luckList
    let arrNum = this.state.arrNum
    // chose=1为转动到位置,0为正常位置
    luckList[arrNum[this.state.prizeIndex]].chose = 1
    luckList[arrNum[this.state.prizeIndex-1<0?7:this.state.prizeIndex-1]].chose = 0
    
    // 如果指针位置prizeIndex与结果位置相同,则抽奖完成,清除所有定时器
    if (this.state.stopIndex !== null && (this.state.prizeIndex === this.state.stopIndex)) {
      clearInterval(this.state.timer1);
      clearInterval(this.state.timer2);
      setTimeout(() => {
        this.setState({
            isRolling: false    // 可以继续抽奖
        })
      }, 300);
      
    } 
    // 否则,继续转动,这里需要对零界位置做一下处理
    else {
      this.setState({
        prizeIndex: this.state.prizeIndex + 1 === 8 ? 0 : this.state.prizeIndex + 1
      })
    }
  }

另外需要在第二次点击时恢复状态值,我是在其他条件下做的处理,这个根据实际情况而定。

好了,一个简单的9宫格抽奖就完成了,有更好的,更丰富的方案的请不吝赐教。

更新 function component 实现

最近在搭建组件库的时候,想到这也是个不错的功能性组件,于是想起用hook语法再实现一遍

type Res = 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7;

interface IProps {
  begin: () => Promise<Res>;
}

const SuperPrizeGrid = (props: IProps) => {
  const timer1 = useRef();
  const timer2 = useRef();
  const [args, setArgs] = useState<{
    prizeIdx: Res;
    stopIdx: Res | null;
    isRolling: boolean;
  }>({
    prizeIdx: 0,
    stopIdx: null,
    isRolling: false,
  });

  const [, forceUpdate] = useReducer(n => ++n, 0);
  const move = useCallback(() => {
    console.log(args);
    // 当prizeIdx转到stopIdx的位置时停止
    if (args.stopIdx !== null && args.prizeIdx === args.stopIdx) {
      timer1.current && clearInterval(timer1.current);
      timer2.current && clearInterval(timer2.current);
      timer1.current = undefined;
      timer2.current = undefined;

      setTimeout(() => {
        message.info(
          `中奖了:${
            data.filter(item => item.seat === args.stopIdx)[0].content
          }`,
          () => {
            setArgs(
              Object.assign(args, {
                prizeIdx: 0,
                stopIdx: null,
                isRolling: false,
              }),
            );
            forceUpdate();
          },
        );
      }, 300);
      return;
    }
    setArgs(
      Object.assign(args, { prizeIdx: ((args.prizeIdx + 1) % 8) as Res }),
    );
    forceUpdate();
  }, [args.stopIdx, args.prizeIdx]);

  const lowSpeed = async () => {
    timer1.current && clearInterval(timer1.current);
    timer1.current = undefined;
    //@ts-ignore
    timer2.current = setInterval(move, 180);
    // TODO
    let res = await props.begin();
    setArgs(Object.assign(args, { stopIdx: res }));
  };

  const clickPrize = () => {
    if (!args.isRolling) {
      setArgs(Object.assign(args, { isRolling: true }));
      // @ts-ignore
      timer1.current = setInterval(move, 80);
      // 转2秒再请求数据
      setTimeout(() => {
        lowSpeed();
      }, 3000);
    }
  };

  return (
    <div className="super-prize-grid">
      {data.map((item, index) => (
        <div
          className={`item item-${index} ${
            args.isRolling && map.get(index) === args.prizeIdx ? 'current' : ''
          }`}
          key={index}
          onClick={() => index === 4 && clickPrize()}
        >
          {item.content}
        </div>
      ))}
    </div>
  );
};

const data = [
  {
    seat: 0,
    content: '红包1',
  },
  {
    seat: 1,
    content: '红包2',
  },
  {
    seat: 2,
    content: '优惠券1',
  },
  {
    seat: 7,
    content: '优惠券2',
  },
  {
    seat: null,
    content: '开始抽奖',
  },
  {
    seat: 3,
    content: '积分1',
  },
  {
    seat: 6,
    content: '积分2',
  },
  {
    seat: 5,
    content: '谢谢1',
  },
  {
    seat: 4,
    content: '谢谢2',
  },
];

const map = new Map([
  [0, 0],
  [1, 1],
  [2, 2],
  [5, 3],
  [8, 4],
  [7, 5],
  [6, 6],
  [3, 7],
]);