JavaScript 转盘类型抽奖代码

实际效果

效果图,GIF帧数较低,凑合着看吧

一些变量

const config = {
  wheel: '/image/wheel.png', // 转盘图片
  icon: '/image/icon.png', // 中心 LOGO
  arrow: '/image/arrow.png', // 顶部剪头
  round_time: 5000, // 获取到奖品后转盘旋转动画时间
  text_color: '#000', // 文字颜色
  // 奖品列表
  prizeList: [
    { id: 1, text: '1华为P40', img: '/image/prize1.png' },
    { id: 2, text: '2谢谢参与', img: '/image/prize2.png' },
    { id: 3, text: '3华为P40', img: '/image/prize1.png' },
    { id: 4, text: '4谢谢参与', img: '/image/prize2.png' },
    { id: 5, text: '5华为P40', img: '/image/prize1.png' },
    { id: 6, text: '6谢谢参与', img: '/image/prize2.png' },
    { id: 7, text: '7华为P40', img: '/image/prize1.png' },
    { id: 8, text: '8谢谢参与', img: '/image/prize2.png' },
  ]
};

HTML

<div class="content">
    <div class="wheel">
        <div class="round" :class="{'not_roting' : roting}"
            :style="{ transition: `${config.round_time}ms`, transform: `rotate(${deg}deg)` }">
            <!-- 转盘图片 -->
            <img :src="config.wheel" alt="" />
            <div class="prize">
            	<!-- 奖品,宽高均意志,重叠在一起,以转盘中心为原点旋转 360/奖品个数 的角度 -->
            	<!-- 这边使用了 v-for, react 使用 map, 原生或 jQuery 需要在 js 中遍历创建完 DOM 元素再插入 -->
                <div v-for="(item, index) in config.prizeList" class="item_box" :key="index.toString()"
                    :style="{color: config.text_color, transform: `rotate(${(360 / config.prizeList.length) * index}deg)`}">
                    <div class="item">
                        <span class="text">{{ item.text }}</span>
                        <img class="img" :src="item.img" :alt="item.text" />
                    </div>
                </div>
            </div>
        </div>
        <!-- 中部LOGO -->
        <div class="icon">
            <img :src="config.icon" alt="" />
        </div>
        <div class="" arrow>
            <img :src="config.arrow" alt="" />
        </div>
    </div>
    <div class="btn">
        <el-button size="large" type="primary" v-loading="loading" @click="clickBtn">
            立即抽奖
        </el-button>
    </div>
</div>

CSS

.content {
  width: 500px; /* 移动端,限制最大宽度 */
  max-width: 100%;
  margin: auto;
  overflow-x: hidden;

  .wheel {
    position: relative;
    width: 90%;
    /* margin、padding的百分比基于宽度,在使用rem的情况下,高度和宽度保持比例可以用padding来撑起 */
    padding-top: 90%;
    margin: calc(100 / 750 * 100%) auto auto;
    overflow: hidden;

    .round {
      position: absolute;
      top: 0;
      left: 0;
      width: 100%;
      height: 100%;

      /* 抽完奖后复位时,旋转不需要过渡时间 */
      &.not_roting { transition: 0s !important; }

      .prize {
        position: absolute;
        top: 0;
        left: 0;
        width: 100%;
        height: 100%;

        .item_box {
          position: absolute;
          top: 0;
          left: 0;
          width: 100%;
          height: 100%;

          .item {
            position: absolute;
            top: calc(85 / 675 * 100%);
            left: calc((1 - 145 / 675) * 100% * 0.5);
            display: flex;
            flex-direction: column;
            justify-content: center;
            align-items: center;
            width: calc(145 / 675 * 100%);

            .text {
              font-size: calc(100vw * 25 / 750);
              font-weight: bold;
              line-height: 1;

              @media (min-width: 500px) { font-size: 17px; }
            }
            .img { width: calc(90 / 145 * 100%); margin-top: calc(30 / 145 * 100%); }
          }
        }
      }
    }
    /* LOGO 居中 */
    .icon { position: absolute; top: 50%; left: 50%; width: calc(155 / 675 * 100%); transform: translate(-50%, -50%); }
    /* 箭头顶部居中 */
    .arrow { position: absolute; top: calc(28 / 675 * 100%); left: 50%; width: calc(55 / 675 * 100%); transform: translateX(-50%); }
  }

  .btn {
    width: 100%;
    margin-top: calc(80 / 750 * 100%);
    >button { display: block; width: calc(650 / 750 * 100%); margin: auto; }
  }
}

JavaScript

  // 开始抽奖
  clickBtn() {
    if (this.roting || this.loading) return
    this.loading = true
    // 前端生成随机数奖品
    setTimeout(() => {
      const config = this.config
      const activePrize = config.prizeList[Math.floor(Math.random() * 7)]
      // 使用 some 循环,获取到奖品即停止遍历
      config.prizeList.some((item, index) => {
        if (item.id === activePrize.id) {
          // 获取该奖品的一个随机角度,具体见下方函数注释
          const newDeg = this.getRote(index, config.prizeList.length)
          // roting 为 true 时,转盘有旋转动画,时间为 config.round_time
          this.roting = true
          this.deg = newDeg
          setTimeout(() => {
            // 经过 config.round_time 的时间后,转盘进行复位,方便下一次抽取,deg 不取 0 而是 NewDeg % 360 以确保用户看不到转盘闪动
            this.roting = false
            this.deg = NewDeg % 360
          }, config.round_time)
          return true
        }
      })
      this.loading = false
    }, 1000);
  },
  /**
   * 根据奖品获取转盘角度
   * @param {number} index 获得奖品在 prizeList 中的 index
   * @param {number} count 奖品数量
   * @return {number}
   */
  getRote(index, count) {
    if (!count) count = 8; // 若不传奖品数量,则默认为8
    const MAX_ROUND = 10; // 最大转圈数
    const MIN_ROUND = 8; // 最小转圈数
    const OFFSET_MULTIPLE = 0.6; // 偏移量倍数,范围 [0, 1),例如4个奖品时,偏移量是[-45, 45),若倍数为 1,则可能出现贴边的情况
    const unit = 360 / count; // 单元角度,为360 ÷ 奖品总数
    const offset = Math.floor(Math.random() * -unit) + unit / 2;
    const newDeg = 360 * Math.floor(Math.random() * (MAX_ROUND - MIN_ROUND + 1) + MIN_ROUND) - unit * index;
    return newDeg + OFFSET_MULTIPLE * offset;
  };

源码

https://gitee.com/Blade_gen/vite_vue_base/blob/master/src/views/LuckDraw/index.vue

  • 3
    点赞
  • 26
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Blade_gen

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值