简单实现掘金抽奖九宫格

662 阅读2分钟

小知识,大挑战!本文正在参与“程序员必备小知识”创作活动。

前言

看到掘金的签到抽奖,就想着如何实现这个动画的交互呢,今天就自己尝试了一下,这个只是一个初级版本的,动画是线性的,所以属于匀速的动画效果。

看一下效果:

屏幕录制2021-10-09 下午4.gif

准备

主要是使用了 window.requestAnimationFrame 来执行动画。

语法:

window.requestAnimationFrame(callback)

对比setTimeout/setInterval的优势

setTimeout 是以 n 毫秒后执行回调函数,回调函数中可以递归 调用 setTimeout 来实现动画。 setInterval 以 n 毫秒的间隔时间调用回调函数。

动画的实现就是不连续的运动通过每一帧的画面呈现出来的效果。

由于 js 是单线程的,浏览器是基于事件循环机制去执行代码的。setTimeout属于 JS 引擎,存在时间队列,所以即使是设置了定时时间,但是如果有任务没有执行完成,它的执行也是会有延迟的。setTimeout 的执行时间并不是确定的。

requestAnimationFrame 属于 GUI 引擎,发生在渲 染过程的中重绘重排部分,与电脑分辨路保持一致。

setTimeoutJS 引擎调用 GUI 引擎渲染。而requestAnimationFrame直属 GUI 引擎。

requestAnimationFrame 由浏览器专门为动画提供 的 API,在运行时浏览器会自动优化方法的调用,在特定性环境下可以有效节省了 CPU 开销。

代码实现

import React, { useState, useRef } from 'react';
import './index.less';
import { message } from 'antd';

const sudokuArray = [  { index: 0, dataIndex: 0 },  { index: 1, dataIndex: 1 },  { index: 2, dataIndex: 2 },  { index: 3, dataIndex: 7 },  { index: 4, center: true },  { index: 5, dataIndex: 3 },  { index: 6, dataIndex: 6 },  { index: 7, dataIndex: 5 },  { index: 8, dataIndex: 4 },];

function getRandomIntInclusive(min, max) {
  min = Math.ceil(min);
  max = Math.floor(max);
  return Math.floor(Math.random() * (max - min + 1)) + min; //含最大值,含最小值 
}

function animate({
  timing = f => f,
  draw,
  duration = 5000,
}) {
  let start = performance.now();
  let requestId;

  requestId = requestAnimationFrame(function step(time) {
    // timeFraction 从 0 增加到 1
    let timeFraction = (time - start) / duration;

    if (timeFraction > 1) {
      timeFraction = 1;
    }

    // 计算当前动画状态
    let progress = timing(timeFraction);
    draw(progress); // 绘制

    if (timeFraction < 1) {
      requestId = requestAnimationFrame(step);
    }
  });

  return () => {
    cancelAnimationFrame(requestId);
  }
}


const Sudoku = () => {
  const [active, setActive] = useState();
  // 防止重复点击
  const lockRef = useRef(false);

  const onStart = () => {
    if(lockRef.current) {
      return;
    }
    lockRef.current = true;

    try {
      new Promise((resolve) => {
        setTimeout(() => {
          // 获取抽奖结果
          resolve(getRandomIntInclusive(0, 7))
        })
      }).then((res) => {
        const target = 8 * 4 + res;
        console.log('抽中了 '+ res, '==>', target, )
        animate({
          draw: (progress) => {
            const cur = Math.floor(target * progress);

            if(cur < target) {
              setActive(cur % 8);
              return;
            }

            setActive(cur % 8);
            lockRef.current = false;
            message.info(`抽中了 ${res}`)
          }
        })
      })
    } catch(e) {
      lockRef.current = false;
      console.log(e)
    }
  }


  const renderItem = (item) => {
    if(item.center) {
      return <div onClick={onStart} key={item.index} className={`item${item.index}`}>start</div>
    }

    return(
      <div key={item.index} className={`item${item.index} ${active === item.dataIndex && 'activeItem'}` }>{item.dataIndex}</div>
    )
  }


  return (
    <div className="container">
      {sudokuArray.map((ele) => renderItem(ele))}
    </div>
  );
};


.container {
  display: grid;
  grid-template-columns: 100px 100px 100px;
  grid-template-rows: 100px 100px 100px;

  row-gap: 10px;
  column-gap: 10px;


  > div {
    font-size: 24px;
    display: flex;
    justify-content: center;
    align-items: center;
  }

  .item0 {
    background-color: aqua;
  }

  .item1 {
    background-color: aquamarine;
  }

  .item2 {
    background-color: bisque;
  }

  .item3 {
    background-color: burlywood;
  }

  .item4 {
    background-color: chocolate;

    &:hover {
      cursor: pointer;
    }
  }

  .item5 {
    background-color: cornflowerblue;
  }

  .item6 {
    background-color: darkcyan;
  }

  .item7 {
    background-color: darkgreen;
  }

  .item8 {
    background-color: darkorchid;
  }

  .activeItem {
    opacity: 0.6;
  }
}

结语

如果这篇文章帮到了你,欢迎点赞👍和关注⭐️。

文章如有错误之处,希望在评论区指正🙏🙏。