Vue 项目中实现3D文字球的抽奖效果

今天突发奇想的想实现3D球和抽奖效果,一不做二不休把他俩融合了,效果如下:

1644388259(1).png

1644388297(1).png

话不多说,上代码

html

<template>
    <div class="sd-ball-box">
        <ul class="item-box" @mousemove="mouseMove" @mouseover="active = true" @mouseout="active = false" ref="item_box_ref">
            <li v-for="(item, key) in listTxt" :key="key">{{item}}</li>
        </ul>
        <div class="handle-box" >
            <div class="cnt-box" :style="!showRes ? 'background: transparent;' : 'background: rgba(118,5,5, .9);'">
                <p v-if="showRes">中奖名单</p>
                <p v-if="showRes">
                    <span v-for="name in sltTxtList" :key="name">{{name}}</span>
                </p>
            </div>
            <div class="btn-box">
                单次抽奖个数
                :<select v-model="sltNum">
                    <option value="1">1</option>
                    <option value="2">2</option>
                    <option value="3">3</option>
                    <option value="5">5</option>
                    <option value="10">10</option>
                </select>
                <button @click="start">开始</button>
                <button @click="end">结束</button>
            </div>
        </div>
    </div>
</template>

js

<script>
let sa, ca, sb, cb, sc, cc, per;

export default {
    data () {
        return {
            listTxt: [], // 内容
            itemBox: null, // 元素盒子
            itemList: null, // 元素集合
            temSize: [], // 每个元素宽高集合
            radius: 250, // 旋转半径
            active: false, // 鼠标是否悬浮
            tspeed: 5, // 旋转速度
            mouseX: 0, // 坐标点x
            mouseY: 0, // 坐标点y
            mouseX_move: 1,
            mouseY_move: 1,
            distr: true, //
            timer: null,
            isStart: false, // 是否开始抽奖
            isInit: true, // 是否是初始化
            defaultTimer: null, // 默认自传
            showRes: false,
            sltNum: 1,
            sltTxtList: []
        }
    },
    mounted () {
        this.init();
    },
    methods: {
        start () {
            this.showRes = false;
            if(this.isStart) return;
            // 开始抽奖时不启动默认旋转
            if(this.defaultTimer){
                clearTimeout(this.defaultTimer);
                this.defaultTimer = null;
            }
            // 一般是第一次初始化完成或自动旋转时
            if ( this.timer ) {
                this.isInit = false;
                this.isStart = true;
            } else {
                // 计算正/余弦
                this.sineCosine( 0, 0, 0 );
                // 所有元素随机排序,并重新计算位置
                this.positionAll();
                this.timer = setInterval( this.rolling, 50 );
                this.isInit = false;
                this.isStart = true;
                console.log(333)
            }
        },
        end () {
            if(!this.isStart) return;
            let tmpList = [], isStop = false, randomIndex = Math.floor((Math.random()*this.listTxt.length));
            while(!isStop){
                randomIndex = Math.floor((Math.random()*this.listTxt.length));
                if(!tmpList.includes(this.listTxt[randomIndex])){
                    tmpList.push(this.listTxt[randomIndex])
                }
                isStop = tmpList.length == this.sltNum;
            }
            this.isStart = false;
            this.showRes = true;
            console.log( this.sltNum )
            // debugger
            this.sltTxtList = tmpList;
        },
        // 计算正/余弦
        sineCosine ( a, b, c ) {
            let dtr = Math.PI / 180; // 弧度
            sa = Math.sin( a * dtr );
            ca = Math.cos( a * dtr );
            sb = Math.sin( b * dtr );
            cb = Math.cos( b * dtr );
            sc = Math.sin( c * dtr );
            cc = Math.cos( c * dtr );
        },
        // 所有元素随机排序,并重新计算位置
        positionAll () {
            var phi = 0, theta = 0, itemTmpSize = null;
            // 元素个数
            var max = this.temSize.length;
            // 元素临时存放,用于随机排序
            var itemTmpList = Array.from( this.itemList );
            // 文档片段
            var oFragment = document.createDocumentFragment();
            //随机排序
            itemTmpList.sort( () => {
                return Math.random() < 0.5 ? 1 : -1;
            } );
            // 遍历元素,逐个插入文档片段集合
            for ( let i = 0; i < itemTmpList.length; i++ ) {
                oFragment.appendChild( itemTmpList[i] );
            }
            // 把文档片段集合插入元素盒子尾部
            this.itemBox.appendChild( oFragment );
            // 循环遍历出每个元素的坐标 和 left top
            for ( let i = 1; i < max + 1; i++ ) {
                // if ( this.distr ) {
                phi = Math.acos( -1 + ( 2 * i - 1 ) / max );
                theta = Math.sqrt( max * Math.PI ) * phi;
                // }
                // else {
                //     console.log(4444)
                //     phi = Math.random() * ( Math.PI );
                //     theta = Math.random() * ( 2 * Math.PI );
                // }

                // 坐标变换
                itemTmpSize = this.temSize[i - 1];

                itemTmpSize.cx = this.radius * Math.cos( theta ) * Math.sin( phi );
                itemTmpSize.cy = this.radius * Math.sin( theta ) * Math.sin( phi );
                itemTmpSize.cz = this.radius * Math.cos( phi );

                this.itemList[i - 1].style.left = itemTmpSize.cx + this.itemBox.offsetWidth / 2 - itemTmpSize.offsetWidth / 2 + 'px';
                this.itemList[i - 1].style.top = itemTmpSize.cy + this.itemBox.offsetHeight / 2 - itemTmpSize.offsetHeight / 2 + 'px';
                this.itemList[i - 1].style.color = '#f5b16d';
                if(Math.random() > .5){
                    this.itemList[i - 1].style.color = '#ff6060';
                }
            }
        },
        // 鼠标在元素移动事件, 计算出鼠标最后消失的位置
        // 解开注释可实现跟随商标方向滚动
        mouseMove ( ev ) {
            let oEvent = window.event || ev;

            this.mouseX = oEvent.clientX - ( this.itemBox.offsetLeft + this.itemBox.offsetWidth / 2 );
            this.mouseY = oEvent.clientY - ( this.itemBox.offsetTop + this.itemBox.offsetHeight / 2 );

            this.mouseX /= 5;
            this.mouseY /= 5;
        },
        // 滚动
        // 解开注释可实现跟随商标方向滚动
        rolling () {
            let a, b, c = 0, size = 200, howElliptical = 1;
            // debugger
            // 只有初始化时才可以使用鼠标控制,即抽奖时不可以
            if ( this.active && !this.isStart ) {
                a = ( -Math.min( Math.max( -this.mouseY, -size ), size ) / this.radius ) * this.tspeed;
                b = ( Math.min( Math.max( -this.mouseX, -size ), size ) / this.radius ) * this.tspeed;
            }
            // 首次加载时默认滚动
            else if ( this.isInit ) {
                a = this.mouseX_move * ( 1 ); // 小于 1 会慢慢停止
                b = this.mouseY_move * ( 1 ); // 小于 1 会慢慢停止
            }
            // 开始抽奖
            else if ( !this.isInit && this.isStart ) {
                a = this.mouseX_move * ( 1.10 );
                b = this.mouseY_move * ( 1.25 );
            }
            // 结束抽奖
            else if ( !this.isStart ) {
                clearInterval( this.timer );
                this.timer = null;
                // 10s 后自动恢复至初始化状态即默认自动旋转
                this.defaultTimer = setTimeout( () => {
                    // 计算正/余弦
                    this.sineCosine( 1, 0, 0 );
                    // 所有元素随机排序,并重新计算位置
                    this.positionAll();
                    this.isInit = true;
                    this.isStart = false;
                    this.timer = setInterval( this.rolling, 50 );
                    clearTimeout(this.defaultTimer);
                    this.defaultTimer = null;
                }, 5000 );
            }
            this.mouseX_move = a || 1;
            this.mouseY_move = b || 1;
            if ( Math.abs( a ) <= 0.01 && Math.abs( b ) <= 0.01 ) {
                clearInterval( this.timer );
                this.timer = null;
                return;
            }
            // 重新计算正/余弦
            this.sineCosine( a, b, c );
            for ( let j = 0; j < this.temSize.length; j++ ) {
                let rx1 = this.temSize[j].cx,
                    ry1 = this.temSize[j].cy * ca + this.temSize[j].cz * ( -sa ),
                    rz1 = this.temSize[j].cy * sa + this.temSize[j].cz * ca;

                let rx2 = rx1 * cb + rz1 * sb,
                    ry2 = ry1,
                    rz2 = rx1 * ( -sb ) + rz1 * cb;

                let rx3 = rx2 * cc + ry2 * ( -sc ),
                    ry3 = rx2 * sc + ry2 * cc,
                    rz3 = rz2;

                this.temSize[j].cx = rx3;
                this.temSize[j].cy = ry3;
                this.temSize[j].cz = rz3;

                // 计算透视距离
                per = 330 / ( 330 + rz3 );

                this.temSize[j].x = ( howElliptical * rx3 * per ) - ( howElliptical * 2 );
                this.temSize[j].y = ry3 * per;
                this.temSize[j].scale = per;
                this.temSize[j].alpha = per;
                this.temSize[j].alpha = ( this.temSize[j].alpha - .6 ) * ( 10 / 6 );
            }

            this.doPosition();
            this.depthSort();
        },
        // 转动时重新计算定位
        doPosition () {
            let lf = this.itemBox.offsetWidth / 2, tp = this.itemBox.offsetHeight / 2, itemListTmp = null;
            for ( let i = 0; i < this.temSize.length; i++ ) {
                itemListTmp = this.itemList[i];

                itemListTmp.style.left = this.temSize[i].cx + lf - this.temSize[i].offsetWidth / 2 + 'px';
                itemListTmp.style.top = this.temSize[i].cy + tp - this.temSize[i].offsetHeight / 2 + 'px';
                itemListTmp.style.fontSize = Math.ceil( 12 * this.temSize[i].scale / 2 ) + 8 + 'px';
                itemListTmp.style.filter = "alpha(opacity=" + 100 * this.temSize[i].alpha + ")";
                itemListTmp.style.opacity = this.temSize[i].alpha;
            }
        },
        // 转动时重新计算 zIndex
        depthSort () {
            let aTmp = [];
            for ( let i = 0; i < this.itemList.length; i++ ) {
                aTmp.push( this.itemList[i] );
            }

            aTmp.sort( ( vItem1, vItem2 ) => {
                if ( vItem1.cz > vItem2.cz ) {
                    return -1;
                }
                else if ( vItem1.cz < vItem2.cz ) {
                    return 1;
                }
                else {
                    return 0;
                }
            } );

            for ( let i = 0; i < aTmp.length; i++ ) {
                aTmp[i].style.zIndex = i;
            }
        },
        init () {
            const baiJiaXing = '赵钱孙李周吴郑王冯陈褚卫蒋沈韩杨朱秦尤许何吕施张孔曹严华金魏陶姜戚谢邹喻柏水窦章云苏潘葛奚范彭郎鲁韦昌马苗凤花方俞任袁柳酆鲍史唐费廉岑薛雷贺倪汤滕殷罗毕郝邬安常乐于时傅皮卞齐康伍余元卜顾孟平黄和穆萧尹姚邵湛汪祁毛禹狄米贝明臧计伏成戴谈宋茅庞熊纪舒屈项祝董梁杜阮蓝闵席季麻强贾路娄危江童颜郭梅盛林刁钟徐邱骆高夏蔡田樊胡凌霍虞万支柯昝管卢莫经房裘缪干解应宗丁宣贲邓郁单杭洪包诸左石崔吉钮龚程嵇邢滑裴陆荣翁荀羊於惠甄曲家封芮羿储靳汲邴糜松井段富巫乌焦巴弓牧隗山谷车侯宓蓬全郗班仰秋仲伊宫宁仇栾暴甘钭厉戎祖武符刘景詹束龙叶幸司韶郜黎蓟薄印宿白怀蒲邰从鄂索咸籍赖卓蔺屠蒙池乔阴鬱胥能苍双闻莘党翟谭贡劳逄姬申扶堵冉宰郦雍郤璩桑桂濮牛寿通边扈燕冀郏浦尚农温别庄晏柴瞿阎充慕连茹习宦艾鱼容向古易慎戈廖庾终暨居衡步都耿满弘匡国文寇广禄阙东欧殳沃利蔚越夔隆师巩厍聂晁勾敖融冷訾辛阚那简饶空曾毋沙乜养鞠须丰巢关蒯相查后荆红游竺权逯盖益桓公';
            let isTrue = false, strTmp = '';
            this.listTxt = []
            // 填充内容
            for ( let i of baiJiaXing ) {
                if ( ( isTrue && strTmp.length == 3 ) || ( !isTrue && strTmp.length == 2 ) ) {
                    this.listTxt.push( strTmp );
                    strTmp = '';
                    isTrue = Math.random() > .5;
                }
                else {
                    strTmp += i;
                }
            }
            this.$nextTick( _ => {
                // 遍历时获得当前项
                let oTag = null;
                // 获取 包裹所有元素的 盒子
                this.itemBox = this.$refs.item_box_ref;
                // 获取 盒子里所有 元素
                this.itemList = this.itemBox.getElementsByTagName( 'li' );
                for ( let i = 0; i < this.itemList.length; i++ ) {
                    oTag = {};
                    oTag.offsetWidth = this.itemList[i].offsetWidth;
                    oTag.offsetHeight = this.itemList[i].offsetHeight;
                    this.temSize.push( oTag );
                }
                // 计算正/余弦
                this.sineCosine( 0, 0, 0 );
                // 所有元素随机排序,并重新计算位置
                this.positionAll();
                // 初始化时,默认转动
                this.timer = setInterval( this.rolling, 50 );
            } )
        }
    }
}
</script>

css

<style lang="scss" scoped>
.sd-ball-box {
     width: 650px;
    height: 600px;
    display: flex;
    justify-content: center;
    align-items: center;
    position: relative;
    margin: 0 auto;
}
.item-box {
    width: 650px;
    height: 600px;
    background: #000;
    list-style: none;
    position: relative;
    li {
        position: absolute;
        top: 50%;
        left: 50%;
        padding: 3px 6px;
        // transition: all .1s;
        transform: translate(-10%, -10%);
        font-family: Microsoft YaHei;
        font-weight: bold;
        // color: #f5b16d; // #84a4fd #f7bd82
    }
}
.handle-box {
    position: absolute;
    left: 50px;
    top: 38px;
    width: 550px;
    display: flex;
    flex-direction: column;
    justify-content: space-between;
    z-index: 99999;
    // 中奖名单
    .cnt-box {
        height: 450px;
        text-align: center;
        padding: 50px 125px 20px;
        border-radius: 8px;
        p:first-child{
            font-size: 55px;
            font-weight: bold;
            color: #ffd300;
            text-shadow:#000 10px 5px 5px;
            letter-spacing: 10px;
        }
        p:last-child {
            padding-top: 50px;
            letter-spacing: 2px;
            span {
                display: inline-block;
                width: 100px;
                padding-top: 18px;
                font-size: 26px;
                // font-weight: bold;
                color: yellow;
                text-shadow:#000 5px 3px 5px;
                // text-shadow: #84a4fd 1px 0 0, #84a4fd 0 1px 0, #84a4fd -1px 0 0, #84a4fd 0 -1px 0;
            }
        }
    }
    .btn-box {
        padding: 20px;
        display: flex;
        justify-content: center;
        align-items: center;
        position: relative;
        bottom: -56px;
        select {
            width: 66px;
            height: 26px;
            margin-left: 20px;
        }
        button {
            outline: none;
            border: none;
            padding: 5px 15px;
            margin-left: 30px;
            cursor: pointer;
        }
    }
}
</style>

有些东西我也没搞太明白,欢迎大家留言 ~~~

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 201,312评论 5 473
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 84,578评论 2 377
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 148,337评论 0 333
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,134评论 1 272
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,161评论 5 363
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,303评论 1 280
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,761评论 3 393
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,421评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,609评论 1 295
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,450评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,504评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,194评论 3 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,760评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,836评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,066评论 1 257
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,612评论 2 348
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,178评论 2 341

推荐阅读更多精彩内容