【JavasScript】折腾一个基础到不能再基础的顺滑抽奖页面

前言

事情是这样的,作为一个意志力极低的人,最近一直在找寻提高意志力的方法。
然后决定试一试所谓的“建立奖励机制”,也就是说,完成一项意志力挑战后给自己一些奖励(具体操作方法不在这里进行赘述)。
那么,一款丝滑的“抽奖页面”也就理所当然加入了我的(瞎)待(折)办(腾)事项中。

整个过程还是比较简单的,涉及到的知识也相对基础,作为这个博客的第一篇技术文章,再加上作为一个技术小白,写的相对简单。希望能带个大家些许帮助,如果有建议批评也欢迎大家踊跃反馈。

涉及到的技术栈/框架

  • 前端开发基础知识(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="//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变量


效果如下图⬇️
image

需求

我们现有的基础功能毫无疑问是过于简陋的,僵硬的抽取过程,无严谨可言的抽取规则等等,让我们的抽奖系统现在看起来就像是并夕夕的 “赢取百万现金”轮盘一样透露和不可言说的廉价感,那么“如何改善”就显得尤为关键了。

  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="//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>

效果如下⬇️
image

结语

一个简单到不简单的前端例子而已,没有进行完善的封装,就这样发布了。希望嫩个够对大家有所帮助。以后我会尽量经常发布一些有营养的技术案例等等同时也会记录我在学习工作过程中遇到的有趣的知(坑)识。

感谢阅读,祝你生活愉快。