怎么写一个大转盘抽奖

Posted by CodingWithAlice on February 3, 2021

怎么写一个大转盘抽奖

方案一依赖于前后端数据的一致性,如果奖品池的商品有变化,需要 UI + 前端 + 后端 整条链路都进行后续修改,强力推荐方案二

方案一:UI给定背景图,和后端约定id

html 部分:转盘背景需要用 rotateStyle 变量的变化来实现转动

<div class="circle-wrap">
    <div class="circle" :style="rotateStyle"></div>
    <img @click="beginRotate" class="click-btn" src="http://h0.beicdn.com/open202105/2d87ca2b371447ea_250x250.png" />
</div>

computed 部分:利用计算属性,将rotateStyle 变量的变化转换为 this.rotateAngle 的改变,并给出默认配置项

computed: {
    rotateStyle() {
        return `-webkit-transition: transform ${this.config.duration}ms ${this.config.mode};
        transition: transform ${this.config.duration}ms ${this.config.mode};
        -webkit-transform: rotate(${this.rotateAngle}deg);
        transform: rotate(${this.rotateAngle}deg);
        background: url(${this.config.circleImg}) no-repeat;
        background-size: 100% 100%;`;
    },
},
const config = {
    // 总旋转时间
    duration: 3000,
    // 旋转圈数
    circle: 6,
    mode: 'ease-in-out',
    circleImg: 'http://h0.beicdn.com/open202105/653083f5e0971dc3_750x722.png'
},
const CIRCLE_ANGLE = 360;

其中 this.circleImg 如下图,奖励背景图 image-20210204212515785

js 计算每个商品需要旋转的角度

const list = [1, 6, 7, 4, 5, 3, 9, 11, 2, 10, 8]; // 对应于UI写死的转盘奖品【逆时针】

// 格式化奖品列表,计算每个奖品的位置
formatPrizeList(list) {
    // 记录每个奖的位置
    const angleList = [];

    const len = list.length;
    // 计算单个奖项所占的角度
    const average = CIRCLE_ANGLE / len;

    list.forEach((item, i) => {
        // 记录每个奖项的角度范围
        angleList[item] = (i * average);
    });

    this.angleList = angleList;

    return list;
},

js 点击抽奖按钮->调用后端接口->获取抽中的商品的 index->开始旋转

beginRotate() {
    // 避免重复调用接口,并且转盘转完才可以调用下一次接口
    if (this.isLoading || this.isRotating) {
        return;
    }
    this.isLoading = true;
    api.getGameReward({
        act_id: this.actId
    }).then((res) => {
        this.isLoading = false;
        if (res.success && res.data && res.data.is_drawed) {
            // 刷新抽奖次数
            this.timesNum = res.data.chance || 0;
            // 接口若不返回指定奖品,则取奖品1
            this.index = res.data..index || 1;
            if (this.index) {
                // 开始旋转
                this.rotating();
                return;
            }
            return;
        }
        note(res.message || '请稍后再试~');
    })
        .catch((err) => {
            note(err.message || '请稍后再试~');
        });
},

js 旋转修改rotateAngle触发computed方法,修改转盘旋转角度

// 旋转
rotating() {
    const {
        isRotating, angleList, config, rotateAngle, index,
    } = this;
    if (isRotating) {
        return;
    }

    this.isRotating = true;
    const len = this.prizeList.length;
    const average = CIRCLE_ANGLE / len;

    // 计算角度
    const angle =
        // 初始角度
        rotateAngle +
        // 多旋转的圈数
        (config.circle * CIRCLE_ANGLE) +
        // 奖项的角度
        angleList[index] -
        // 减去上一次转动的角度净值
        (rotateAngle % CIRCLE_ANGLE);

    this.rotateAngle = angle;
    // 旋转结束后,允许再次触发
    setTimeout(() => {
        this.rotateOver();
    }, config.duration + 500);
},

js 旋转结束的时候需要展示中奖的弹窗

 // 旋转结束
rotateOver() {
    this.isRotating = false;
    // 展示中奖弹窗
    this.showRewardDialog = true;
},

方案二:后端给定奖品列表【推荐】

由后端传回抽奖商品的列表,在模版遍历展示商品及样式,样式如下 image-20210204212531700

html 模版部分,主要是遍历展示奖品

<div class="wheel-main">
    <div class="wheel-bg" :style="rotateStyle">
        <div class="prize-list">
            // 循环遍历展示不同的奖品
            <div
                class="prize-item"
                v-for="(item, index) in prizeList"
                :key="index"
                :style="item.style"
            >
                <div class="prize-name"></div>
                <img class="prize-img" :src="item.img"/>
            </div>
            // 循环展示划分各奖品的划分线
            <img class="item-line" 
                v-for="(item, index) in prizeList"
                :key="index"
                :style="item.lineStyle"
                src="config.lineImg">
        </div>
    </div>
</div>

computed部分,需要计算的仍旧是中奖后的角度;给出划分奖品的分割线链接

computed: {
    rotateStyle() {
        return `-webkit-transition: transform ${this.config.duration}ms ${this.config.mode};
        transition: transform ${this.config.duration}ms ${this.config.mode};
        -webkit-transform: rotate(${this.rotateAngle}deg);
        transform: rotate(${this.rotateAngle}deg);`;
    },
},
const config = {
    lineImg: 'http://h0.beicdn.com/open202018/6e9275e324f838e1_19x235.png'
}

js 计算每个商品需要旋转的角度 – 和方案一不同的是:需要多计算每个奖品和分割线的旋转角度

// 格式化奖品列表,计算每个奖品的位置
formatPrizeList(list) {
    // 记录每个奖的位置
    const angleList = [];
    // 计算单个奖项所占的角度
    const len = list.length;
    const average = CIRCLE_ANGLE / len;
    const half = average / 2;

    // 循环计算给每个奖项添加style属性
    list.forEach((item, i) => {
        // 每个奖项旋转的位置为 当前 i * 平均值 + 平均值 / 2
        const angle = -((i * average) + half) + half;
        // 增加 style - 奖品+分割线
        item.style = `-webkit-transform: rotate(${angle}deg);
            transform: rotate(${angle}deg);`;
        item.lineStyle = `-webkit-transform: rotate(${angle + half}deg);
            transform: rotate(${angle + half}deg);`;

        // 记录每个奖项的角度范围
        angleList.push((i * average) + half);
    });

    this.angleList = angleList;
    return list;
},

js 点击抽奖按钮->调用后端接口->获取抽中的商品的 index->开始旋转 这里的逻辑和上述一致