vue3手写抽奖转盘

有许多第三方抽奖的转盘的库,如果对样式没有要求的,建议直接用第三方库,

这是我看到比较好的转盘库:在 Vue 中使用 | 基于 Js / TS / Vue / React / 微信小程序 / uni-app / Taro 的【大转盘 & 九宫格 & 老虎机】抽奖插件

但是转盘有样式要求的,可以把下面手写转盘参考,这可以设置你想要的装盘样式,旋转停止在你想要停的位置,当然也可以设置随机旋转停止位置

支持自动设置转盘颜色,文字颜色,装盘伞形块个数

一、先看具体实现效果:

二,具体实现

1、上面展示的转盘仅用到的三张图片

当然,没有类似图片资源一样正常使用,就是美观度不够,下面我展示是不用任何图片资源的写法

不用图片资源的效果如下:

看完下面代码,你可以根据自己想要的样式需求任意调整css

2.转盘的元素分布分析图

3.三角形的位置计算公式如下

const triangleComputeFn = () => {
  // 计算每个板块三角形的边长 以第一个三角区域为例
  /*
  .wheel-rotate { // 正方形滚动盘父盒子元素
    width: 152px; //滚动盘的宽(自己设定)
    height: 152px; //滚动盘的高
    border-radius: 50%;
    position: relative;
  }
  .wheel-area1 {
    position: absolute;
    top: 0;
    width: 0;
    height: 0;

    /  代表除法

    //  定位在 滚动盘left举例 公式:left = 滚动盘的宽/2 - R(R为border-right的大小)
    left: 25.24px;  

    //border-right,border-left的大小 公式为:R=滚动盘的宽*4 / 轮盘分割的块数 / 2
    //border-top的大小 公式为:L=滚动盘的宽*4 / 轮盘分割的块数 / 2
    border-right: 50.66px solid transparent;
    border-left: 50.66px solid transparent;
    border-top: 76px solid #ea3033; 

    transform-origin: bottom; //以底部中心为圆心
    transform: rotate(60deg); // 旋转角度的公式 : N = 360 / 轮盘分割的块数
  } */
}

4、最终的实现完整代码和逻辑如下 (这里是放了一个转盘组件的完整代码):

<template>
  <div class="wheel">
    <div class="wheel-title">转盘</div>
    <div class="wheel-container">
      <!-- 转盘div区域 -->
      <div class="wheel-box">
        <div class="wheel-edge"></div>
        <!-- <div class="wheel-center1"></div> -->
        <!-- <div class="wheel-center2"></div> -->
        <div class="wheel-center3"></div>
        <div class="wheel-rotate" ref="myZPan">
          <template v-for="item in wheelList" :key="item.id">
            <div
              class="wheel-area"
              :style="{
                borderTopColor: item.bgColor,
                transform: 'rotate(' + `${item.RotateDeg}` + ')'
              }"
            >
              <div class="wheel-text" :style="{ color: item.textColor }">{{ item.name }}</div>
            </div>
          </template>
          <!-- <div class="wheel-area1">
            <div class="wheel-text">1~1</div>
          </div>
          <div class="wheel-area2">
            <div class="wheel-text">2~2</div>
          </div>
          <div class="wheel-area3">
            <div class="wheel-text">3~3</div>
          </div>
          <div class="wheel-area4">
            <div class="wheel-text">4~4</div>
          </div>
          <div class="wheel-area5">
            <div class="wheel-text">5~5</div>
          </div>
          <div class="wheel-area6">
            <div class="wheel-text">6~6</div>
          </div> -->
        </div>
      </div>
    </div>
    <div class="wheel-col">
      <div class="wheel-place">
        <div class="wheel-place-text">指定停止位置:</div>
        <div class="wheel-place-input">
          <input
            v-model="stopValue"
            placeholder="输入停止值(1~6)"
            maxlength="10"
            min="1"
            pattern="[0-9]*"
            inputmode="numeric"
            type="number"
            class="inputSum"
            @input="changeMailInput"
          />
        </div>
        <el-button class="wheel-button" type="primary" @click="goRewardFn">抽</el-button>
      </div>
      <div class="wheel-place">
        <div class="wheel-place-text">随机停止位置:</div>
        <el-button class="wheel-button" type="primary" @click="goRandomFn">抽</el-button>
      </div>
    </div>
  </div>
</template>
<script lang="ts" setup>
import { onMounted, ref } from 'vue'

const myZPan = ref<any>(null) //需要旋转的元素dom
const endAngle = ref(0) //计算要最终旋转的弧度
const stopValue = ref(1) //停摆位置
const NumberTurns = ref(1) //旋转圈数 初始值

const changeMailInput = () => {
  console.log('输入框的改变值为', stopValue.value)
}

const onRoll = (index: number) => {
  // 开始旋转转盘
  let angle = 0 //最终旋转的角度
  NumberTurns.value = NumberTurns.value + 4 //每轮旋转的次数

  //计算终止角度,加指定停止位置 这里(360 / NSum.value)的计算公式为 360 / 轮盘分割的块数
  angle = 360 * NumberTurns.value + (360 - (index - 1) * (360 / NSum.value))

  myZPan.value.style = 'transform:rotate(' + angle + 'deg); transition: all 3s ease;' //设置旋转角度
  //在元素上应用一个 CSS 过渡(通过 transition 属性),并且那个过渡完成时,就会触发 transitionend 事件
  myZPan.value.addEventListener('transitionend', stopRoll)
  endAngle.value = angle
  console.log('设置旋转角度', endAngle.value, index)
}

const stopRoll = () => {
  // 停止旋转转盘
  myZPan.value.style = 'transform:rotate(' + endAngle.value + 'deg);' //设置旋转角度
  hasDraw.value = true //抽奖结束
}

const hasDraw = ref(true) //是否正在抽奖中,true是可以抽,false 是正在抽奖中
const goRewardFn = () => {
  if (!hasDraw.value) return
  hasDraw.value = false //开始抽奖--正在抽奖中
  onRoll(stopValue.value)
  // return;
}
const goRandomFn = () => {
  if (!hasDraw.value) return
  hasDraw.value = false //开始抽奖--正在抽奖中
  // 1到6之间的随机整数
  stopValue.value = Math.floor(Math.random() * 6) + 1
  console.log('1到6之间的随机整数', stopValue.value)

  onRoll(stopValue.value)
  // return;
}

interface WheelList {
  id: number | string //唯一id标识 -->必填
  name: string // 伞形块名字  -->必填
  bgColor: string //伞形块颜色  -->选填
  textColor: string // 伞形块文字颜色 -->选填
  RotateDeg: string //伞形块需要在圆盘摆放的位置 -->不用填写,下面会计算出来
}
// wheelList伞形块数组长度必须wheelList>=3,要不样式会出错
const wheelList = ref<Array<WheelList>>([
  {
    id: 1,
    name: '1~1',
    bgColor: '#ea3033',
    textColor: '#fff',
    RotateDeg: '0'
  },
  {
    id: 2,
    name: '2~2',
    bgColor: '#33cdef',
    textColor: '#003790',
    RotateDeg: '60deg'
  },
  {
    id: 3,
    name: '3~3',
    bgColor: '#f674f2',
    textColor: '#6c0067',
    RotateDeg: '120deg'
  },
  {
    id: 4,
    name: '4~4',
    bgColor: '#2876f4',
    textColor: '#fff',
    RotateDeg: '180deg'
  },
  {
    id: 5,
    name: '5~5',
    bgColor: '#86eb21',
    textColor: '#215200',
    RotateDeg: '240deg'
  },
  {
    id: 6,
    name: '6~6',
    bgColor: '#ffc212',
    textColor: '#cc0001',
    RotateDeg: '300deg'
  },
  {
    id: 7,
    name: '7~7',
    bgColor: '#86eb21',
    textColor: '#215200',
    RotateDeg: '240deg'
  },
  {
    id: 8,
    name: '8~8',
    bgColor: '#f674f2',
    textColor: '#6c0067',
    RotateDeg: '120deg'
  },
  {
    id: 9,
    name: '9~9',
    bgColor: '#2876f4',
    textColor: '#fff',
    RotateDeg: '180deg'
  }
])

const boxWidth = ref(152) //滚动盘的宽(这里我自己设定盒子宽高152px)
const positionLeft = ref('') //三角形定位在 滚动盘left
const borderLeft = ref('') //三角形宽的距离
const borderTop = ref('') //三角形高的距离
const transformRotate = ref(0) ///三角形旋转角度 初始值为0
const NSum = ref(wheelList.value.length) //轮盘分割的块数

const triangleComputeFn = () => {
  // 计算每个板块三角形的边长 以第一个三角区域为例
  /*
  .wheel-rotate { // 正方形滚动盘父盒子元素
    width: 152px; //滚动盘的宽(自己设定)
    height: 152px; //滚动盘的高
    border-radius: 50%;
    position: relative;
  }
  .wheel-area1 {
    position: absolute;
    top: 0;
    width: 0;
    height: 0;

    /  代表除法

    //  定位在 滚动盘left举例 公式:left = 滚动盘的宽/2 - R(R为border-right的大小)
    left: 25.24px;  

    //border-right,border-left的大小 公式为:R=滚动盘的宽*4 / 轮盘分割的块数 / 2
    //border-top的大小 公式为:L=滚动盘的宽/2
    border-right: 50.66px solid transparent;
    border-left: 50.66px solid transparent;
    border-top: 76px solid #ea3033; 

    transform-origin: bottom; //以底部中心为圆心
    transform: rotate(60deg); // 旋转角度的公式 : N = 360 / 轮盘分割的块数
  } */

  let boxWidthValue = boxWidth.value //滚动盘的宽(自己设定)数值
  let positionLeftValue = 0 //三角形定位在 滚动盘left数值
  let borderLeftValue = 0 //三角形宽的距离数值
  let borderTopValue = 0 //三角形高的距离数值
  let transformRotateValue = 0 ///三角形旋转角度数值
  let NValue = NSum.value || wheelList.value.length //轮盘分割的块数数值

  borderLeftValue = Number(((boxWidthValue * 4) / NValue / 2).toFixed(2))
  positionLeftValue = boxWidthValue / 2 - borderLeftValue
  borderTopValue = boxWidthValue / 2
  transformRotateValue = 360 / NValue

  positionLeft.value = positionLeftValue + 'px'
  borderLeft.value = borderLeftValue + 'px'
  borderTop.value = borderTopValue + 'px'
  transformRotate.value = transformRotateValue
  const newWheelList = wheelList.value.map((item, index) => {
    const ele = Object.assign(item, {
      RotateDeg: transformRotateValue * index + 'deg'
    })
    return ele
  })
  wheelList.value = newWheelList
  // console.log('newWheelList', newWheelList)
}
onMounted(() => {
  triangleComputeFn()
})
</script>
<style scoped long="scss">
.wheel {
  width: 200px;
  height: 300px;
  border: 2px solid #504d4d;
  border-radius: 5px;
  display: flex;
  flex-direction: column;
  justify-content: space-between;
  align-items: center;
  .wheel-title {
    width: 100%;
    height: 30px;
    font-size: 16px;
    display: flex;
    justify-content: center;
    align-items: center;
    background-color: rgb(226, 241, 157);
  }
  .wheel-container {
    width: 200px;
    height: 200px;
    display: flex;
    justify-content: center;
    align-items: center;
    .wheel-box {
      width: 180px;
      height: 180px;
      background-color: antiquewhite;
      border-radius: 50%;
      position: relative;
      display: flex;
      justify-content: center;
      align-items: center;
      .wheel-edge {
        position: absolute;
        top: 0;
        left: 0;
        right: 0;
        bottom: 0;
        margin: auto;
        z-index: 2;
        width: 180px;
        height: 180px;
        border-radius: 50%;
        background-size: contain;
        background-position: center center;
        background-repeat: no-repeat;
        /* background-image: url('@/views/learning/img/wheel/zpp.png'); */
      }
      .wheel-center1 {
        position: absolute;
        top: 0;
        left: 0;
        right: 0;
        bottom: 0;
        margin: auto;
        z-index: 3;
        width: 30px;
        height: 30px;
        border-radius: 50%;
        background-size: contain;
        background-position: center center;
        background-repeat: no-repeat;
        /* background-image: url('@/views/learning/img/wheel/zp1.png'); */
      }
      .wheel-center2 {
        position: absolute;
        top: 68px;
        left: 77px;
        z-index: 4;
        width: 30px;
        height: 30px;
        border-radius: 50%;
        background-size: contain;
        background-position: center center;
        background-repeat: no-repeat;
        /* background-image: url('@/views/learning/img/wheel/zp-zj.png'); */
        transform: rotate(180deg);
      }
      .wheel-center3 {
        position: absolute;
        top: 66px;
        left: 81px;
        width: 0;
        height: 0;
        z-index: 5;
        border-radius: 50%;
        border-right: 10px solid transparent;
        border-left: 10px solid transparent;
        border-bottom: 30px solid #ceea30;
      }
      .wheel-rotate {
        width: 152px;
        height: 152px;
        border-radius: 50%;
        overflow: hidden;
        /* background-color: rgb(112, 110, 107); */
        position: relative;
        .wheel-area {
          position: absolute;
          top: 0;
          left: v-bind(positionLeft);
          width: 0;
          height: 0;
          z-index: 1;
          border-right: v-bind(borderLeft) solid transparent;
          border-left: v-bind(borderLeft) solid transparent;
          border-top: v-bind(borderTop) solid #ea3033;
          display: flex;
          align-items: center;
          justify-content: center;

          transform-origin: bottom;
          transform: rotate(0deg);
          .wheel-text {
            position: absolute;
            bottom: 20px;
            left: 50%;
            font-size: 12px;
            color: #fff;
            font-weight: bold;
            transform-origin: left center;
            transform: rotate(270deg);
            text-align: left;
          }
        }
        .wheel-area1 {
          position: absolute;
          top: 0;
          left: 25.24px;
          width: 0;
          height: 0;
          z-index: 1;
          border-right: 50.66px solid transparent;
          border-left: 50.66px solid transparent;
          border-top: 76px solid #ea3033;
          border-radius: 50%;
          display: flex;
          align-items: center;
          justify-content: center;

          /* transform-origin: bottom;
            transform: rotate(60deg); */
          .wheel-text {
            position: absolute;
            bottom: 20px;
            left: 50%;
            font-size: 12px;
            color: #fff;
            font-weight: bold;
            transform-origin: left center;
            transform: rotate(270deg);
            text-align: left;
          }
        }
        .wheel-area2 {
          position: absolute;
          top: 0;
          left: 25.24px;
          width: 0;
          height: 0;
          z-index: 1;
          border-right: 50.66px solid transparent;
          border-left: 50.66px solid transparent;
          border-top: 76px solid #33cdef;
          border-radius: 50%;
          display: flex;
          align-items: center;
          justify-content: center;

          transform-origin: bottom;
          transform: rotate(60deg);
          .wheel-text {
            position: absolute;
            bottom: 20px;
            left: 50%;
            font-size: 12px;
            color: #003790;
            font-weight: bold;
            transform-origin: left center;
            transform: rotate(270deg);
            text-align: left;
          }
        }
        .wheel-area3 {
          position: absolute;
          top: 0;
          left: 25.24px;
          width: 0;
          height: 0;
          z-index: 1;
          border-right: 50.66px solid transparent;
          border-left: 50.66px solid transparent;
          border-top: 76px solid #f674f2;
          border-radius: 50%;
          display: flex;
          align-items: center;
          justify-content: center;

          transform-origin: bottom;
          transform: rotate(120deg);
          .wheel-text {
            position: absolute;
            bottom: 20px;
            left: 50%;
            font-size: 12px;
            color: #6c0067;
            font-weight: bold;
            transform-origin: left center;
            transform: rotate(270deg);
            text-align: left;
          }
        }
        .wheel-area4 {
          position: absolute;
          top: 0;
          left: 25.24px;
          width: 0;
          height: 0;
          z-index: 1;
          border-right: 50.66px solid transparent;
          border-left: 50.66px solid transparent;
          border-top: 76px solid #2876f4;
          border-radius: 50%;
          display: flex;
          align-items: center;
          justify-content: center;

          transform-origin: bottom;
          transform: rotate(180deg);
          .wheel-text {
            position: absolute;
            bottom: 20px;
            left: 50%;
            font-size: 12px;
            color: #fff;
            font-weight: bold;
            transform-origin: left center;
            transform: rotate(270deg);
            text-align: left;
          }
        }
        .wheel-area5 {
          position: absolute;
          top: 0;
          left: 25.24px;
          width: 0;
          height: 0;
          z-index: 1;
          border-right: 50.66px solid transparent;
          border-left: 50.66px solid transparent;
          border-top: 76px solid #86eb21;
          border-radius: 50%;
          display: flex;
          align-items: center;
          justify-content: center;

          transform-origin: bottom;
          transform: rotate(240deg);
          .wheel-text {
            position: absolute;
            bottom: 20px;
            left: 50%;
            font-size: 12px;
            color: #215200;
            font-weight: bold;
            transform-origin: left center;
            transform: rotate(270deg);
            text-align: left;
          }
        }
        .wheel-area6 {
          position: absolute;
          top: 0;
          left: 25.24px;
          width: 0;
          height: 0;
          z-index: 1;
          border-right: 50.66px solid transparent;
          border-left: 50.66px solid transparent;
          border-top: 76px solid #ffc212;
          border-radius: 50%;
          display: flex;
          align-items: center;
          justify-content: center;

          transform-origin: bottom;
          transform: rotate(300deg);
          .wheel-text {
            position: absolute;
            bottom: 20px;
            left: 50%;
            font-size: 12px;
            color: #cc0001;
            font-weight: bold;
            transform-origin: left center;
            transform: rotate(270deg);
            text-align: left;
          }
        }
      }
    }
  }
  .wheel-col {
    width: 100%;
    height: 70px;
    font-size: 16px;
    display: flex;
    flex-direction: column;
    justify-content: space-around;
    align-items: center;
    background-color: rgb(226, 241, 157);
    .wheel-place {
      width: 100%;
      height: 30px;
      display: flex;
      justify-content: center;
      align-items: center;
      .wheel-place-text {
        width: 84px;
        height: 30px;
        font-size: 12px;
        text-align: center;
        line-height: 30px;
        /* scale: 0.85; */
      }
      .wheel-place-input {
        width: 60px;
        height: 30px;
        display: flex;
        justify-content: center;
        align-items: center;
        .inputSum {
          width: 50px;
          height: 20px;
          font-size: 12px;
          overflow: hidden;
          padding: 0;
        }
      }
      .wheel-button {
        width: 40px;
        height: 20px;
        font-size: 12px;
        line-height: 20px;
      }
    }
  }
}
</style>

  • 9
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
生成抽奖转盘可以使用 Vue.js 和一些 JavaScript 库来实现。以下是一个简单的实现步骤: 1. 安装必要的库 使用 npm 安装以下库: - vue:用于构建应用程序的主要框架。 - vue-router:用于创建 SPA 应用程序的路由库。 - axios:用于从 API 中获取数据的库。 - vue-axios:用于在 Vue 组件中使用 axios 的库。 - vue-luck-draw:用于创建抽奖转盘的库。 ``` npm install vue vue-router axios vue-axios vue-luck-draw --save ``` 2. 创建一个 Vue 组件 在 Vue 组件中,我们可以使用 vue-luck-draw 库创建一个抽奖转盘。以下是一个示例组件: ``` <template> <div> <vue-luck-draw :prizes="prizes" :buttons="buttons" :colors="colors" :bg-colors="bgColors" :is-start="isStart" :start-btn-text="startBtnText" :start-btn-color="startBtnColor" :start-btn-bg-color="startBtnBgColor" :start-btn-border-color="startBtnBorderColor" :speed="speed" :rotate-num="rotateNum" :rotate-end="rotateEnd" @start="onStart" @end="onEnd"></vue-luck-draw> </div> </template> <script> import VueLuckDraw from 'vue-luck-draw' export default { data() { return { prizes: [], // 奖品列表 buttons: [], // 按钮列表 colors: [], // 每个奖品扇形的颜色 bgColors: [], // 每个奖品扇形背景色 isStart: false, // 是否在进行抽奖 startBtnText: '开始', // 开始按钮文本 startBtnColor: '#ffffff', // 开始按钮文字颜色 startBtnBgColor: '#f00', // 开始按钮背景颜色 startBtnBorderColor: '#f00', // 开始按钮边框颜色 speed: 20, // 转盘速度 rotateNum: 8, // 转多少圈 rotateEnd: 0, // 转到哪个奖品位置 } }, components: { VueLuckDraw, }, methods: { onStart() { // 抽奖开始 }, onEnd(prize) { // 抽奖结束,prize 为中奖奖品 }, }, } </script> ``` 3. 设置奖品、按钮、颜色等参数 在组件中设置奖品、按钮、颜色等参数。以下是一个示例: ``` data() { return { prizes: [ { id: 1, name: '一等奖', }, { id: 2, name: '二等奖', }, { id: 3, name: '三等奖', }, { id: 4, name: '谢谢参与', }, { id: 5, name: '四等奖', }, { id: 6, name: '五等奖', }, ], buttons: [ { id: 1, name: '开始', }, { id: 2, name: '重置', }, ], colors: ['#ffff00', '#ff0000', '#ffff00', '#ff0000', '#ffff00', '#ff0000'], bgColors: ['#ffffff', '#ffffff', '#ffffff', '#ffffff', '#ffffff', '#ffffff'], } }, ``` 4. 处理抽奖逻辑 在组件中处理抽奖逻辑。例如,在 onStart 方法中从后端 API 中获取中奖奖品,然后在 onEnd 方法中显示中奖信息。 ``` methods: { onStart() { // 抽奖开始 this.isStart = true // 从后端 API 中获取中奖奖品 axios.get('/api/prize') .then(response => { const prize = response.data.prize // 计算中奖奖品位置 const index = this.prizes.findIndex(item => item.id === prize.id) this.rotateEnd = (index + 1) * (360 / this.prizes.length) - 180 / this.prizes.length // 停止转盘 setTimeout(() => { this.isStart = false }, 5000) }) .catch(error => { console.log(error) }) }, onEnd(prize) { // 抽奖结束,prize 为中奖奖品 alert(`恭喜您获得了${prize.name}`) }, }, ``` 5. 添加样式 在组件中添加样式,例如: ``` <style scoped> .vue-luck-draw { width: 300px; height: 300px; margin: 0 auto; } </style> ``` 6. 在路由中使用组件 在 Vue 路由中使用组件,例如: ``` import Vue from 'vue' import Router from 'vue-router' import Lottery from '@/components/Lottery' Vue.use(Router) export default new Router({ routes: [ { path: '/', name: 'Lottery', component: Lottery, }, ], }) ``` 7. 运行应用程序 运行应用程序,例如: ``` npm run serve ``` 然后在浏览器中打开 http://localhost:8080。现在您应该可以看到一个抽奖转盘

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值