按下右側的「點擊預覽」按鈕能夠在當前頁面預覽,點擊連接能夠全屏預覽。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
大功告成!