前言

事情是這樣的,作為一個意志力極低的人,最近一直在找尋提高意志力的方法。

然後決定試一試所謂的“建立獎勵機制”,也就是說,完成一項意志力挑戰後給自己一些獎勵(具體操作方法不在這裡進行贅述)。

那麼,一款絲滑的“抽獎頁面”也就理所當然加入了我的(瞎)待(折)辦(騰)事項中。

整個過程還是比較簡單的,涉及到的知識也相對基礎,作為這個部落格的第一篇技術文章,再加上作為一個技術小白,寫的相對簡單。希望能帶個大家些許幫助,如果有建議批評也歡迎大家踴躍反饋。

涉及到的技術棧/框架

  • 前端開發基礎知識(Html/Css/JavaScript)
  • Javascript ES6語法
  • Vue.js相關技術知識
  • JavaScript非同步知識

基礎程式碼

先上一段整體的基礎程式碼

<!DOCTYPE html>
<html lang="zh"> <head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Lottery</title>
<style>
#app {
margin: auto;
width: 200px;
text-align: center;
}
</style>
</head> <body>
<div id="app">
<p style="height:22px">{{item}}</p>
<button @click="start">開始!</button>
</div> <script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
</body>
<script>
const app = new Vue({
el: "#app",
data: {
list: ['1積分', '5積分', '10積分', '20積分', '謝謝惠顧', '再來一次'],
item: "開始抽獎!"
},
methods: {
start() {
for (let index = 0; index < 50; index++) {
setTimeout(() => {
this.item = this.list[Math.round(Math.random() * (this.list.length - 1))]
}, 100 + index * 100) } }
}
})
</script> </html>

可以看到整體頁面元素是再簡單不過的,頁面上除了 最外層的div元素 之外,就只剩一個用於啟動抽獎的 按鈕 和一個用於顯示當前Item的 段落元素。通過外鏈的方式引用了 Vue框架

data中,list陣列 作為獎池,用於放置可以被抽取到的item,item變數 則用來指明當前抽取到的item並與用於顯示當前item的 段落元素 進行繫結 ,在 start方法 執行之前,item變數 預設值為“開始抽獎!”。

methods中只有一個 start方法 用於啟動抽獎。通過 for迴圈 ,進行50次的抽取。setTimeOut 將抽取時間控制在100毫秒,也就是說每100毫秒進行一次抽取,從 list陣列 中隨機抽取值替換當前的 item變數


效果如下圖️

需求

我們現有的基礎功能毫無疑問是過於簡陋的,僵硬的抽取過程,無嚴謹可言的抽取規則等等,讓我們的抽獎系統現在看起來就像是並夕夕的 “贏取百萬現金”輪盤一樣透露和不可言說的廉價感,那麼“如何改善”就顯得尤為關鍵了。

  1. 優化抽取過程,使抽取過程流暢不那麼僵硬
  2. 優化抽取規則,使抽取規則更為嚴謹
  3. 優化完善部分細節

完善過程

優化抽取過程

由於item抽取的間隔時間都為固定的100毫秒,看起來很呆板不絲滑,所以對抽取的間隔時間做出優化,讓它看起來絲滑流暢一些。

  • start 方法的更新:
            async start() {
for (let index = 0; index < 50; index++) {
this.item = await new Promise(resolve => {
setTimeout(() => {
resolve(this.list[Math.round(Math.random() * (this.list.length - 1))])
}, Math.round(this.getTime(index)))
})
}
},

將非同步函式 setTimeOut 進行 await 非同步處理,讓間隔時間可以疊加方便我們進行間隔時間的控制。由於使用了es6語法中的 await 關鍵字,所以此函式加上了 async 字首。間隔時間使用新新增的函式 getTime 傳參獲取。

  • 新增 getTime 方法
            getTime(index) {
// 初始/正常間隔時間
let time = 80
// 啟動間隔時間處理
if (index <= 10) {
time = time + (11 - index) * 20
}
// 結束間隔時間的處理
if (index >= 39) {
time = time + (index - 39) * 20
if (index === 49) time = 1000
}
return time
}

通過引數 index 獲取當時的迴圈次數,並計算出本次對應的 間隔時間 進行返回。(簡單寫的一個方法,且只針對於本專案,以後有空再進行封裝完善。間隔時間的規律演算法只經過了作者簡單的計算,讓它看起來比較符合作者對“順滑”的定義,大家要是不喜歡也可以自己去嘗試更改。畢竟現在是實驗專案,能用就行)

優化抽取規則

由於抽取的規則並沒有新增任何限制,導致專案抽取item的時候很可能抽取到的item與上一個相同,那就導致item在顯示切換的時候出現問題,看起來好像並沒有進行抽取。

  • start 方法的更新:
            async start() {
for (let index = 0; index < 50; index++) {
this.item = await new Promise(resolve => {
setTimeout(() => {
resolve(this.getItem())
}, Math.round(this.getTime(index)))
})
}
},

不再是簡單的返回抽取結果,而是返回 getItem 方法計算出的item,對抽取的item結果新增限制。

  • 新增 getItem 方法
            getItem() {
let newItem;
do {
newItem = this.list[Math.round(Math.random() * (this.list.length - 1))]
} while (this.item === newItem);
return newItem
}

如果抽取到與上一個item相同的結果,那麼就進行重複抽取,直到抽取到不同的結果為止。

優化細節

        data: {
startButtonDisabled: false,
finished: false,
list: ['1積分', '5積分', '10積分', '20積分', '謝謝惠顧', '再來一次'],
item: "開始抽獎!"
},

data 中加入 startButtonDisabledfinished 用來分別記錄按鈕狀態以及開獎的狀態。

            handleStart(index) {
if (index === 0) {
this.finished = false
this.startButtonDisabled = true
}
},
handleEnd(index) {
if (index === 49) {
this.finished = true
this.startButtonDisabled = false
}
}

新增函式 handleStarthandleEndstart 方法中迴圈體的首尾新增事件進行細節上的優化。在傳入引數 index 為0(迴圈開始)時,利用方法 handleStart 將 開獎狀態 改變為未開獎,按鈕禁用,且在傳入引數為49(迴圈結束)時,利用方法 handleEnd 再次改變其狀態。

            async start() {
for (let index = 0; index < 50; index++) {
this.handleStart(index)
this.item = await new Promise(resolve => {
setTimeout(() => {
resolve(this.getItem())
}, Math.round(this.getTime(index)))
})
this.handleEnd(index)
}
},

start 方法的迴圈體首尾呼叫 handleStarthandleEnd 方法。

  <div id="app">
<p :class="{active:finished}" style="height:22px">{{item}}</p>
<button :disabled="startButtonDisabled" @click="start">開始!</button>
</div>

將頁面元素進行修改,使對應元素會隨著 data 中資料狀態的改變而產生變化。

        p.active {
font-weight: bold;
color: red
}

最後為開獎狀態為true的 active 類新增css樣式,優化完成!

最終程式碼

<!DOCTYPE html>
<html lang="zh"> <head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Lottery</title>
<style>
#app {
margin: auto;
width: 200px;
text-align: center;
} p.active {
font-weight: bold;
color: red
}
</style>
</head> <body>
<div id="app">
<p :class="{active:finished}" style="height:22px">{{item}}</p>
<button :disabled="startButtonDisabled" @click="start">開始!</button>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
</body>
<script>
const app = new Vue({
el: "#app",
data: {
startButtonDisabled: false,
finished: false,
list: ['1積分', '5積分', '10積分', '20積分', '謝謝惠顧', '再來一次'],
item: "開始抽獎!"
},
methods: {
async start() {
for (let index = 0; index < 50; index++) {
this.handleStart(index)
this.item = await new Promise(resolve => {
setTimeout(() => {
resolve(this.getItem())
}, Math.round(this.getTime(index)))
})
this.handleEnd(index)
}
},
getTime(index) {
// 初始/正常間隔時間
let time = 80
// 啟動間隔時間處理
if (index <= 10) {
time = time + (11 - index) * 20
}
// 結束間隔時間的處理
if (index >= 39) {
time = time + (index - 39) * 20
if (index === 49) time = 1000
}
return time
},
getItem() {
let newItem;
do {
newItem = this.list[Math.round(Math.random() * (this.list.length - 1))]
} while (this.item === newItem);
return newItem
},
handleStart(index) {
if (index === 0) {
this.finished = false
this.startButtonDisabled = true
}
},
handleEnd(index) {
if (index === 49) {
this.finished = true
this.startButtonDisabled = false
}
}
}
})
</script> </html>

效果如下️

結語

一個簡單到不簡單的前端例子而已,沒有進行完善的封裝,就這樣釋出了。希望嫩個夠對大家有所幫助。以後我會盡量經常釋出一些有營養的技術案例等等同時也會記錄我在學習工作過程中遇到的有趣的知(坑)識。

感謝閱讀,祝你生活愉快。