主要描述的是如何運用 css 繪制一個抽獎轉(zhuǎn)盤帅戒,并運用原生 js 實現(xiàn)轉(zhuǎn)盤抽獎效果。
先來張效果圖:
布局
一般來說昧港,轉(zhuǎn)盤一般有四個部分組成:外層閃爍的燈擎椰、內(nèi)層旋轉(zhuǎn)的圓盤、圓盤上的中獎結(jié)果创肥、指針达舒。
所以html的結(jié)構(gòu)如下:
<div class="turntable-wrap">
<div class="light" id="turntable_light"></div>
<div class="turntable" id="turntable">
<ul class="bg" id="turntable_bg"></ul>
<ul class="gift" id="turntable_gift"></ul>
</div>
<div class="pointer disabled" id="turntable_pointer">點擊抽獎</div>
</div>
其中燈需要一直閃爍值朋,而抽獎的時候轉(zhuǎn)盤需要轉(zhuǎn)動連帶其中的中獎結(jié)果,而轉(zhuǎn)盤指針不需要轉(zhuǎn)動是固定的休弃。因此 light 吞歼、 turntable 、 pointer放在同一級別塔猾。
外層容器利用border-radius:50%
實現(xiàn)從正方形變圓形的效果篙骡。border: 7px solid #b2a98d
加個邊框,box-shadow: 0 0 20px #b2a98d
加點陰影丈甸,美化一下糯俗。
.turntable-wrap {
position: relative;
overflow: hidden;
margin: 50px;
width: 340px;
height: 340px;
border: 7px solid #b2a98d;
border-radius: 50%;
box-shadow: 0 0 20px #b2a98d;
}
1. 閃爍的燈
- 利用 js 動態(tài)的給 light 生成多個小圓點,lightNum = 18睦擂,感覺這個數(shù)字得湘,轉(zhuǎn)盤每小塊顯示 3 個燈,感覺效果最好
// 初始化燈
let lightFragment = document.createDocumentFragment();
for (let i = 0; i < this.lightNum; i++) {
let lightItem = document.createElement('span');
let deg = (360 / this.lightNum) * i
lightItem.style.transform = `rotate(${deg}deg)`;
lightFragment.appendChild(lightItem);
}
this.light.appendChild(lightFragment);
- 接下來就是如何讓這些小圓點顿仇,顯示在“正確的位置上”
透露一下淘正,主要利用的是transform:rotate()
、transform-origin: center center
這兩個屬性臼闻。
給 light 寬鸿吆、高和外層容器一致,里面添加元素述呐,而元素的寬度給和小圓點一致的寬度惩淳,而這些元素旋轉(zhuǎn)一定的角度((360 / this.lightNum) * i
),而它們的旋轉(zhuǎn)中心設(shè)置在中心點乓搬。達(dá)到的效果就是這樣的:
接下來就是繪制小圓點了思犁,利用:before
屬性,將小圓點繪制到頂部进肯。而閃爍的燈激蹲,有兩種顏色,再利用:nth-type-of(event/odd)
實現(xiàn)江掩。最后就是閃爍了托呕,利用的是@keyframes
、animation
频敛。
最終實現(xiàn):
@keyframes white-to-yellow {
0% {
background: #fff;
}
100% {
background: #d7a945;
}
}
.turntable-wrap .light {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: #e0ddd1;
animation: rotate 5s linear infinite;
}
.turntable-wrap .light span {
position: absolute;
top: 0;
left: 0;
right: 0;
margin: 0 auto;
width: 10px;
height: 100%;
transform-origin: center center;
}
.turntable-wrap .light span:before {
content: '';
position: absolute;
top: 5px;
left: 0;
right: 0;
margin: 0 auto;
width: 10px;
height: 10px;
border-radius: 50%;
}
.turntable-wrap .light span:nth-of-type(even):before {
background: #fff;
animation: white-to-yellow 1s linear infinite;
}
.turntable-wrap .light span:nth-of-type(odd):before {
background: #d7a945;
animation: white-to-yellow 1s linear reverse infinite;
}
轉(zhuǎn)盤的背景
轉(zhuǎn)盤的背景的布局原理和燈的布局原理是一致的项郊。就不重復(fù)了。itemNum = 6
斟赚,表示轉(zhuǎn)盤分6塊着降。
// 初始化轉(zhuǎn)盤背景
let bgFragment = document.createDocumentFragment();
for (let i = 0; i < this.itemNum; i++) {
let bgItem = document.createElement('li');
let deg = (360 / this.itemNum) * i
bgItem.style.transform = `rotate(${deg}deg)`;
bgFragment.appendChild(bgItem);
}
this.bg.appendChild(bgFragment);
主要的 css 如下:
.turntable-wrap .turntable .bg {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: #fff;
border: 1px solid #dfd8be;
border-radius: 50%;
transform: rotate(90deg);
}
.turntable-wrap .turntable .bg li {
position: absolute;
top: 0;
left: 0;
right: 0;
margin: 0 auto;
width: 1px;
height: 100%;
background: #dfd8be;
transform-origin: center center;
}
效果圖:
轉(zhuǎn)盤的中獎圖
利用的也是transform:rotate()
、transform-origin
屬性拗军,達(dá)到旋轉(zhuǎn)布局的任洞。但是又有一點點不一樣蓄喇。
為了讓中獎圖顯示在轉(zhuǎn)盤每個塊的正中間
所以每個塊是按transorm-origin:right bottom
這個方向旋轉(zhuǎn)角度,而外層旋轉(zhuǎn)transform: rotate(45deg)
交掏,里面的每個小圖旋轉(zhuǎn)transform: rotate(-45deg)
再糾正過來妆偏。
ps:其實按照燈的transorm-origin:center center
應(yīng)該也能實現(xiàn),但開始想到的就是這種方案盅弛。
js 初始化塊的元素代碼如下:
// 初始化轉(zhuǎn)盤上的中獎圖
let giftFragment = document.createDocumentFragment();
for (let i = 0; i < this.itemNum; i++) {
let giftItem = document.createElement('li');
giftItem.style.transform = `rotate(${deg}deg)`;
giftItem.className = this.typeClassMap[this.lottery[i].type];
let span = document.createElement('span');
span.innerHTML = this.typeMap[this.lottery[i].type];
giftItem.appendChild(span);
giftFragment.appendChild(giftItem)
}
this.gift.appendChild(giftFragment);
其中元素span
完全用來顯示具體內(nèi)容的钱骂,比如紅包。
typeMap: {1: '¥', 2: '謝謝參與'},
typeClassMap: {1: '', 2: 'no-gift'},
css 布局代碼如下:
.turntable-wrap .turntable .gift {
position: relative;
width: 100%;
height: 100%;
transform: rotate(45deg);
}
.turntable-wrap .turntable .gift li {
position: absolute;
top: 5%;
left: 5%;
width: 45%;
height: 45%;
transform-origin: right bottom;
}
.turntable-wrap .turntable .gift li span {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
display: block;
width: 50px;
height: 70px;
margin: auto;
background: yellow;
transform: rotate(-45deg);
text-align: center;
line-height: 80px;
border-radius: 5px;
background: #f23c3c;
color: #fff;
font-size: 24px;
}
.turntable-wrap .turntable .gift li:not(.no-gift) span:before {
content: '';
position: absolute;
top: 15px;
left: 0;
width: 50px;
height: 1px;
background: #fff;
}
.turntable-wrap .turntable .gift li.no-gift span {
background: #fff;
line-height: 70px;
color: #bfa74f;
font-size: 12px;
}
轉(zhuǎn)盤指針
其實這個還是比較簡單的挪鹏,利用border
繪制的三角形见秽。
.turntable-wrap .pointer {
box-sizing: border-box;
position: absolute;
top: 50%;
left: 0;
right: 0;
margin: -23px auto;
width: 46px;
height: 46px;
border-radius: 50%;
background: #fff;
border: 5px solid #fff;
box-shadow: 0 0 0 5px #b9a046;
text-align: center;
line-height: 16px;
color: #b9a046;
font-size: 14px;
font-weight: 700;
cursor: pointer;
}
.turntable-wrap .pointer:before {
content: '';
position: absolute;
top: -58px;
left: 0;
right: 0;
margin: 0 auto;
width: 0;
border-style: solid;
border-color: transparent transparent #b9a046 transparent;
border-width: 25px 10px 25px 10px;
}
原生 js 實現(xiàn)抽獎效果
主要代碼如下:
gameStart () {
if (this.isGoing) {
return
}
this.isGoing = true;
// 1. 隨機(jī)中獎結(jié)果
// 從1-100之間得到一個隨機(jī)數(shù),看這個隨機(jī)數(shù)在中獎設(shè)置的范圍讨盒,得到最終中獎的項
let randomRate = ~~(Math.random() * 100) // ~~ == Math.floor()
// 設(shè)置中獎數(shù)據(jù)的概率范圍
let num = 0
this.lottery.forEach(item => {
item.min = num;
num += item.rate;
item.max = num;
})
// 根據(jù)隨機(jī)數(shù)解取,得到中獎結(jié)果
let res = this.lottery.filter(item => {
return randomRate >= item.min && randomRate < item.max;
})[0];
// 這兒可以根據(jù)實際情況,可重置中獎結(jié)果
// 2. 計算旋轉(zhuǎn)角度, 需要多轉(zhuǎn)5圈返顺,達(dá)轉(zhuǎn)1圈用時1s, 到旋轉(zhuǎn)的效果
let rotateItemDeg = (res.location - 1) * (360 / this.lottery.length); // 每個item旋轉(zhuǎn)角度, 第一個不用旋轉(zhuǎn)
let rotate = rotateItemDeg + 5 * 360;
let rotateSpeed = (rotateItemDeg / 360 * 1 + 5).toFixed(2);
// 重置轉(zhuǎn)盤樣式
this.turntable.removeAttribute('style');
// 保證下一次旋轉(zhuǎn)動畫生效
setTimeout(() => {
this.turntable.style.transform = `rotate(${rotate}deg)`;
this.turntable.style.transition = `transform ${rotateSpeed}s ease-out`;
}, 10)
// 3. 動畫結(jié)束禀苦,顯示中獎結(jié)果,中獎結(jié)果如何顯示遂鹊,視實際情況而定
setTimeout(() => {
this.isGoing = false;
console.log('中獎結(jié)果:', randomRate, res, this.typeMap[res.type]);
}, rotateSpeed * 1000);
}
由于代碼中的注釋還是比較全的振乏,就不贅述了。
以下是中獎結(jié)果的配置稿辙,實際中,可通過接口獲取气忠。rate 是中獎比例的控制邻储。
let lottery = [
{
location: 1, // 位置
type: 1, // 中獎
rate: 30, // 中獎比例 1-100
},
{
location: 2,
type: 2, // 未中獎
rate: 20
},
{ location: 3, type: 1, rate: 10 },
{ location: 4, type: 2, rate: 20 },
{ location: 5, type: 1, rate: 10 },
{ location: 6, type: 2, rate: 10 }
];
最后的最后
- 文字描述的可能不是很清晰,可看github上的源碼實現(xiàn)旧噪。也用 vue吨娜、react實現(xiàn)了一遍效果,其中 vue 版本的是已經(jīng)運用于實際中的淘钟。
- 實現(xiàn)方案可能不是最佳的宦赠,有更好方案的可提供建議。_