今天突发奇想的想实现3D球和抽奖效果,一不做二不休把他俩融合了,效果如下:
话不多说,上代码
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>
有些东西我也没搞太明白,欢迎大家留言 ~~~