首页 > 其他分享 >Vue3实现九宫格抽奖效果

Vue3实现九宫格抽奖效果

时间:2023-11-24 18:24:53浏览次数:57  
标签:baidu pic name 九宫格 state 抽奖 https Vue3 com

需求与效果

需求: 1、礼品根据后台配置生成 2、跑马灯转动效果 3、结果后台生成并且每个礼物概率不一样(概率这里不讨论)

注意点: 1、布局如何排列,是按照跑动排列还是从左至右自上而下排列 2、点击按钮如何插入,DOM结构如何生成 3、跑马效果如何实现,速度如何控制 4、接口如何处理,包括接口报错、请求Pending时特效 5、后台返回结果和跑马完选中结果一致等

最终效果图:

GIF 2022-11-16 九宫格抽奖.gif

注:图片都是百度图片找的

功能实现

第一步:实现布局

思路:  请求到后台配置的礼物列表,一般是配置了8个礼物,包括谢谢参与,第一种是手动布局写9个礼物div标签,这种方式开始按钮可以直接写到布局里面。第二种可以遍历,这里我使用遍历的方式,但是按钮就需要我们插入到礼物列表数组中去。  css代码可以使用flex布局,三行三列刚好,然后添加所需样式。js部分主要是使用splice()方法插入<开始按钮>。

部分代码:

<body>
  <div id="app" v-cloak>
    <div class="container">
      <div :class="['item', {'active': currentIndex === index}]"
        v-for="(item, index) in prizeList"
        @click="start(index)">
        <img :src="item.pic" alt="">
        <p v-if="index !== 4">{{ item.name }}</p>
      </div>
    </div>
  </div>
</body>
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' }
  ], // 后台配置的奖品数据
})
const startBtn = { name: '开始按钮', pic: 'https://img2.baidu.com/it/u=1497996119,382735686&fm=253' }

onMounted(() => {
  // state.prizeList.forEach((item, index) => {
  //  item.id = index
  // })
  state.prizeList.splice(4, 0, startBtn)
  console.log(state.prizeList)
})
第二步:实现动效

思路  跑马灯效果的实现主要是转圈高亮,这里我们可以想到当前礼物坐标和跑马执行步数除以8的余数一致时就高亮,但是跟礼物列表渲染的顺序又不一样,所以我们可以定义一个跑马执行的礼物坐标顺序数组 prizeSort = [0, 1, 2, 5, 8, 7, 6, 3],这样就是转圈执行啦。  然后动画一步一步的执行的话我们可以使用一个定时器,然后隔一点时间让当前高亮的下标索引currentIndex变化  转动的圈数我们可以自定义几圈,这里我们用总执行步数计算,必须是8的倍数,比如每次要转4圈,那基本的总执行步数就是32步,再根据后台中奖的礼物坐标计算出还要走多少步加上32即跑马执行的总部数  转动速度的话一般是先快后忙,我们利用定时器setTimeout()方法的话第二个参数的等待时间就要越来越长,可以判断如果执行总步数超过一般或三分之二就开始增加定时器等待时间,让下一步执行越来越慢。

第三步:后台抽奖结果

思路:  后台返回抽奖结果的话,这里可能出现上面注意点问题4和5,接口报错和长时间等待,我们处理方式可以为,点击先请求返回结果再开始动效,对于接口报错我们可以直接提示出来,如何长时间等待我们可以增加一个loading提示,这样就基本可以解决这两个问题了。对于后台返回结果和动效执行完结果如何匹配的问题,上面说过,我们有个基本的执行步数,然后根据请求结果的礼物坐标计算出还要走执行多少步,相加结果就可以匹配上。

代码
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
  <style>
    * {  margin: 0; padding: 0; box-sizing: border-box; }
    [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>
</head>
<body>
  <div id="app" v-cloak>
    <div class="container">
      <div :class="['item', {'active': currentIndex === index}]"
        v-for="(item, index) in prizeList"
        @click="start(index)">
        <img :src="item.pic" alt="">
        <p v-if="index !== 4">{{ item.name }}</p>
      </div>
    </div>
  </div>

  <script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
  <script>
    const { createApp, onMounted, ref, reactive, toRefs, computed } = Vue
    
    createApp({
      setup () {
        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 = { name: '开始按钮', pic: 'https://img2.baidu.com/it/u=1497996119,382735686&fm=253' }

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

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

        onMounted(() => {
          // state.prizeList.forEach((item, index) => {
          //  item.id = index
          // })
          state.prizeList.splice(4, 0, startBtn)
          console.log(state.prizeList)
        })

        // 获取随机数
        const getRandomNum = () => {
          // const num = Math.floor(Math.random() * 9)
          // if (num === 4) {
          //  console.log(">>>>>不能为4")
          //  return getRandomNum()
          // } else {
          //  return num        
          // }
          // 这里一次必然可以取到 时间为1次
          return prizeSort[Math.floor(Math.random() * prizeSort.length)]
        }

        const start = (i) => {
          if (i === 4 && !state.isRunning) {
            // 重置数据
            state.currentRunCount = 0
            state.speed = 100
            state.isRunning = true

            console.log('开始抽奖,后台请求中奖奖品')
            // 请求返回的奖品编号 这里使用随机数 但不能为4
            // const prizeId = getRandomNum()
            // console.log('中奖ID>>>', prizeId, state.prizeList[prizeId])
            // state.prizeId = prizeId
            // 模拟接口延时返回 如果接口突然报错如何处理?直接调用stopRun()方法停止转动
            setTimeout(() => {
              const prizeId = getRandomNum()
              console.log('中奖ID>>>', prizeId, state.prizeList[prizeId])
              state.prizeId = prizeId
            }, 2000)
            startRun()
          }
        }

        const startRun = () => {
          stopRun()
          console.log(state.currentRunCount, totalRunStep.value)
          // 要执行总步数
          // 已走步数超过
          if (state.currentRunCount > totalRunStep.value) {
            state.isRunning = false
            return
          }
          state.currentIndex = prizeSort[state.currentRunCount % 8]
          // 如果当前步数超过了2/3则速度慢下来
          if (state.currentRunCount > Math.floor(state.totalRunCount * 2 / 3)) {
            state.speed = state.speed + Math.floor(state.currentRunCount / 3)
            console.log('速度>>>>', state.speed)
          }
          state.timerIns = setTimeout(() => {
            state.currentRunCount++
            startRun()
          }, state.speed)
        }

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

        return {
          ...toRefs(state),
          start
        }
      }
    }).mount('#app')
  </script>
</body>
</html>

标签:baidu,pic,name,九宫格,state,抽奖,https,Vue3,com
From: https://www.cnblogs.com/wp-leonard/p/17854457.html

相关文章

  • Vue3实现转盘抽奖效果
    1、实现转盘数据动态配置(可通过接口获取)2、背景色通过分隔配置3、转动速度慢慢减速,最后停留在每一项的中间,下一次开始从本次开始4、当动画停止后在对应事件中自定义生成中奖提示。5、本次中奖概率随机生成,也可自定义配置实现代码html<template><divclass="graph-page">......
  • 遇到了vue3 刷新问题
     index.d762f427.js:3[Vuewarn]:Unhandlederrorduringexecutionofschedulerflush.ThisislikelyaVueinternalsbug.Pleaseopenanissueathttps://new-issue.vuejs.org/?repo=vuejs/coreat<Tags>at<HomeonVnodeUnmounted=fn<onVnodeU......
  • java实现大文件的分片上传与下载(springboot+vue3)
    1.1项目背景对于超大文件上传我们可能遇到以下问题•大文件直接上传,占用过多内存,可能导致内存溢出甚至系统崩溃•受网络环境影响,可能导致传输中断,只能重新传输•传输时间长,用户无法知道传输进度,用户体验不佳1.2项目目标对于上述问题,我们需要对文件做分片传输。分片传输就是......
  • uniapp+vue3中使用swiper和自定义header实现左右滑动的Tabs功能
    首先创建一个Tabs的Header,包含有一个下划线的指示器,在点击tabs的标题时候下划线会跟着动态的滑动下面是完整的Tabs的代码,可以看到定义了Tabs的background颜色样式,包含tab的宽度indicatorWidth,以及下划线的颜色indicatorColor主要的是tabList属性,通过tabList传入对应的tab数组得......
  • 全屏API及vue3 hook封装
    最近在一个大屏项目遇到一个需求:用户可以通过一个按钮,触发页面部分模块全屏。通过以下API可以实现:Element.requestFullscreen()方法用于发出异步请求使元素进入全屏模式。且全屏状态变化会触发以下事件:fullscreenchange事件会在浏览器进入或退出全屏模式后立即触发。基于......
  • 关于FastAPI与Vue3的通信
    学习一下前后端分离技术,前端采用三大框架之一的Vue.js,后端则采用Python的FastAPI框架。一、前端设计1.建目录mydemo2.在mydemo目录下打开命令行,运行:npminitvue@latest(这里如果cmd卡死了,就ctrl+C结束,再次运行npminitvue@latest)3.工程名设置为 frontend ,其余按默......
  • vue3 watch
    constfilterCommandList=computed(()=>{timerList.value.forEach((item)=>clearInterval(item));timerList.value=[];letdata=repeatReminderList.value;returndata.map((row)=>{row.close=false;row.lastT......
  • vue3所遇问题
    1. table表格无边框数据乱飞 解决方法:将import{}from'Elementplus'  删去  2.表单无法输入内容 解决方法 :   ref="form"     :model="form333"   ref与:modle 不可重名......
  • vue面试题_vue2和vue3的区别
    1、数据绑定原理不同vue2:vue2的数据绑定是利用ES5的一个API:Object.definePropert()对数据进行劫持,结合发布订阅模式的方式来实现的。vue3:vue3中使用了ES6的ProxyAPI对数据代理。相比vue2.x,使用proxy的优势如下:defineProperty只能监听某个属性,不能对全对象监听可以省去fori......
  • vue3 ts 父子 组件小例子
    <project-tabref="projectTabRef"v-model="form.projectVO":data="parentToChild"@update="updateHandler"></project-tab>//引用组件constProjectTab=defineAsyncComponent(()=>import('./tabsProject.......