這兩天閒着無事,寫了幾個Web遊戲供自己打發時間。其中筆者感覺掃雷這個遊戲的實現中涉及到的知識點比較全面,故在此和大家分享一下。
先放效果圖:
首先我們要把基本的架子搭建起來:
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>原生js實現掃雷小遊戲</title>
<style>
*{margin:0;padding:0;}
.container{width:600px;height:600px;border:1px solid #ccc;margin:0 auto;}
.container .block{width:60px;height:60px;background:#abcdef;float:left;border:1px solid #f00;box-sizing:border-box;}
.container .block:active{background:#eee;}
.container .lei{}
/* 雷顯示 */
.container .show{background:url('tim.jpg') no-repeat center center/ 100% 100%;cursor:pointer;}
/* 周圍有雷的話顯示數字 */
.container .number{background:#fff;text-align:center;line-height:60px;}
/* 紅旗標記顯示 */
.container .biaoji{background:url('qz.jpg') no-repeat center center/ 100% 100%;cursor:pointer;}
.ts0{position:fixed;width:100%;height:100vh;top:0;left:0;z-index:888;background:rgba(255,255,255,0);display:none;}
.ts{position:fixed;width:200px;height:60px;background:rgb(255,255,255);top:0;right:0;left:0;bottom:0;margin:auto;z-index:999;display:none;}
.ts p{text-align:center;line-height:60px;}
</style>
</head>
<body>
<h4>mxc-雲風清</h4>
<div class="container">
</div>
<div class="ts0"></div>
<div class="ts">
<p>遊戲結束!</p>
</div>
</body>
<script>
</script>
</html>
這並沒有什麼好說的 —— 事實上,它非常簡單: 一個小標題(<h4>
)、一個“遊戲區域”(class="container"
)、一個結束時的提示框(class="ts"
)、以及襯托提示框的蒙層(class="ts0"
)。
但你會發現,所謂“遊戲區域”僅僅是一個擁有一個div的“空架子”?
這裏筆者採用了JS動態渲染div元素(一個一個小方塊),因爲後面“劇情”的需要,我們要將每個小方塊都設置【專屬id】(id名):
let container=document.querySelector('.container');
for(let i=0;i<10;i++){
for(let j=0;j<10;j++){
let divObj=document.createElement('div');
divObj.classList.add('block');
divObj.id='a'+i+'_'+j; //設置專屬id(id名)
container.appendChild(divObj);
}
}
我們很快便完成了這一代碼。這樣真的好嗎? 現在只是10X10,如果是100X100呢?在大數據量循環中執行appendChild
可不是一個明智的決定!
在筆者關於JS性能優化的文章中提到過一種解決方案:createDocumentFragment()
:
let container=document.querySelector('.container');
let fragment=document.createDocumentFragment();
for(let i=0;i<10;i++){
for(let j=0;j<10;j++){
let divObj=document.createElement('div');
divObj.classList.add('block');
divObj.id='a'+i+'_'+j; //設置專屬id(id名)
fragment.appendChild(divObj);
}
}
container.appendChild(fragment);
其實,原生中對元素的很多樣式上的操作從性能上考慮最終都可歸結到對classname的操作。
現在,我們畫面上有了下面的樣式:
該進行下一步了。
不過在此之前,我們需要找兩張圖片:
(我們不必關注他們的大小 —— 在css中改變即可)
做完上面的準備工作,正餐便開始了:
雷的分佈:隨機,且
上面筆者說:原生中對元素的很多樣式上的操作從性能上考慮最終都可歸結到對classname的操作,於是我們可以想到:爲每個需要變成“雷”的小方塊添加一個【特別的類】(.lei):
筆者採用循環實現:
let count=13;
let block=document.querySelectorAll('.block');
do{
let random=Math.floor(Math.random()*block.length);
block[random].classList.add('lei');
}while(document.querySelectorAll('.lei').length<=count);
看!這就體現出了do-while循環的好處了。
然後是處理鼠標事件:這將是最後一步了。
筆者發現身邊不少學前端的同學在做事件處理時直接上去就是onclick、oninput、onmouse… 小一點的文件還好說,稍微對性能很高的網頁或者說本遊戲設置爲10000X10000之後,瀏覽器就“吃不消”了 —— 因爲在初始時對函數的加載太過龐大!
函數的優化:我們爲什麼不能遵循【惰性加載模式】,在觸發時再去加載函數呢?
//block是前面獲取過的變量——獲取的“.block”,指“.container”中的每個小方塊
block.forEach(function(item){
item.onclick=function(){ //鼠標左鍵事件
leftClick(item);
}
item.oncontextmenu=function(e){ //鼠標右鍵事件
//阻止瀏覽器默認鼠標右鍵事件
e.preventDefault();
rightClick(item);
}
})
xxx.oncontextmenu鼠標右鍵事件注意一下:平時說的像禁止鼠標右鍵、禁止複製粘貼(其中的一種)、f12彈出控制檯都和這個有關
然後便是兩個函數了:
鼠標左鍵的對應函數到沒什麼——判斷點擊處是不是雷(有沒有.lei這個類)、如果不是,循環判斷點擊處周圍8個小方塊中有幾個雷並顯示出來:
function leftClick(obj){
if(obj.classList.contains('biaoji')){
return "";
}
if(obj.classList.contains('lei')){
let lei=document.querySelectorAll('.lei');
lei.forEach(function(item){
item.classList.add('show');
})
//結束時提示框及蒙層顯示
document.querySelector('.ts0').style.display='block';
document.querySelector('.ts').style.display='block';
setTimeout(function(){let yes=prompt("是否刷新刷新進入下一關?");if(yes === "是"){window.location.reload()}},1700);
}else{
obj.classList.add('number');
let ids=obj.id;
//下面三行作用是將【專屬id】(id名)拆分開來,以確定位置座標
let arr=ids.split('_');
let x=Number(arr[0].substr(1));
let y=Number(arr[1]);
let num=0;
for(let i=x-1;i<=x+1;i++){
for(let j=y-1;j<=y+1;j++){
let objs=document.querySelector('#a'+i+'_'+j);
//這裏也可以用“es6模板字符串”:let objs=document.querySelector(`#a${i}_${j}`);
if(objs && objs.classList.contains('lei')){
num++;
}
}
}
if(num){
obj.innerHTML=num;
}
if(num===0){
for(let i=x-1;i<=x+1;i++){
for(let j=y-1;j<=y+1;j++){
let objs=document.querySelector('#a'+i+'_'+j);
if(objs && !objs.classList.contains('number')){
leftClick(objs);
}
}
}
}
}
}
倒是鼠標右鍵:它是插紅旗的
這裏涉及到一個問題:插紅旗和掃到雷不一樣,你還可以取消。這問題說大不大,但也是個性能問題。
筆者查閱資料找到了一個函數:toggle()
—— 單次點擊時操作一個函數,偶次點擊時執行另一個函數(也可以在裏面放一個類名,即單次點擊時添加類名,偶次點擊時取消類名):
function rightClick(obj){
if(!obj.classList.contains('number')){
obj.classList.toggle('biaoji');
}
let biaoji=document.querySelectorAll('.biaoji.lei');
let biaoji2=document.querySelectorAll('.biaoji');
if(biaoji.length===count && count===biaoji2.length){
document.querySelector('.ts0').style.display='block';
document.querySelector('.ts').style.display='block';
}
}
最後,筆者將本文代碼上傳至了百度網盤,需要者請自行下載:
鏈接:https://pan.baidu.com/s/1BSUhmvelBGy0FdEvFKe6vA
提取碼:v0m8