Vue 項目中實現(xiàn)3D文字球的抽獎效果

今天突發(fā)奇想的想實現(xiàn)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">
                單次抽獎個數(shù)
                :<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">結(jié)束</button>
            </div>
        </div>
    </div>
</template>

js

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

export default {
    data () {
        return {
            listTxt: [], // 內(nèi)容
            itemBox: null, // 元素盒子
            itemList: null, // 元素集合
            temSize: [], // 每個元素寬高集合
            radius: 250, // 旋轉(zhuǎn)半徑
            active: false, // 鼠標(biāo)是否懸浮
            tspeed: 5, // 旋轉(zhuǎn)速度
            mouseX: 0, // 坐標(biāo)點x
            mouseY: 0, // 坐標(biāo)點y
            mouseX_move: 1,
            mouseY_move: 1,
            distr: true, //
            timer: null,
            isStart: false, // 是否開始抽獎
            isInit: true, // 是否是初始化
            defaultTimer: null, // 默認(rèn)自傳
            showRes: false,
            sltNum: 1,
            sltTxtList: []
        }
    },
    mounted () {
        this.init();
    },
    methods: {
        start () {
            this.showRes = false;
            if(this.isStart) return;
            // 開始抽獎時不啟動默認(rèn)旋轉(zhuǎn)
            if(this.defaultTimer){
                clearTimeout(this.defaultTimer);
                this.defaultTimer = null;
            }
            // 一般是第一次初始化完成或自動旋轉(zhuǎn)時
            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;
            // 元素個數(shù)
            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 );
            // 循環(huán)遍歷出每個元素的坐標(biāo) 和 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 );
                // }

                // 坐標(biāo)變換
                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';
                }
            }
        },
        // 鼠標(biāo)在元素移動事件, 計算出鼠標(biāo)最后消失的位置
        // 解開注釋可實現(xiàn)跟隨商標(biāo)方向滾動
        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;
        },
        // 滾動
        // 解開注釋可實現(xiàn)跟隨商標(biāo)方向滾動
        rolling () {
            let a, b, c = 0, size = 200, howElliptical = 1;
            // debugger
            // 只有初始化時才可以使用鼠標(biāo)控制,即抽獎時不可以
            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;
            }
            // 首次加載時默認(rèn)滾動
            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 );
            }
            // 結(jié)束抽獎
            else if ( !this.isStart ) {
                clearInterval( this.timer );
                this.timer = null;
                // 10s 后自動恢復(fù)至初始化狀態(tài)即默認(rèn)自動旋轉(zhuǎn)
                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();
        },
        // 轉(zhuǎn)動時重新計算定位
        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;
            }
        },
        // 轉(zhuǎn)動時重新計算 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 = '趙錢孫李周吳鄭王馮陳褚衛(wèi)蔣沈韓楊朱秦尤許何呂施張孔曹嚴(yán)華金魏陶姜戚謝鄒喻柏水竇章云蘇潘葛奚范彭郎魯韋昌馬苗鳳花方俞任袁柳酆鮑史唐費廉岑薛雷賀倪湯滕殷羅畢郝鄔安常樂于時傅皮卞齊康伍余元卜顧孟平黃和穆蕭尹姚邵湛汪祁毛禹狄米貝明臧計伏成戴談宋茅龐熊紀(jì)舒屈項祝董梁杜阮藍(lán)閔席季麻強賈路婁危江童顏郭梅盛林刁鐘徐邱駱高夏蔡田樊胡凌霍虞萬支柯昝管盧莫經(jīng)房裘繆干解應(yīng)宗丁宣賁鄧郁單杭洪包諸左石崔吉鈕龔程嵇邢滑裴陸榮翁荀羊於惠甄曲家封芮羿儲靳汲邴糜松井段富巫烏焦巴弓牧隗山谷車侯宓蓬全郗班仰秋仲伊宮寧仇欒暴甘鈄厲戎祖武符劉景詹束龍葉幸司韶郜黎薊薄印宿白懷蒲邰從鄂索咸籍賴卓藺屠蒙池喬陰鬱胥能蒼雙聞莘黨翟譚貢勞逄姬申扶堵冉宰酈雍郤璩桑桂濮牛壽通邊扈燕冀郟浦尚農(nóng)溫別莊晏柴瞿閻充慕連茹習(xí)宦艾魚容向古易慎戈廖庾終暨居衡步都耿滿弘匡國文寇廣祿闕東歐殳沃利蔚越夔隆師鞏厙聶晁勾敖融冷訾辛闞那簡饒空曾毋沙乜養(yǎng)鞠須豐巢關(guān)蒯相查后荊紅游竺權(quán)逯蓋益桓公';
            let isTrue = false, strTmp = '';
            this.listTxt = []
            // 填充內(nèi)容
            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( _ => {
                // 遍歷時獲得當(dāng)前項
                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();
                // 初始化時,默認(rèn)轉(zhuǎn)動
                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>

有些東西我也沒搞太明白,歡迎大家留言 ~~~

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

推薦閱讀更多精彩內(nèi)容