vue3手写抽奖转盘

news/2024/7/10 0:31:28 标签: javascript, 前端, html, vue
htmledit_views">

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

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

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

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

一、先看具体实现效果:

二,具体实现

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

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

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

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

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

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

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

html" title=javascript>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>


http://www.niftyadmin.cn/n/5451759.html

相关文章

JavaParser 手动安装和配置

目录 前言 一、安装 Maven 工具 1.1 Maven 软件的下载 1.2 Maven 软件的安装 1.3 Maven 环境变量配置 1.4 通过命令检查 Maven 版本 二、配置 Maven 仓库 2.1 修改仓库目录 2.2 添加国内镜像 三、从 Github 下载 JavaParser 3.1 下载并解压 JavaParser 3.2 从路径打…

docker的安装和镜像的拉取

一、 如果自己以前安装的docker有残留&#xff0c;不想要了&#xff0c;可以使用下面命令卸载&#xff1a; yum remove docker \docker-client \docker-client-latest \docker-common \docker-latest \docker-latest-logrotate \docker-logrotate \docker-selinux \docker-en…

【C++】隐藏的this指针

文章目录 1.this指针的引出2.this指针的特性 1.this指针的引出 我们通过日期类来学习this指针&#xff0c;首先我们先定义一个日期类。 class Date { public:void Display(){cout << _year << "-" << _month << "-" << _d…

SGE 如何影响 SEO?

虽然谷歌的 “Search Generative Experience”&#xff08;SGE&#xff09;并不保证一定会推出&#xff08;谷歌以其废弃项目的坟场而闻名&#xff09;&#xff0c;但 SEO 人员不能忽视它&#xff0c;因为它预计会对有机搜索产生负面影响&#xff1a; 可见性流量转化率收入 S…

基于Java的超市进销存系统

开发语言&#xff1a;Java 框架&#xff1a;springboot JDK版本&#xff1a;JDK1.8 服务器&#xff1a;tomcat7 数据库&#xff1a;mysql 5.7 数据库工具&#xff1a;Navicat11 开发软件&#xff1a;eclipse/myeclipse/idea Maven包&#xff1a;Maven3.3.9 浏览器&…

自定义图像增强工具包

文章目录 前言使用方法文件夹结构运行 可用功能 前言 每次都得按照新的图像创建图像增强代码&#xff0c;显得太麻烦了。于是自己写了这样一个包。开源地址&#xff1a;GitHub地址 使用方法 其实在REAMDE里面已经写得很清楚了。主要还是因为在Windows里面路径跟编码的问题没…

一文看懂算法交易(二)

国内T0算法哪家强&#xff1f;算法交易费用是多少&#xff1f;算法交易哪些平台好&#xff1f; 我们接上文今天给大家继续分享&#xff0c;我们昨天大致了解了什么是算法交易&#xff0c;国内的算法总线大概有些啥&#xff0c;算法交易的类型。那么我们今天再普及下&#xff0c…

【力扣】392.判断子序列

题目描述 给定字符串 s 和 t &#xff0c;判断 s 是否为 t 的子序列。 字符串的一个子序列是原始字符串删除一些&#xff08;也可以不删除&#xff09;字符而不改变剩余字符相对位置形成的新字符串。&#xff08;例如&#xff0c;"ace"是"abcde"的一个子…