简单的九宫格抽奖动画

153 阅读2分钟

简易的九宫格抽奖

// 渐变动画 animation.js
export default class animation {
  public myReq: number;
  public fpsAni: number;
  public maxSpeed: number;
  public defaultSpeed: number;
  public cycleNumber: number;
  public DataArr: Record<string, any>[];
  public running: Function | null;
  public runend: Function | null;

  constructor(DataArr: Record<string, any>[], RotateDir: number[], cycleNumber: number, minSpeed: number) {
    this.DataArr = JSON.parse(JSON.stringify(DataArr));
    // 最大速度
    this.maxSpeed = 2;
    this.cycleNumber = cycleNumber || 2;
    this.myReq = 0;
    this.fpsAni = 0;
    // 最小速度
    this.defaultSpeed = minSpeed || 15;
    
    this.DataArr.forEach((element, index) => {
      element.next = this.DataArr[RotateDir[index]];
    });

    this.running = null
    this.runend = null
  }

  run(id: number, running: Function, runend: Function) {
    const times: number[] = [];
    let fps: number; // 帧率
    const _this = this;
    let counter = 0; // 计数器,当前遍历到哪个节点
    let current = 0; // 当前数字值
    let n = 0;
    let currentObj = this.DataArr[0];
    let tem = this.DataArr[0];
    let drawId = id; // 中奖id
    if (drawId > this.DataArr.length) {
      drawId = Math.ceil(Math.random() * 8);
    }

    while (true) {
      if (n > this.DataArr.length) {
        console.error(`${drawId}不存在`);
        return;
      }
      if (tem.id == drawId) {
        break;
      }
      tem = tem.next;
      n++;
    }
    const allCount =
      id <= 8
        ? this.cycleNumber * this.DataArr.length + n
        : this.cycleNumber * this.DataArr.length * 2;
    // 加速区间
    const addSpeed = this.defaultSpeed - this.maxSpeed;
    // 减速区间
    const reduceSpeed = allCount - (this.defaultSpeed - this.maxSpeed);
    this.running = running;
    this.runend = runend;
    const refreshLoop = () => {
      this.fpsAni = requestAnimationFrame(() => {
        const now = performance.now();
        while (times.length > 0 && times[0] <= now - 1000) {
          times.shift();
        }
        times.push(now);
        fps = times.length;
        refreshLoop();
      });
    };
    refreshLoop();

    const step = () => {
      if (counter < addSpeed) {
        // 加速环节
        if (current < Math.pow(_this.defaultSpeed - counter, 2)) {
          if (fps < 40 || fps > 65) {
            current = current + (_this.defaultSpeed - counter) * 3;
          } else {
            current += _this.defaultSpeed / 2;
          }
        } else {
          current = 0;
          // 往前移动一个;
          counter++;
          currentObj = currentObj.next;
          currentObj.status = 1;
          _this.running && _this.running(currentObj);
        }
      } else if (counter >= addSpeed && counter < reduceSpeed) {
        // 匀速
        if (current < _this.maxSpeed) {
          current++;
        } else {
          // 计数清零
          current = 0;
          // 往前移动一个;
          counter++;
          currentObj = currentObj.next;
          currentObj.status = 2;
          _this.running && _this.running(currentObj);
        }
      } else if (counter >= reduceSpeed && counter < allCount) {
        //减速环节
        if (Math.sqrt(current) <= _this.defaultSpeed - (allCount - counter)) {
          current = current + 10;
        } else {
          // 计数清零
          current = 0;
          // 往前移动一个;
          counter++;
          currentObj = currentObj.next;
          currentObj.status = 3;
          _this.running && _this.running(currentObj);
        }
      }

      // 停止
      if (counter >= allCount) {
        _this.runend && _this.runend(currentObj);
        cancelAnimationFrame(_this.myReq);
        return;
      }
      _this.myReq = requestAnimationFrame(step);
    }
    this.myReq = requestAnimationFrame(step);
  }
  stop() {
    cancelAnimationFrame(this.myReq);
    cancelAnimationFrame(this.fpsAni);
  }
}
// 使用

// dom
<div v-for="(item, i) in sourceData" :key="i" class="grid-item" :class="{ 'select': item.isSelect }" @click="runAni(i)">{{ i === 4 ? 'click' : item.name }}</div>
// 样式
<style lang="less" scoped>
.grid{
  display: grid;
  grid-template-columns: repeat(3, 50px);
  grid-template-rows: repeat(3, 50px);
  background: #fff;
  border-radius: 16px;
  font-size: 12px;
  .grid-item{
    background-color: blueviolet;
  }
  .grid-item.select{
    background-color: chocolate;
  }
}
</style>
// 样式end

import animation from '@/utils/animation'

const sourceData = ref<Record<string, any>[]>([
  { id: 1, name: '1 name', isSelect: false},
  { id: 2, name: '2 name', isSelect: false},
  { id: 3, name: '3 name', isSelect: false},
  { id: 4, name: '4 name', isSelect: false},
  { id: 5, name: '5 name', isSelect: false},
  { id: 6, name: '6 name', isSelect: false},
  { id: 7, name: '7 name', isSelect: false},
  { id: 8, name: '8 name', isSelect: false},
  { id: 9, name: '9 name', isSelect: false},
])

// 抽奖动画转动顺序
const rotateDir = [1, 2, 3, 4, 5, 6, 7, 8, 0]

const luckdrawfn = new animation(sourceData.value, rotateDir, 3, 20);

// 执行抽奖动画
const runAni = (i: number) => {
  if (i !== 4) return
  const id = 2
  luckdrawfn.run(id, (params: Record<string, any>) => {
    sourceData.value = sourceData.value.map((item: Record<string, any>) => {
      return { ...item, isSelect: item.id === params.id}
    })
  }, (params: Record<string, any>) => {
    console.log('end', params, params.id)
  })
}

欢迎指正