主要描述的是如何運用 css 繪制一個抽獎轉盤,並運用原生 js 實現轉盤抽獎效果。
先來張效果圖:
布局
一般來說,轉盤一般有四個部分組成:外層閃爍的燈、內層旋轉的圓盤、圓盤上的中獎結果、指針。
所以html的結構如下:
<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>
其中燈需要一直閃爍,而抽獎的時候轉盤需要轉動連帶其中的中獎結果,而轉盤指針不需要轉動是固定的。因此 light 、 turntable 、 pointer放在同一級別。
外層容器利用border-radius:50%
實現從正方形變圓形的效果。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 動態的給 light 生成多個小圓點,lightNum = 18,感覺這個數字,轉盤每小塊顯示 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 寬、高和外層容器一致,里面添加元素,而元素的寬度給和小圓點一致的寬度,而這些元素旋轉一定的角度((360 / this.lightNum) * i
),而它們的旋轉中心設置在中心點。達到的效果就是這樣的:
接下來就是繪制小圓點了,利用:before
屬性,將小圓點繪制到頂部。而閃爍的燈,有兩種顏色,再利用:nth-type-of(event/odd)
實現。最后就是閃爍了,利用的是@keyframes
、animation
。
最終實現:
@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;
}
轉盤的背景
轉盤的背景的布局原理和燈的布局原理是一致的。就不重復了。itemNum = 6
,表示轉盤分6塊。
// 初始化轉盤背景
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;
}
效果圖:
轉盤的中獎圖
利用的也是transform:rotate()
、transform-origin
屬性,達到旋轉布局的。但是又有一點點不一樣。
為了讓中獎圖顯示在轉盤每個塊的正中間
所以每個塊是按transorm-origin:right bottom
這個方向旋轉角度,而外層旋轉transform: rotate(45deg)
,里面的每個小圖旋轉transform: rotate(-45deg)
再糾正過來。
ps:其實按照燈的transorm-origin:center center
應該也能實現,但開始想到的就是這種方案。
js 初始化塊的元素代碼如下:
// 初始化轉盤上的中獎圖
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
完全用來顯示具體內容的,比如紅包。
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;
}
轉盤指針
其實這個還是比較簡單的,利用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 實現抽獎效果
主要代碼如下:
gameStart () {
if (this.isGoing) {
return
}
this.isGoing = true;
// 1. 隨機中獎結果
// 從1-100之間得到一個隨機數,看這個隨機數在中獎設置的范圍,得到最終中獎的項
let randomRate = ~~(Math.random() * 100) // ~~ == Math.floor()
// 設置中獎數據的概率范圍
let num = 0
this.lottery.forEach(item => {
item.min = num;
num += item.rate;
item.max = num;
})
// 根據隨機數,得到中獎結果
let res = this.lottery.filter(item => {
return randomRate >= item.min && randomRate < item.max;
})[0];
// 這兒可以根據實際情況,可重置中獎結果
// 2. 計算旋轉角度, 需要多轉5圈,達轉1圈用時1s, 到旋轉的效果
let rotateItemDeg = (res.location - 1) * (360 / this.lottery.length); // 每個item旋轉角度, 第一個不用旋轉
let rotate = rotateItemDeg + 5 * 360;
let rotateSpeed = (rotateItemDeg / 360 * 1 + 5).toFixed(2);
// 重置轉盤樣式
this.turntable.removeAttribute('style');
// 保證下一次旋轉動畫生效
setTimeout(() => {
this.turntable.style.transform = `rotate(${rotate}deg)`;
this.turntable.style.transition = `transform ${rotateSpeed}s ease-out`;
}, 10)
// 3. 動畫結束,顯示中獎結果,中獎結果如何顯示,視實際情況而定
setTimeout(() => {
this.isGoing = false;
console.log('中獎結果:', randomRate, res, this.typeMap[res.type]);
}, rotateSpeed * 1000);
}
由於代碼中的注釋還是比較全的,就不贅述了。
以下是中獎結果的配置,實際中,可通過接口獲取。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上的源碼實現。也用 vue、react實現了一遍效果,其中 vue 版本的是已經運用於實際中的。
- 實現方案可能不是最佳的,有更好方案的可提供建議。_