vue3手写抽奖转盘

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

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

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

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

一、先看具体实现效果:

二,具体实现

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

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

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

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

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

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

javascript 复制代码
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、最终的实现完整代码和逻辑如下 (这里是放了一个转盘组件的完整代码):

javascript 复制代码
<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 分钟前
记录学习react的一些内容
javascript·学习·react.js
吕彬-前端14 分钟前
使用vite+react+ts+Ant Design开发后台管理项目(五)
前端·javascript·react.js
学前端的小朱16 分钟前
Redux的简介及其在React中的应用
前端·javascript·react.js·redux·store
guai_guai_guai26 分钟前
uniapp
前端·javascript·vue.js·uni-app
也无晴也无风雨27 分钟前
在JS中, 0 == [0] 吗
开发语言·javascript
bysking1 小时前
【前端-组件】定义行分组的表格表单实现-bysking
前端·react.js
王哲晓2 小时前
第三十章 章节练习商品列表组件封装
前端·javascript·vue.js
fg_4112 小时前
无网络安装ionic和运行
前端·npm
理想不理想v2 小时前
‌Vue 3相比Vue 2的主要改进‌?
前端·javascript·vue.js·面试
酷酷的阿云2 小时前
不用ECharts!从0到1徒手撸一个Vue3柱状图
前端·javascript·vue.js