JS 30 是由加拿大的全端工程師 Wes Bos 免費提供的 JavaScript 簡單應用課程,課程主打 No Frameworks
、No Compilers
、No Libraries
、No Boilerplate
在30天的30部教學影片裡,建立30個JavaScript的有趣小東西。
另外,Wes Bos 也很無私地在 Github 上公開了所有 JS 30 課程的程式碼,有興趣的話可以去 fork 或下載。
最後一天要實作的內容是"網頁版打地鼠"。(下面是實際網頁效果的gif圖)
<h1>
是頁面的標題,旁邊的<span class="score">
是打地鼠遊戲的記分板。
<button>
在滑鼠點擊時,會呼叫startGame()
方法,開始打地鼠遊戲。
<div class="game">
是打地鼠遊戲的主體,內部有<dvi class="hole">
共計六個洞,每個洞(.hole
)都有一隻初始被隱藏的地鼠(<div class="mole">
)。
<h1>Whack-a-mole! <span class="score">0</span></h1>
<button onClick="startGame()">Start!</button>
<div class="game">
<div class="hole hole1">
<div class="mole"></div>
</div>
<div class="hole hole2">
<div class="mole"></div>
</div>
<div class="hole hole3">
<div class="mole"></div>
</div>
<div class="hole hole4">
<div class="mole"></div>
</div>
<div class="hole hole5">
<div class="mole"></div>
</div>
<div class="hole hole6">
<div class="mole"></div>
</div>
</div>
初始每一隻地鼠(.mole
)都是採用絕對定位(position:absolute
),然後將top
指定為100%,把地鼠(.mole
)隱藏起來。
在遊戲過程中,我們會為地洞(.hole
)添加.up
這個 CSS class 選擇器,讓地鼠探頭出來給我們打 XD。
.mole {
/*上略...*/
position: absolute;
top: 100%;
/*下略...*/
}
.hole.up .mole {
top: 0;
}
宣告常數holes
取得所有的地洞(.hole
),資料型態是 NodeList。
宣告常數scoreBoard
取得頁面中顯示的分數(.score
)。
宣告常數mole
取得所有的地鼠(.mole
),資料型態是 NodeList。
const holes = document.querySelectorAll('.hole'); //NodeList
const scoreBoard = document.querySelector('.score');
const moles = document.querySelectorAll('.mole');
撰寫ranTime()
幫我們決定地鼠出現的持續時間並給定參數min
、max
作為出現持續時間範圍的最小、最大值。
下面用Math.random()
隨機產生一個介於0~1的數字,然後把它乘上(max-min)
再加上min
,最後用Math.round()
四捨五入得到隨機的出現持續時間。
function randTime(min,max){
return Math.round(Math.random() * (max-min) + min);
}
randomHole()
幫我們隨機選擇地鼠出現的洞,為避免接連選到兩次一樣的洞,所以另外宣告變數lastHole
,來幫我們記住上一次出現的洞。
在方法裡,首先要傳入所有洞穴(holes
,NodeList),接著一樣用Math.random()
隨機產生0~1的數字並乘上holes
的長度後,呼叫Math.floor()
無條件捨去小數點,取得一個隨機的index
放入常數idx
中。
然後,宣告的常數hole
就可以用這個idx
,隨機取得holes
中的一個洞穴。
為避免選到和上次一樣的洞穴,利用條件判斷hole
是不是跟lastHole
相同,如果相同就遞迴呼叫randomHole()
,直至選到不同的洞為止。
在方法的最後,把這次的結果放到lastHole
中,之後回傳被隨機選到的hole
。
let lastHole;
function randomHole(holes){
const idx = Math.floor(Math.random() * holes.length);
const hole = holes[idx]
if(hole === lastHole){
console.log('You got the same hole.');
return randomHole(holes);
}
lastHole = hole;
return hole;
}
宣告變數timeup
作為遊戲是否已經結束的flag
。
peep()
是讓地鼠從洞穴探頭出來的關鍵!!!
在peep()
的最一開始,宣告常數time
並取得由randTime(200,1000)
隨機產生的地鼠持續出現時間,接著宣告常數hole
取得由randomHole(holes)
隨機挑出的地洞。
隨機挑出的hole
會被添加.up
這個 class,目的是讓躲在洞中的地鼠探出頭來。
如果超過地鼠持續出現的時間,地鼠就應該要重新回到洞中。所以這邊使用setTimeout()
,在經過地鼠出現持續時間time
後,移除hole
上的.up
,讓地鼠順利回家。在遊戲未結束(timeup = false
)且前一隻地鼠已經回家的狀態下,我們會重新呼叫peep()
,讓下一隻地鼠探頭出來。
let timeup = false;
function peep(){
const time = randTime(200,1000);
const hole = randomHole(holes);
hole.classList.add('up');
setTimeout(()=>{
hole.classList.remove('up');
if(!timeup) peep();
},time);
}
宣告變數score
給定初始值0,用來幫我們算分數。
startGame()
可以讓我們在點擊頁面上的button
後,立即開始打地鼠遊戲。
在方法的一開始,先把記分板(scoreBoard
)歸零,然後把代表結束遊戲與否的timeup
設成flase
,同時也把計算的分數(score
)歸零。
上面都做完後,呼叫peep()
讓地鼠開始探頭出來給我們打,接著利用setTimeout()
訂定打地鼠遊戲的時間限制,這邊設定遊戲時間為15秒(15000毫秒),15秒後把timeup
設為true
並提示使用者遊戲結束。
let score = 0;
function startGame(){
scoreBoard.textContent = 0;
timeup = false;
score = 0;
peep();
setTimeout(()=>{
timeup = true;
alert("時間到,遊戲結束!!!");
} , 15000);
}
最後一部分要來處理的是"當地鼠被點擊到,頁面上的得分要加1,然後地鼠要縮回洞中"。
我們可以為每一隻地鼠(mole
)註冊click event listener
以bonk()
作為event handler
。
在bonk()
裡,首先判斷點擊是不是"人為"的,如果是使用者點擊觸發,則e.isTrusted
會回傳true
,而如果是用像是script
之類的去觸發click event
,則會回傳false
並直接停止往下執行方法。
接著,在每一次點擊成功後,把分數(score
)加1並移除加到地鼠(mole
)上的.up
,讓地鼠回到洞中,之後更新頁面上的得分(scoreBoard
)就完成了。
function bonk(e){
if(!e.isTrusted) return; //cheater
score++;
this.classList.remove('up');
scoreBoard.textContent = score;
}
moles.forEach(mole => mole.addEventListener('click',bonk));
Math.random()
Math.round()
Math.floor()
setTimeout()
Element.classList
Node.textContent
Event.isTrusted