Vue.js学习笔记(五)抽奖组件封装——转盘抽奖

基于VUE2转盘组件的开发


前言

因为之前的转盘功能是图片做的,每次活动更新都要重做UI和前端,为了解决这一问题进行动态配置转盘组件开发,可以减少一些UI和前端的工作量。


一、开发步骤

1.组件布局

 <van-row class="container">
 	  <!-- turntableBox 为整个转盘容器,为正方形,大小由里面元素决定 -->
      <van-col span="24" class="turntableBox">
      	<!-- turntableMain 为转盘底座,比里面的内容大,显示为效果图灰色外圈,但不是空心圆 -->
        <div class="turntableMain" :style="`height:${window.innerWidth * 0.8}px;width:${window.innerWidth * 0.8}px;`">
         <!-- turntable 为转动区域,作用是为了不让外圈一起转动 -->
          <div ref="turntable" class="turntable"
            :style="`height:${window.innerWidth * 0.8}px;width:${window.innerWidth * 0.8}px;`">
           <!-- Canvas 转盘饼图背景,具体划分多少块由奖项决定 -->
            <Canvas />
            <!-- prizeBox 奖项,高为饼图的半径,宽为饼图半径里面有多少块就多少分之一 -->
            <div class="prizeBox">
              <div class="prizeItem" :style="`width:${perPrize.width}px;height:${perPrize.height}px;transform:translateX(-50%) rotate(-${(perPrize.degree * (index + 1)) - (perPrize.degree / 2)}deg);left:calc(50%)`"
                v-for="(item, index) in activeInfo.prizeList" :key="index">
                <p class="title">{{ item.name }}</p>
                <p class="describe">{{ item.describe }}</p>
                <img :src="item.img" style="width: 38%;" />
              </div>
            </div>
          </div>
          <!-- 启动按钮 -->
          <van-image class="go" fit="cover" width="42px" :src="goPointer" @click="go" />
        </div>
      </van-col>
      <!-- 结果展示列表 -->
      <van-col span="24">
        <div id="result"></div>
      </van-col>
    </van-row>

2.布局样式

.turntableBox {
 
  margin-top: 10%;

  .turntableMain {
    margin: 0 auto;
    position: relative;
    border: 10px solid #E5E5E5;
    border-radius: 100%;
  }

  .turntable {
    transition: all 4s;
    margin: 0 auto;
  }

  .go {
    position: absolute;
    top: calc(50% - 31px);
    left: calc(50% - 21px);
  }

  .prizeBox {
    position: absolute;
    width: 80%;
    top: 0;
    left: calc(50% - 40%);

    .prizeItem {
      text-align: center;
      position: absolute;
      top: 0;
      overflow: hidden;
      text-align: center;
      transform-origin: center bottom;
      transform: translateX(-50%);
      color: #2c3e50;

      p {
        margin: 0;
        padding: 0;
      }

      .title {
        font-size: 18px;
        margin-top: 12px;
      }

      .describe {
        font-size: 14px;
        line-height: 28px;
        white-space: break-spaces;
      }

      img {
        margin-top: 6px;
      }
    }
  }
}

3.数据准备

data 代码如下:包含页面功能所需要的变量

  data() {
    return {
      window,
       /** 活动设置 */
      activeInfo: {
        /** 中奖概率 */
        probabilities: {
          "一等奖": 10,
          "二等奖": 10,
          "三等奖": 10,
          "四等奖": 10,
        },
        /** 奖品信息 */
        prizeList: [
          {
            name: '一等奖',
            describe: '一等奖',
            img: 'https://img01.yzcdn.cn/vant/cat.jpeg'
          },
          {
            name: '未中奖',
            describe: '未中奖',
            img: 'https://img01.yzcdn.cn/vant/cat.jpeg'
          },
          {
            name: '二等奖',
            describe: '二等奖',
            img: 'https://img01.yzcdn.cn/vant/cat.jpeg'
          },
          {
            name: '未中奖',
            describe: '未中奖',
            img: 'https://img01.yzcdn.cn/vant/cat.jpeg'
          },
          {
            name: '三等奖',
            describe: '三等奖',
            img: 'https://img01.yzcdn.cn/vant/cat.jpeg'
          },
          {
            name: '四等奖',
            describe: '四等奖',
            img: 'https://img01.yzcdn.cn/vant/cat.jpeg'
          },

        ]
      },
      /** 是否正在执行动画 */
      isGo: false,
      /** 执行动画的对象 */
      oTurntable: '',
      /** 即将旋转的度数 */
      randomDeg: 0,
      /** 上一次旋转的度数 */
      lastDeg: 0,
      /** 抽奖次数 */
      goTimes: 3,
      /** 奖品图片 */
      perPrize: {
        degree: null,
        width: null,
        height: null
      }
     }
   } 

created 代码如下:主要处理角度、宽、高

  created() {
    const params = getAllParams();
    if (params) {
      this.params = params;
    };
    /** 奖品 */
    const angle = (360 / this.activeInfo.prizeList.length) / 2; // 对角角度
    const ratio = Number(Math.sin(angle * (Math.PI * 2 / 360)).toFixed(2)); // 与半径的比率
    this.perPrize = {
      degree: (360 / this.activeInfo.prizeList.length),
      width: Math.floor((window.innerWidth * ratio)) / 2,
      /** 高度是直径的一半 */
      height: window.innerWidth * 0.8 / 2
    }
  },

mounted 代码如下:获取转盘区域DOM元素,方便后面操作

  mounted() {
    this.oTurntable = this.$refs.turntable;
  },

methods 代码如下:主要操作方法

 /** 点击抽奖 */
    go() {
      /** 正在抽奖,未结束继续点击无效 */
      if (!this.isGo && this.goTimes > 0) {
      	/** 获取中奖结果,再根据结果去转动转盘 */
        const result = this.generatePrize();
        /** 
         * 获取奖项下标
         * 奖项名字可能会重复,所以需要找到奖项的所有下标保存到数组里
         * 根据下标数组随机生成一个数字来决定选择哪个下标成为最终结果的下标
         *  */
        const resultIndexArray = this.activeInfo.prizeList.reduce((acc, item, index) => {
          if (item.name === result) {
            acc.push(index);
          }
          return acc;
        }, []);
        const randomResultIndex = Math.floor(Math.random() * resultIndexArray.length);
        const index = resultIndexArray[randomResultIndex];
        /** 奖项总和数量 */
        const length = this.activeInfo.prizeList.length;
        /** 调用旋转方法 */
        this.ratating((360 / length * index) + (360 / length / 2), result);
      }
      else if (!this.isGo && this.goTimes <= 0) {
        this.$toast({
          message: '抱歉,您的抽奖次数用完了',
          duration: 3000,
        });
      }
      else {
        this.$toast('请勿重复点击')
        return
      }
    },
    /** 获取抽奖结果 */
    generatePrize() {
      /** 生成一个 0 到 99 之间的随机数 */
      const randomNum = Math.floor(Math.random() * 100);
      let cumulativeProbability = 0;
      /** 如果概率落在奖项范围内 */
      for (const prize in this.activeInfo.probabilities) {
        cumulativeProbability += this.activeInfo.probabilities[prize];
        if (randomNum < cumulativeProbability) {
          /** 返回中奖内容 */
          return prize;
        }
      }
      // 默认返回未中奖
      return "未中奖";
    },

    /** 该方法能产生[n,m]之间随机数,决定转盘转多少圈 */
    getRandom(n, m) {
      let result = Math.floor(Math.floor(Math.random() * (m - n + 1) + n))
      return result;
    },
    
    /** 旋转 */
    ratating(deg, text) {
      this.goTimes--;
      this.isGo = true;
      /** 旋转圈数 */
      let turnNumber = this.getRandom(3, 6);
      /** 记录这次要旋转的度数(传来的度数+圈数) */
      this.randomDeg = deg + 360 * turnNumber;
      /*上次指针离初始状态的度数 + 上次的度数 + 这次要旋转的度数
      (这样的目的是为了每次旋转都从原点开始,保证数据准确)*/
      let realDeg = (360 - this.lastDeg % 360) + this.lastDeg + this.randomDeg;
      /** 为对象添加执行动画 */
      this.oTurntable.style.transform = `rotate(${realDeg}deg)`;
      setTimeout(() => {
        this.isGo = false;
        var list = document.getElementById('result');
        list.innerHTML += /未中奖/.test(text) ? `<p>很遗憾,您${text}!</p>` : `<p>恭喜您,获得${text}!</p>`;
        /** 把这次度数存储起来,方便下一次获取 */
        this.lastDeg = realDeg;
      }, 4000);
    }

canvas 组件代码如下:主要使用canvas标签根据奖项长度进行角度划分绘画,


<template>
  <canvas class="canvas" id="canvasImg" :style="`width:${perimeter}px;height: ${perimeter}px;`">您的浏览器不支持canvas!</canvas>
</template>

<script>


export default {
  name: 'Canvas',
  components: {

  },
  data() {
    return {
      /** 直径 */
      perimeter: 320,
    }
  },
  created() {

  },
  mounted() {
    this.perimeter = window.innerWidth * 0.8;
    this.drawPie();
  },
  methods: {
    /** 画饼图 */
    drawPie() {
      const PI = Math.PI;
      /** 获取画布并获取2d上下文对象 */
      const canvas = document.getElementById('canvasImg');
      const ctx = canvas.getContext('2d');
      /** 假设周长为500 */
      const perimeter = this.perimeter;
      /** 半径 */
      const radius = perimeter * 0.5;
      /** 总奖品数,需要根据实际数据长度从父组件传入 */
      const prizeTotal = 6;
      /** 每个扇形的角度=360度 / 总奖品数 */
      const degree = 360 / prizeTotal;
      /** 画布宽高 */
      canvas.width = perimeter;
      canvas.height = perimeter;
      /** 根据奖品数把圆形分成等份的扇形 */
      for (let i = 0; i < prizeTotal; i++) {
        /** 奇偶颜色 */
        const color = i % 2 === 0 ? "#F8D383" : "#F8E2BC";
        /** 开始一条新路径 */
        ctx.beginPath();
        /** 设置路径起点 */
        ctx.moveTo(radius, radius);
        /** 填充颜色 */ 
        ctx.fillStyle = color;
        /** 绘制扇形 (圆心坐标,圆心坐标,半径,扇形起始角度,扇形终止角度) */
        ctx.arc(radius, radius, radius, (270 - degree  + (degree * i)) * PI / 180, (270 - degree  + degree + (degree * i)) * PI / 180);
        /** 自动绘制一条当前点到起点的直线,形成一个封闭图形,省却使用一次moveTo方法。 */
        ctx.closePath();
        /** 闭合路径 */
        ctx.fill();
      }
    }
  },
}
</script>

<style lang="less">

</style>

二、最后效果

在这里插入图片描述


总结

本文仅仅简单记录了转盘组件的基本实现,仅供学习参考。

相关推荐

最近更新

  1. docker php8.1+nginx base 镜像 dockerfile 配置

    2024-07-11 15:20:06       67 阅读
  2. Could not load dynamic library ‘cudart64_100.dll‘

    2024-07-11 15:20:06       72 阅读
  3. 在Django里面运行非项目文件

    2024-07-11 15:20:06       58 阅读
  4. Python语言-面向对象

    2024-07-11 15:20:06       69 阅读

热门阅读

  1. react获取访问过的路由历史记录

    2024-07-11 15:20:06       24 阅读
  2. 编程范式实现思路介绍

    2024-07-11 15:20:06       19 阅读
  3. 表单验证的艺术:WebKit 支持 HTML 表单的全面解析

    2024-07-11 15:20:06       19 阅读
  4. Android --- Kotlin学习之路:基础语法学习笔记

    2024-07-11 15:20:06       24 阅读
  5. 智能制造热点词汇科普篇——工业微服务

    2024-07-11 15:20:06       22 阅读
  6. C++中的模板(二)

    2024-07-11 15:20:06       21 阅读
  7. slf4j日志框架和logback详解

    2024-07-11 15:20:06       22 阅读
  8. Redis的配置和优化

    2024-07-11 15:20:06       22 阅读
  9. springboot 抽出多个接口中都有相同的代码的方法

    2024-07-11 15:20:06       23 阅读
  10. OpenJudge | 最高的分数

    2024-07-11 15:20:06       21 阅读
  11. springmvc 如何对接接口

    2024-07-11 15:20:06       23 阅读