【前端】九宫格抽奖效果,每次从当前位置开始

场景

做一个h5活动页,需要实现九宫格旋转抽奖效果。

参考:Vue3实现九宫格抽奖效果前言  好久没有写文章了,上一次发文还是年终总结,眨眼间又是一年,每每想多总结却是坚持不来,难 - 掘金

在这里插入图片描述
跟上述参考链接的不同点是:我从当前位置开始旋转。

逻辑

九宫格旋转

九宫格如下排列:

012
345
678

其中4为空,不会转到。九宫格只会在外面转,顺时针的顺序为:

[0,1,2,5,8,7,6,3]

一圈8格。如果想要多转几圈,则加8N

假设规定一定要走32步。每次计算的总步数=到达目标的步数+32。从非0开始时,距离0的步数视为【已走步数】

  • 首次点击时,指针从0开始

  • 假设抽到5,则共走32+3=35步,会走到5号

  • 假设第二次抽到6:

    • 共走:6+32=38步
    • 从0开始走38步即正确位置
    • 将当前位置5赋值为【已走步数】,这步是从当前开始的关键
    • 则从当前位置只需再走33步
    • 以此类推

转一圈其实是调用一次方法。

先加速后减速

配置一个速度(等待时间)。当执行的步数> 2/3总步数,则增加等待时间

代码

基本数据

const state = reactive({
  prizeList: [
    {
      name: '手机',
      pic: 'https://bkimg.cdn.bcebos.com/pic/3801213fb80e7bec54e7d237ad7eae389b504ec23d9e',
    },
    {
      name: '手表',
      pic: 'https://img1.baidu.com/it/u=2631716577,1296460670&fm=253&fmt=auto&app=120&f=JPEG',
    },
    {
      name: '苹果',
      pic: 'https://img2.baidu.com/it/u=2611478896,137965957&fm=253&fmt=auto&app=138&f=JPEG',
    },
    {
      name: '棒棒糖',
      pic: 'https://img2.baidu.com/it/u=576980037,1655121105&fm=253&fmt=auto&app=138&f=PNG',
    },
    {
      name: '娃娃',
      pic: 'https://img2.baidu.com/it/u=4075390137,3967712457&fm=253&fmt=auto&app=138&f=PNG',
    },
    {
      name: '木马',
      pic: 'https://img1.baidu.com/it/u=2434318933,2727681086&fm=253&fmt=auto&app=120&f=JPEG',
    },
    {
      name: '德芙',
      pic: 'https://img0.baidu.com/it/u=1378564582,2397555841&fm=253&fmt=auto&app=120&f=JPEG',
    },
    {
      name: '玫瑰',
      pic: 'https://img1.baidu.com/it/u=1125656938,422247900&fm=253&fmt=auto&app=120&f=JPEG',
    },
  ], // 后台配置的奖品数据
  currentIndex: 0, // 当前位置(下标)
  isRunning: false, // 是否正在抽奖
  speed: 10, // 抽奖等待时间
  timerIns: null, // 定时器实例
  currentRunCount: 0, // 已跑次数
  totalRunCount: 32, // 规定一定要跑的次数 8的倍数
  prizeId: 0, // 中奖id
})

模拟抽奖

// 获取随机数
const getRandomNum = () => {
  return prizeSort.value[Math.floor(Math.random() * prizeSort.value.length)]
}

核心逻辑

// 奖品高亮顺序
const prizeSort = ref([0, 1, 2, 5, 8, 7, 6, 3])

// 要执行总步数
const totalRunStep = computed(() => {
  return state.totalRunCount + prizeSort.value.indexOf(state.prizeId)
})

// 在中间塞一个4号,不会转到
onMounted(() => {
  state.prizeList.splice(4, 0, startBtn.value)
})

// 获取随机数
const getRandomNum = () => {
  return prizeSort.value[Math.floor(Math.random() * prizeSort.value.length)]
}

// 点击开始
const start = () => {
  if (state.isRunning) return
  state.isRunning = true
  state.currentRunCount = state.currentIndex // 从当前位置开始
  state.speed = 50

  // 模拟接口延时返回
  setTimeout(() => {
    const prizeId = getRandomNum()
    console.log('中奖ID>>>', prizeId, state.prizeList[prizeId])
    state.prizeId = prizeId
    state.isRunning = false
    console.log(totalRunStep.value, '总步数')
    // 拿到结果后再开始转
    startRun()
  }, 200)
}

const startRun = () => {
  stopRun()

  // 已走步数超过总步数
  if (state.currentRunCount > totalRunStep.value) {
    state.isRunning = false
    return
  }

  // 计算当前显示的奖品索引
  state.currentIndex = prizeSort.value[state.currentRunCount % 8]

  // 如果当前步数超过了 2/3,则速度慢下来 : 只要效果是先快后慢即可。
  if (state.currentRunCount > Math.floor((state.totalRunCount * 2) / 3)) {
    state.speed = state.speed + Math.floor(state.currentRunCount / 3)
  }

  state.timerIns = setTimeout(() => {
    state.currentRunCount++
    startRun()
  }, state.speed)
}

const stopRun = () => {
  state.timerIns && clearTimeout(state.timerIns)
}

总代码

<template>
  <div>
    <div id="app" v-cloak>
      <div class="container">
        <div
          :class="['item', { active: state.currentIndex === index }]"
          v-for="(item, index) in state.prizeList"
          @click="start"
        >
          <img :src="item.pic" alt="" />
          <p v-if="index !== 4">{{ item.name }}</p>
        </div>
      </div>
    </div>
  </div>
</template>

<script lang="ts" setup>
import { ref, reactive, onMounted, computed } from 'vue'

const state = reactive({
  prizeList: [
    {
      name: '手机',
      pic: 'https://bkimg.cdn.bcebos.com/pic/3801213fb80e7bec54e7d237ad7eae389b504ec23d9e',
    },
    {
      name: '手表',
      pic: 'https://img1.baidu.com/it/u=2631716577,1296460670&fm=253&fmt=auto&app=120&f=JPEG',
    },
    {
      name: '苹果',
      pic: 'https://img2.baidu.com/it/u=2611478896,137965957&fm=253&fmt=auto&app=138&f=JPEG',
    },
    {
      name: '棒棒糖',
      pic: 'https://img2.baidu.com/it/u=576980037,1655121105&fm=253&fmt=auto&app=138&f=PNG',
    },
    {
      name: '娃娃',
      pic: 'https://img2.baidu.com/it/u=4075390137,3967712457&fm=253&fmt=auto&app=138&f=PNG',
    },
    {
      name: '木马',
      pic: 'https://img1.baidu.com/it/u=2434318933,2727681086&fm=253&fmt=auto&app=120&f=JPEG',
    },
    {
      name: '德芙',
      pic: 'https://img0.baidu.com/it/u=1378564582,2397555841&fm=253&fmt=auto&app=120&f=JPEG',
    },
    {
      name: '玫瑰',
      pic: 'https://img1.baidu.com/it/u=1125656938,422247900&fm=253&fmt=auto&app=120&f=JPEG',
    },
  ], // 后台配置的奖品数据
  currentIndex: 0, // 当前位置(下标)
  isRunning: false, // 是否正在抽奖
  speed: 10, // 抽奖等待时间
  timerIns: null, // 定时器实例
  currentRunCount: 0, // 已跑次数
  totalRunCount: 32, // 规定一定要跑的次数 8的倍数
  prizeId: 0, // 中奖id
})

const startBtn = ref({
  name: '开始按钮',
  pic: 'https://img2.baidu.com/it/u=1497996119,382735686&fm=253',
})

// 奖品高亮顺序
const prizeSort = ref([0, 1, 2, 5, 8, 7, 6, 3])

// 要执行总步数
const totalRunStep = computed(() => {
  return state.totalRunCount + prizeSort.value.indexOf(state.prizeId)
})

onMounted(() => {
  state.prizeList.splice(4, 0, startBtn.value)
})

// 获取随机数
const getRandomNum = () => {
  return prizeSort.value[Math.floor(Math.random() * prizeSort.value.length)]
}

const start = () => {
  if (state.isRunning) return
  state.isRunning = true
  state.currentRunCount = state.currentIndex // 从当前位置开始
  state.speed = 50

  // 模拟接口延时返回
  setTimeout(() => {
    const prizeId = getRandomNum()
    console.log('中奖ID>>>', prizeId, state.prizeList[prizeId])
    state.prizeId = prizeId
    state.isRunning = false
    console.log(totalRunStep.value, '总步数')
    startRun()
  }, 200)
}

const startRun = () => {
  stopRun()

  // 已走步数超过总步数
  if (state.currentRunCount > totalRunStep.value) {
    state.isRunning = false
    return
  }

  // 计算当前显示的奖品索引
  state.currentIndex = prizeSort.value[state.currentRunCount % 8]

  // 如果当前步数超过了 2/3,则速度慢下来
  if (state.currentRunCount > Math.floor((state.totalRunCount * 2) / 3)) {
    state.speed = state.speed + Math.floor(state.currentRunCount / 3)
  }

  state.timerIns = setTimeout(() => {
    state.currentRunCount++
    startRun()
  }, state.speed)
}

const stopRun = () => {
  state.timerIns && clearTimeout(state.timerIns)
}
</script>

<style lang="less" scoped>
[v-cloak] {
  display: none;
}
.container {
  width: 450px;
  height: 450px;
  background: #98d3fc;
  border: 1px solid #98d3fc;
  margin: 100px auto;
  display: flex;
  flex-wrap: wrap;
  justify-content: space-around;
  align-items: center;
}
.item {
  width: 140px;
  height: 140px;
  border: 2px solid #fff;
  position: relative;
}
.item:nth-of-type(5) {
  cursor: pointer;
}
.item img {
  width: 100%;
  height: 100%;
}
.item p {
  width: 100%;
  height: 20px;
  background: rgba(0, 0, 0, 0.5);
  color: #fff;
  font-size: 12px;
  text-align: center;
  line-height: 20px;
  position: absolute;
  left: 0;
  bottom: 0;
}
.active {
  border: 2px solid red;
  box-shadow: 2px 2px 30px #fff;
}
</style>

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

karshey

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值