按下右侧的“点击预览”按钮能够在当前页面预览,点击连接能够全屏预览。javascript
https://codepen.io/comehope/pen/dwzRyQcss
此视频是能够交互的,你能够随时暂停视频,编辑视频中的代码。html
请用 chrome, safari, edge 打开观看。前端
第 1 部分:
https://scrimba.com/p/pEgDAM/ca6wWSkvue
第 2 部分:
https://scrimba.com/p/pEgDAM/c7Zy2AZjava
第 3 部分:
https://scrimba.com/p/pEgDAM/c9R2Gsygit
每日前端实战系列的所有源代码请从 github 下载:github
https://github.com/comehope/front-end-daily-challengesajax
本项目能够训练加、减、乘、除四则运算。好比训练加法时,界面给出 2 个数值表示 2 个加数,小朋友心算出结果后大声说出,而后点击“?”按钮查看结果,根据对照的结果,若是计算正确(或错误),就点击绿勾(或红叉),而后再开始下一道测验。界面中还会显示已经作过几道题,正确率是多少。为了加强趣味性,加入了音效,答对时会响起小猫甜美的叫声,答错时响起的是小猫失望的叫声。chrome
页面用纯 css 布局,程序逻辑用 vue 框架编写,用 howler.js 库播放音效。整个应用分红 4 个步骤实现:静态页面布局、加法的程序逻辑、四则运算的程序逻辑、音效处理。
先建立 dom 结构,整个文档分红 4 部分,.choose-type
是一组多选一按钮,用于选择四则运算的类型,.score
是成绩统计数据,.expression
是一个算式,它也是游戏的主体部分,.judgment
用于判断答题是否正确:
<div id="app"> <div class="choose-type"></div> <div class="score"></div> <div class="expression"></div> <div class="judgment"></div> </div>
.choose-type
一共包含 4 个 input[type=radio]
控件,命名为 arithmetic-type
,加、减、乘、除 4 种运算类型的值分别为 一、二、三、4,每一个控件后跟随一个对应的label
,最终咱们将把 input
控件隐藏起来,而让用户操做 label
。
<div id="app"> <div class="choose-type"> <div class="choose-type"> <input type="radio" id="addition" name="arithmetic-type" value="1"> <label for="addition">addition</label> <input type="radio" id="subtraction" name="arithmetic-type" value="2"> <label for="subtraction">subtraction</label> <input type="radio" id="multiplication" name="arithmetic-type" value="3"> <label for="multiplication">multiplication</label> <input type="radio" id="division" name="arithmetic-type" value="4"> <label for="division">division</label> </div> <!-- 略 --> </div>
.score
包含 2 个数据,一个是已经作过的题目数,一个是正确率:
<div id="app"> <!-- 略 --> <div class="score"> <span>ROUND 15</span> <span>SCORE 88%</span> </div> <!-- 略 --> </div>
.expression
把一个表达式的各部分拆开,以便能修饰表达式各部分的样式。.number
表示等式左边的 2 个运算数,.operation
表示运算符和等号,.show
是一个问号,同时它也是一个按钮,小心算出结果后,点击它,就显示出 .result
元素,展现运算结果:
<div id="app"> <!-- 略 --> <div class="expression"> <span class="number">10</span> <span class="operation">+</span> <span class="number">20</span> <span class="operation">=</span> <span class="button show">?</span> <span class="result">30</span> </div> <!-- 略 --> </div>
.judgment
包含 2 个按钮,分别是表示正确的绿勾和表示错误的红叉,显示在结果的下方:
<div id="app"> <!-- 略 --> <div class="judgment"> <span class="button right">✔</span> <span class="button wrong">✘</span> </div> </div>
至此,完整的 dom 结构以下:
<div id="app"> <div class="choose-type"> <input type="radio" id="addition" name="arithmetic-type" value="1"> <label for="addition">addition</label> <input type="radio" id="subtraction" name="arithmetic-type" value="2"> <label for="subtraction">subtraction</label> <input type="radio" id="multiplication" name="arithmetic-type" value="3"> <label for="multiplication">multiplication</label> <input type="radio" id="division" name="arithmetic-type" value="4"> <label for="division">division</label> </div> <div class="score"> <span>ROUND 15</span> <span>SCORE 88%</span> </div> <div class="expression"> <span class="number">10</span> <span class="operation">+</span> <span class="number">20</span> <span class="operation">=</span> <span class="button show">?</span> <span class="result">30</span> </div> <div class="judgment"> <span class="button right">✔</span> <span class="button wrong">✘</span> </div> </div>
接下来用 css 布局。
居中显示:
body{ margin: 0; height: 100vh; display: flex; align-items: center; justify-content: center; background: linear-gradient(lightyellow, tan); }
设置应用的容器样式,黑色渐变背景,子元素纵向排列,尺寸用相对单位 vw
和 em
,以便在窗口缩放后能自适应新窗口尺寸:
#app { width: 66vmin; display: flex; flex-direction: column; align-items: center; box-shadow: 0 1em 4em rgba(0, 0, 0, 0.5); border-radius: 2em; padding: 8em 5em; background: linear-gradient(black, dimgray, black); font-family: sans-serif; font-size: 1vw; user-select: none; }
布局 .choose-type
区域。隐藏 input
控件,设置 label
为天蓝色:
.choose-type input[name=arithmetic-type] { position: absolute; visibility: hidden; } .choose-type label { font-size: 2.5em; color: skyblue; margin: 0.3em; letter-spacing: 0.02em; }
在 label
之间加入分隔线:
.choose-type label { position: relative; } .choose-type label:not(:first-of-type)::before { content: '|'; position: absolute; color: skyblue; left: -0.5em; filter: opacity(0.6); }
设置 label
在鼠标悬停时变色,当 input
控件被选中时对应的 label
会变色、首字母变大写并显示下划线,为了使视觉效果切换平滑,设置了缓动时间。这里没有使用 text-decoration: underline
设置下划线,是由于用 border
才有缓动效果:
.choose-type label { transition: 0.3s; } .choose-type label:hover { color: deepskyblue; cursor: pointer; } .choose-type input[name=arithmetic-type]:checked + label { text-transform: capitalize; color: deepskyblue; border-style: solid; border-width: 0 0 0.1em 0; }
.score
区域用银色字,2 组数据之间留出一些间隔:
.score{ font-size: 2em; color: silver; margin: 1em 0 2em 0; width: 45%; display: flex; justify-content: space-between; }
.expression
区域用大字号,各元素用不一样的颜色区分:
.expression { font-size: 12em; display: flex; align-items: center; } .expression span { margin: 0 0.05em; } .expression .number{ color: orange; } .expression .operation{ color: skyblue; } .expression .result{ color: gold; }
.show
是等号右边的问号,它同时也是一个按钮,在这里把按钮的样式 .button
独立出来,由于后面还会用到 .button
样式:
.expression .show { color: skyblue; font-size: 0.8em; line-height: 1em; width: 1.5em; text-align: center; } .button { background-color: #222; border: 1px solid #555; padding: 0.1em; } .button:hover { background-color: #333; cursor: pointer; } .button:active { background-color: #222; }
设置 .judgment
区域 2 个按钮的样式,它们还共享了 .button
样式:
.judgment { font-size: 8em; align-self: flex-end; } .judgment .wrong { color: orangered; } .judgment .right { color: lightgreen; }
至此,静态页面布局完成,完整的 css 代码以下:
body{ margin: 0; height: 100vh; display: flex; align-items: center; justify-content: center; background: linear-gradient(lightyellow, tan); } #app { width: 66vw; display: flex; flex-direction: column; align-items: center; box-shadow: 0 1em 4em rgba(0, 0, 0, 0.5); border-radius: 2em; padding: 8em 5em; background: linear-gradient(black, dimgray, black); font-family: sans-serif; font-size: 1vw; user-select: none; } .choose-type input[name=arithmetic-type] { position: absolute; visibility: hidden; } .choose-type label { font-size: 2.5em; color: skyblue; margin: 0.3em; letter-spacing: 0.02em; position: relative; transition: 0.3s; } .choose-type label:not(:first-of-type)::before { content: '|'; position: absolute; color: skyblue; left: -0.5em; filter: opacity(0.6); } .choose-type label:hover { color: deepskyblue; cursor: pointer; } .choose-type input[name=arithmetic-type]:checked + label { text-transform: capitalize; color: deepskyblue; border-style: solid; border-width: 0 0 0.1em 0; } .score{ font-size: 2em; color: silver; margin: 1em 0 2em 0; width: 45%; display: flex; justify-content: space-between; } .expression { font-size: 12em; display: flex; align-items: center; } .expression span { margin: 0 0.05em; } .expression .number{ color: orange; } .expression .operation{ color: skyblue; } .expression .result{ color: gold; } .expression .show { color: skyblue; font-size: 0.8em; line-height: 1em; width: 1.5em; text-align: center; } .judgment { font-size: 8em; align-self: flex-end; } .judgment .wrong { color: orangered; } .judgment .right { color: lightgreen; } .button { background-color: #222; border: 1px solid #555; padding: 0.1em; } .button:hover { background-color: #333; cursor: pointer; } .button:active { background-color: #222; }
咱们先用加法把流程跑通,再把加法扩展为四则运算。
引入 vue 框架:
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.21/vue.min.js"></script>
建立一个 Vue 对象:
let vm = new Vue({ el: '#app', })
定义数据,round
存储题目数,round.all
表示总共答过了多少道题,round.right
表示答对了多少道题;numbers
数组包含 2 个元素,用于存储等式左边的 2 个运算数,用数组是为了便于后面使用解构语法:
let vm = new Vue({ ///...略 data: { round: {all: 0, right: 0}, numbers: [0, 0], } ///...略 })
定义计算属性,operation
是操做符,目前是加号,result
是计算结果,等于 2 个运算数相加,score
是正确率,开始作第一题时正确率显示为 100%,后续根据实际答对的题数计算正确率:
let vm = new Vue({ ///...略 computed: { operation: function() { return '+' }, result: function() { return this.numbers[0] + this.numbers[1] }, score: function() { return this.round.all == 1 ? 100 : Math.round(this.round.right / (this.round.all - 1) * 100) } }, ///...略 })
把数据绑定到 html 模板中:
<div id="app"> <!-- 略 --> <div class="score"> <span>ROUND {{round.all - 1}}</span> <span>SCORE {{score}}%</span> </div> <div class="expression"> <span class="number">{{numbers[0]}}</span> <span class="operation">{{operation}}</span> <span class="number">{{numbers[1]}}</span> <span class="operation">=</span> <span class="button show">?</span> <span class="result">{{result}}</span> </div> <!-- 略 --> </div>
至此,页面中的数据都是动态获取的了。
等式右边的问号和结果不该同时显示出来,在用户思考时应显示问号,思考结束后应隐藏问号显示结果。为此,增长一个 isThinking
变量,用于标志用户所处的状态,默认为 true
,即进入游戏时,用户开始思考第 1 道题目:
let vm = new Vue({ ///...略 data: { round: {all: 0, right: 0}, numbers: [0, 0], isThinking: true, }, ///...略 })
把 isThinking
绑定到 html 模板中,用户思考时只显示问号 .show
,不然显示结果 .result
和判断结果正确与否的按钮 .judgment
,此处请注意,对于占据同一个视觉位置的元素,用 v-show=false
,即 display: none
隐藏,对于占据独立视觉位置的元素,用 visibility: hidden
隐藏:
<div id="app"> <!-- 略 --> <div class="expression"> <!-- 略 --> <span class="button show" v-show="isThinking">?</span> <span class="result" v-show="!isThinking">{{result}}</span> </div> <div class="judgment" :style="{visibility: isThinking ? 'hidden' : 'visible'}"> <!-- 略 --> </div> </div>
接下来生成随机运算数。建立一个 next()
方法用于开始下一个题目,那么在页面载入后就应执行这个方法初始化第 1 道题目:
let vm = new Vue({ ///...略 methods: { next: function() { }, }, }) window.onload = vm.next
next()
方法一方面要负责初始化运算数,还要把答过的题目数加1,这里独立出来一个 newRound()
方法是为了方便后面复用它:
let vm = new Vue({ ///...略 methods: { newRound: function() { this.numbers = this.getNumbers() this.isThinking = true }, next: function() { this.newRound() this.round.all++ }, }, })
getNumbers()
方法用于生成 2 个随机数,它调用 getRandomNumber()
方法来生成一个随机数,其中 level
参数表示随机数的取值范围,level
为 1 时,生成的随机数介于 1 ~ 9 之间,level
为 2 时,生成的随机数介于 10 ~ 99 之间。为了增长一点加法的难度,咱们把 level
设置为 2:
let vm = new Vue({ ///...略 methods: { getRandomNumber: function(level) { let min = Math.pow(10, level - 1) let max = Math.pow(10, level) return min + Math.floor(Math.random() * (max - min)) }, getNumbers: function() { let level = 2 let a = this.getRandomNumber(level) let b = this.getRandomNumber(level) return [a, b] }, newRound: function() { this.numbers = this.getNumbers() this.isThinking = true }, next: function() { this.newRound() this.round.all++ }, }, })
此时,每刷新一次页面,运算数就会跟着刷新,由于每次页面加载都会运行 vm.next()
方法生成新的随机数。
接下来咱们来处理按钮事件,页面中一共有 3 个按钮:问号按钮 .show
被点击后应显示结果;绿勾按钮 .right
被点击后应给答对题的数目加 1,而后进入下一道题;红叉按钮 .wrong
被点击后直接进入下一道题,因此咱们在程序中增长 3 个方法,getResult()
、answerRight()
、answerWrong
分别对应上面的 3 个点击事件:
let vm = new Vue({ ///...略 methods: { ///...略 getResult: function() { this.isThinking = false }, answerRight: function() { this.round.right++ this.next() }, answerWrong: function() { this.next() }, }, })
把事件绑定到 html 模板:
<div id="app"> <!-- 略 --> <div class="expression"> <!-- 略 --> <span class="button show" v-show="isThinking" @click="getResult">?</span> <!-- 略 --> </div> <div class="judgment" :style="{visibility: isThinking ? 'hidden' : 'visible'}"> <span class="button right" @click="answerRight">✔</span> <span class="button wrong" @click="answerWrong">✘</span> </div> </div>
至此,加法程序就所有完成了,能够一道又一道题一直作下去。
此时的 html 代码以下:
<div id="app"> <div class="choose-type"> <!-- 没有改变 --> </div> <div class="score"> <span>ROUND {{round.all - 1}}</span> <span>SCORE {{score}}%</span> </div> <div class="expression"> <span class="number">{{numbers[0]}}</span> <span class="operation">{{operation}}</span> <span class="number">{{numbers[1]}}</span> <span class="operation">=</span> <span class="button show" v-show="isThinking" @click="getResult">?</span> <span class="result" v-show="!isThinking">{{result}}</span> </div> <div class="judgment" :style="{visibility: isThinking ? 'hidden' : 'visible'}"> <span class="button right" @click="answerRight">✔</span> <span class="button wrong" @click="answerWrong">✘</span> </div> </div>
此时的 javascript 代码以下:
let vm = new Vue({ el: '#app', data: { round: {all: 0, right: 0}, numbers: [0, 0], isThinking: true, }, computed: { operation: function() { return '+' }, result: function() { return this.numbers[0] + this.numbers[1] }, score: function() { return this.round.all == 1 ? 100 : Math.round(this.round.right / (this.round.all - 1) * 100) } }, methods: { getRandomNumber: function(level) { let min = Math.pow(10, level - 1) let max = Math.pow(10, level) return min + Math.floor(Math.random() * (max - min)) }, getNumbers: function() { let level = 2 let a = this.getRandomNumber(level) let b = this.getRandomNumber(level) return [a, b] }, newRound: function() { this.numbers = this.getNumbers() this.isThinking = true }, next: function() { this.newRound() this.round.all++ }, getResult: function() { this.isThinking = false }, answerRight: function() { this.round.right++ this.next() }, answerWrong: function() { this.next() }, }, }) window.onload = vm.next
咱们先来评估一下四种运算在这个程序里会在哪些方面有差别。首先,运算符不一样,加、减、乘、除的运算符分别是“+”、“-”、“×”、“÷”;第二是运算函数不一样,这个不用多说。根据这 2 点,咱们定义一个枚举对象 ARITHMETIC_TYPE
,用它存储四种运算的差别,每一个枚举对象有 2 个属性,operation
表明操做符,f()
函数是运算逻辑。另外,咱们再声明一个变量 arithmeticType
,用于存储用户当前选择的运算类型:
let vm = new Vue({ ///...略 data: { ///...略 ARITHMETIC_TYPE: { ADDITION: 1, SUBTRACTION: 2, MULTIPLICATION: 3, DIVISION: 4, properties: { 1: {operation: '+', f: ([x, y]) => x + y}, 2: {operation: '-', f: ([x, y]) => x - y}, 3: {operation: '×', f: ([x, y]) => x * y}, 4: {operation: '÷', f: ([x, y]) => x / y} } }, arithmeticType: 1, }, })
改造计算属性中关于运算符和计算结果的函数:
let vm = new Vue({ ///...略 computed: { ///...略 operation: function() { // return '+' return this.ARITHMETIC_TYPE.properties[this.arithmeticType].operation }, result: function() { // return this.numbers[0] + this.numbers[1] return this.ARITHMETIC_TYPE.properties[this.arithmeticType].f(this.numbers) }, ///...略 }, })
由于上面 2 个计算属性都用到了 arithmeticType
变量,因此当用户选择运算类型时,这 2 个计算属性的值会自动更新。另外,为了让 ui 逻辑更严密,咱们令 arithmeticType
的值改变时,开始一个新题目:
let vm = new Vue({ ///...略 watch: { arithmeticType: function() { this.newRound() } } })
而后,把 arithmeticType
变量绑定到 html 模板中的 input
控件上:
<div id="app"> <div class="choose-type"> <input type="radio" id="addition" name="arithmetic-type" value="1" v-model="arithmeticType"> <label for="addition">addition</label> <input type="radio" id="subtraction" name="arithmetic-type" value="2" v-model="arithmeticType"> <label for="subtraction">subtraction</label> <input type="radio" id="multiplication" name="arithmetic-type" value="3" v-model="arithmeticType"> <label for="multiplication">multiplication</label> <input type="radio" id="division" name="arithmetic-type" value="4" v-model="arithmeticType"> <label for="division">division</label> </div> <!-- 略 --> </div>
至此,当选择不一样的运算类型时,表达式的运算符和计算结果都会自动更新为匹配的值,好比选择乘法时,运算符就变为乘号,运算结果为 2 个运算数的乘积。
不过,此时的最明显的问题是,除法的运算数由于是随机生成的,商常常是无限小数,为了更合理,咱们规定这里的除法只作整除运算。再延伸一下,对于减法,为了不差为负数,也规定被减数不小于减数。
解决这个问题的办法是在 ARITHMETIC_TYPE
枚举中添加一个 gen()
函数,用于存储生成运算数的逻辑,gen()
函数接收一个包含 2 个随机数的数组做为参数,对于加法和乘法,直接返回数组自己,减法的 gen()
函数为 gen: ([a, b]) => a >= b ? [a, b] : [b, a]
,除法的 gen()
函数为 gen: ([a, b]) => [a * b, b]
,通过如此处理的运算数,就能够实现上面规定的逻辑了。改造后的 ARITHMETIC_TYPE
以下:
let vm = new Vue({ ///...略 data: { ///...略 ARITHMETIC_TYPE: { ADDITION: 1, SUBTRACTION: 2, MULTIPLICATION: 3, DIVISION: 4, pproperties: { 1: {operation: '+', f: (arr) => arr, gen: ([a, b]) => [a, b]}, 2: {operation: '-', f: ([x, y]) => x - y, gen: ([a, b]) => a >= b ? [a, b] : [b, a]}, 3: {operation: '×', f: (arr) => arr, gen: ([a, b]) => [a, b]}, 4: {operation: '÷', f: ([x, y]) => x / y, gen: ([a, b]) => [a * b, b]} } }, ///...略 }, ///...略 })
而后,在 getNumbers()
中调用 gen()
方法:
let vm = new Vue({ ///...略 methods: { ///...略 getNumbers: function() { let level = 2 let a = this.getRandomNumber(2) let b = this.getRandomNumber(2) // return [a, b] return this.ARITHMETIC_TYPE.properties[this.arithmeticType].gen([a, b]) }, ///...略 }, ///...略 })
至此,减法能够保证差不为负数,除法也能够保证商是整数了。
接下来,咱们来配置训练难度。对大多数人来讲,2 个二位数的加减法不是很难,可是 2 个二位数的乘除法的难度就大多了。在生成随机数时,由于定义了 level=2
,因此取值范围固定是 11 ~ 99,咱们但愿可以灵活配置每一个运算数的取值范围,为此,咱们须要再为 ARITHMETIC_TYPE
枚举中增长一个 level
属性,用于表示随机数的取值范围,它是一个包含 2 个元素的数组,分别表示 2 个运算数的取值范围,改造后的 ARITHMETIC_TYPE
以下:
let vm = new Vue({ ///...略 data: { ///...略 ARITHMETIC_TYPE: { ADDITION: 1, SUBTRACTION: 2, MULTIPLICATION: 3, DIVISION: 4, properties: { 1: {operation: '+', f: ([x, y]) => x + y, gen: (arr) => arr, level: [3, 2]}, 2: {operation: '-', f: ([x, y]) => x - y, gen: ([a, b]) => a >= b ? [a, b] : [b, a], level: [3, 2]}, 3: {operation: '×', f: ([x, y]) => x * y, gen: (arr) => arr, level: [2, 1]}, 4: {operation: '÷', f: ([x, y]) => x / y, gen: ([a, b]) => [a * b, b], level: [2, 1]} } }, ///...略 }, ///...略 })
而后,把 getNumbers()
函数的 level
变量的值改成从枚举 ARITHMETIC_TYPE
中取值:
let vm = new Vue({ ///...略 methods: { getNumbers: function() { let level = this.ARITHMETIC_TYPE.properties[this.arithmeticType].level let a = this.getRandomNumber(level[0]) let b = this.getRandomNumber(level[1]) return this.ARITHMETIC_TYPE.properties[this.arithmeticType].gen([a, b]) }, ///...略 }, ///...略 })
如今运行程序能够看到,加减法的 2 个运算数分别是 3 位数和 2 位数,而乘除法的 2 个运算数则分别是 2 位数和 1 位数,你也能够根据本身的须要来调整训练难度。
至此,四则运算的程序逻辑所有完成,此时的 javascript 代码以下:
let vm = new Vue({ el: '#app', data: { round: {all: 0, right: 0}, numbers: [0, 0], isThinking: true, ARITHMETIC_TYPE: { ADDITION: 1, SUBTRACTION: 2, MULTIPLICATION: 3, DIVISION: 4, properties: { 1: {operation: '+', f: ([x, y]) => x + y, gen: (arr) => arr, level: 2}, 2: {operation: '-', f: ([x, y]) => x - y, gen: ([a, b]) => a >= b ? [a, b] : [b, a], level: 2}, 3: {operation: '×', f: ([x, y]) => x * y, gen: (arr) => arr, level: 1}, 4: {operation: '÷', f: ([x, y]) => x / y, gen: ([a, b]) => [a * b, b], level: 1} } }, arithmeticType: 1, }, computed: { operation: function() { return this.ARITHMETIC_TYPE.properties[this.arithmeticType].operation }, result: function() { return this.ARITHMETIC_TYPE.properties[this.arithmeticType].f(this.numbers) }, score: function() { return this.round.all == 1 ? 100 : Math.round(this.round.right / (this.round.all - 1) * 100) } }, methods: { getRandomNumber: function(level) { let min = Math.pow(10, level - 1) let max = Math.pow(10, level) return min + Math.floor(Math.random() * (max - min)) }, getNumbers: function() { let level = this.ARITHMETIC_TYPE.properties[this.arithmeticType].level let a = this.getRandomNumber(level[0]) let b = this.getRandomNumber(level[1]) return this.ARITHMETIC_TYPE.properties[this.arithmeticType].gen([a, b]) }, newRound: function() { this.numbers = this.getNumbers() this.isThinking = true }, next: function() { this.newRound() this.round.all++ }, getResult: function() { this.isThinking = false }, answerRight: function() { this.round.right++ this.next() }, answerWrong: function() { this.next() }, }, watch: { arithmeticType: function() { this.newRound() } } }) window.onload = vm.next
引入 howler 库:
<script src="https://cdnjs.cloudflare.com/ajax/libs/howler/2.1.1/howler.min.js"></script>
声明变量 sound
,它有 2 个属性 right
和 wrong
,分别表明回答正确和错误时的音效,属性值是一个 Howl
对象,在构造函数中指定音频文件的 url:
let vm = new Vue({ ///...略 data: { ///...略 sound: { right: new Howl({src: ['https://freesound.org/data/previews/203/203121_777645-lq.mp3']}), wrong: new Howl({src: ['https://freesound.org/data/previews/415/415209_5121236-lq.mp3']}) }, }, ///...略 })
在 answerRight()
方法和 answerWrong()
方法中分别调用播放声音的 play()
方法便可:
let vm = new Vue({ ///...略 methods: { ///...略 answerRight: function() { this.round.right++ this.sound.right.play() this.next() }, answerWrong: function() { this.sound.wrong.play() this.next() }, ///...略 })
如今,当点击绿勾时,就会响起小猫甜美的叫声;当点击红叉时,响起的是小猫失望的叫声。
至此,程序所有开发完成,最终的 javascript 代码以下:
let vm = new Vue({ el: '#app', data: { round: {all: 0, right: 0}, numbers: [0, 0], isThinking: true, ARITHMETIC_TYPE: { ADDITION: 1, SUBTRACTION: 2, MULTIPLICATION: 3, DIVISION: 4, properties: { 1: {operation: '+', f: ([x, y]) => x + y, gen: (arr) => arr, level: [3, 2]}, 2: {operation: '-', f: ([x, y]) => x - y, gen: ([a, b]) => a >= b ? [a, b] : [b, a], level: [3, 2]}, 3: {operation: '×', f: ([x, y]) => x * y, gen: (arr) => arr, level: [2, 1]}, 4: {operation: '÷', f: ([x, y]) => x / y, gen: ([a, b]) => [a * b, b], level: [2, 1]} } }, arithmeticType: 1, sound: { right: new Howl({src: ['https://freesound.org/data/previews/203/203121_777645-lq.mp3']}), wrong: new Howl({src: ['https://freesound.org/data/previews/415/415209_5121236-lq.mp3']}) }, }, computed: { operation: function() { return this.ARITHMETIC_TYPE.properties[this.arithmeticType].operation }, result: function() { return this.ARITHMETIC_TYPE.properties[this.arithmeticType].f(this.numbers) }, score: function() { return this.round.all == 1 ? 100 : Math.round(this.round.right / (this.round.all - 1) * 100) } }, methods: { getRandomNumber: function(level) { let min = Math.pow(10, level - 1) let max = Math.pow(10, level) return min + Math.floor(Math.random() * (max - min)) }, getNumbers: function() { let level = this.ARITHMETIC_TYPE.properties[this.arithmeticType].level let a = this.getRandomNumber(level[0]) let b = this.getRandomNumber(level[1]) return this.ARITHMETIC_TYPE.properties[this.arithmeticType].gen([a, b]) }, newRound: function() { this.numbers = this.getNumbers() this.isThinking = true }, next: function() { this.newRound() this.round.all++ }, getResult: function() { this.isThinking = false }, answerRight: function() { this.round.right++ this.sound.right.play() this.next() }, answerWrong: function() { this.sound.wrong.play() this.next() }, }, watch: { arithmeticType: function() { this.newRound() } } }) window.onload = vm.next
大功告成!