JS實(shí)現(xiàn)羊了個(gè)羊小游戲?qū)嵗?/h1>
更新時(shí)間:2022年09月17日 11:46:32 作者:夕水
這篇文章主要為大家介紹了JS實(shí)現(xiàn)羊了個(gè)羊小游戲示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
引言
這兩天火爆全場的《羊了個(gè)羊》游戲,相信大家都玩過了,那么在玩這個(gè)游戲的同時(shí),我想大家都會(huì)好奇這個(gè)游戲的實(shí)現(xiàn),本文就帶大家使用css,html,js來實(shí)現(xiàn)一個(gè)動(dòng)物版的游戲。
首先我用到了2個(gè)插件,第一個(gè)插件就是flexible.js,這個(gè)插件就是對(duì)不同設(shè)備設(shè)置根元素字體大小,也就是一個(gè)移動(dòng)端的適配方案。
因?yàn)檫@里使用了rem布局,針對(duì)移動(dòng)端做了自適應(yīng),所以這里選擇采用rem布局方案。
rem布局方案
還有一個(gè)彈框插件,我很早自行實(shí)現(xiàn)的,就是popbox.js,關(guān)于這個(gè)插件,本文不打算講解實(shí)現(xiàn)原理,只講解一下使用原理:
popbox.js使用原理
ewConfirm({
title: "溫馨提示", //彈框標(biāo)題
content: "游戲結(jié)束,別灰心,你能行的!", //彈框內(nèi)容
sureText: "重新開始", //確認(rèn)按鈕文本
isClickModal:false, //點(diǎn)擊遮罩層是否關(guān)閉彈框
sure(context) {
context.close();
//點(diǎn)擊確認(rèn)按鈕執(zhí)行的邏輯
},//點(diǎn)擊確認(rèn)的事件回調(diào)
})
引入了這個(gè)js之后,會(huì)在window對(duì)象上綁定一個(gè)ewConfirm方法,這個(gè)方法傳入一個(gè)自定義對(duì)象,對(duì)象的屬性有title,content,sureText,cancelText,cancel,sure,isClickModal
這幾個(gè)屬性,當(dāng)然這里沒有用到cancel按鈕,所以不細(xì)講。
正如注釋所說,每個(gè)屬性代表的意思,這里不做贅述。
html代碼
<!DOCTYPE html>
<html lang="en">
<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>羊了個(gè)羊《動(dòng)物版》</title>
<link rel="stylesheet" href="./style.css" rel="external nofollow" >
</head>
<body>
</body>
<script src="https://www.eveningwater.com/static/plugin/popbox.min.js"></script>
<script src="https://www.eveningwater.com/test/demo/flexible.js"></script>
<script src="./script.js"></script>
</html>
可以看到html代碼是什么都沒有的,因?yàn)槔锩娴腄OM元素,我們都放在js代碼里面動(dòng)態(tài)生成了,所以script.js這里的代碼是核心,這個(gè)后續(xù)會(huì)講到,接下來看樣式代碼,也比較簡單。
樣式代碼
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body,
html {
height: 100%;
width: 100%;
overflow: hidden;
}
body {
background: url('https://www.eveningwater.com/my-web-projects/js/21/img/2.gif') no-repeat center / cover;
display: flex;
justify-content: center;
align-items: center;
}
.ew-box {
position: absolute;
width: 8rem;
height: 8rem;
}
.ew-box-item {
width: 1.6rem;
height: 1.6rem;
border-radius: 4px;
border: 1px solid #535455;
background-position: center;
background-size: cover;
background-repeat: no-repeat;
cursor: pointer;
transition: all .4s cubic-bezier(0.075, 0.82, 0.165, 1);
}
.ew-collection {
width: 8rem;
height: 2.4rem;
display: flex;
align-items: center;
justify-content: center;
padding: 0 1rem;
background: url('https://www.eveningwater.com/static/dist/20d6c430c2496590f224.jpg') no-repeat center/cover;
position: fixed;
margin: auto;
overflow: auto;
bottom: 10px;
}
.ew-collection > .ew-box-item {
margin-right: 0.3rem;
}
.ew-left-source,
.ew-right-source {
width: 2.6rem;
height: 1.2rem;
position: absolute;
top: 0;
}
.ew-left-source {
left: 0;
}
.ew-right-source {
right: 0;
}
.ew-shadow {
box-shadow: 0 0 50px 10px #535455 inset;
}
首先是通配選擇器'*'代表匹配所有的元素,并且設(shè)置樣式初始化,然后是html和body元素設(shè)置寬高為100%,并且隱藏溢出的內(nèi)容,然后給body元素設(shè)置了一個(gè)背景圖,并且body元素采用彈性盒子布局,水平垂直居中。
接下來是中間消除的盒子元素box,也很簡單就是設(shè)置定位,和固定寬高為8rem。
接下來是box-item,代表每一個(gè)塊元素,也就是消消樂的每一塊元素,接著羊了個(gè)羊底部有一個(gè)存儲(chǔ)選中塊元素的收集盒子元素,也就是ew-collection,然后是左右的看不到層級(jí)的卡牌容器元素。
最后就是為了讓塊元素看起來有層疊效果而添加的陰影效果。
javascript代碼
css核心代碼也就比較簡單,接下來我們來看javascript代碼。
導(dǎo)入圖片素材列表
在開始之前,我們需要先導(dǎo)入圖片素材列表,這里如下:
const globalImageList = [
'https://www.eveningwater.com/my-web-projects/jQuery/7/img/1.jpg',
'https://www.eveningwater.com/my-web-projects/jQuery/7/img/2.jpg',
'https://www.eveningwater.com/my-web-projects/jQuery/7/img/3.jpg',
'https://www.eveningwater.com/my-web-projects/jQuery/7/img/4.jpg',
'https://www.eveningwater.com/my-web-projects/jQuery/7/img/5.jpg',
'https://www.eveningwater.com/my-web-projects/jQuery/7/img/6.jpg',
'https://www.eveningwater.com/my-web-projects/jQuery/7/img/7.jpg',
'https://www.eveningwater.com/my-web-projects/jQuery/7/img/8.jpg',
'https://www.eveningwater.com/my-web-projects/jQuery/7/img/9.jpg',
'https://www.eveningwater.com/my-web-projects/jQuery/7/img/10.jpg'
]
然后在onload也就是頁面加載時(shí)調(diào)用我們封裝好的Game類,將這個(gè)素材列表傳入其中。如下:
window.onload = () => {
const game = new Game(globalImageList);
}
接下來,我們來看Game類的核心代碼吧,首先定義這個(gè)類:
class Game {
constructor(originSource, bindElement){
//核心代碼
}
}
這個(gè)類名有2個(gè)參數(shù),第一個(gè)參數(shù)就是素材列表,第二個(gè)參數(shù)則是綁定的DOM元素,默認(rèn)如果不傳入的話,就綁定到document.body上。也因此,我們?cè)跇?gòu)造函數(shù)里面初始化一些后續(xù)需要用到的變量。如下:
//constructor內(nèi)部
this.doc = document;
this.originSource = originSource;
this.bindElement = bindElement || this.doc.body;
// 存儲(chǔ)隨機(jī)打亂的元素
this.source = [];
// 存儲(chǔ)點(diǎn)擊的元素
this.temp = {};
// dom元素
this.box = null; //存儲(chǔ)消消樂塊元素的容器盒子元素
this.leftSource = null; //左邊素材容器元素
this.rightSource = null; //右邊素材容器元素
this.collection = null; //收集素材的容器元素
// 需要調(diào)用bind方法修改this指向
this.init().then(this.startHandler.bind(this)); //startHandler為游戲開始的核心邏輯函數(shù),init初始化方法
這里存儲(chǔ)了document對(duì)象,存儲(chǔ)了原始素材列表,以及綁定的dom元素,然后還定義了source用來存儲(chǔ)被打亂后的素材列表,以及temp用來存儲(chǔ)點(diǎn)擊的元素,方便做消除,添加陰影這些操作。
還有四個(gè)變量,其實(shí)也就是存儲(chǔ)dom元素的,如注釋所述。
接下來init方法就是做初始化的一些操作,這個(gè)方法返回一個(gè)Promise所以才能調(diào)用then方法,然后startHandler是游戲開始的核心邏輯函數(shù),這個(gè)后面會(huì)講到,注意這里有一個(gè)有意思的點(diǎn),那就是bind(this),因?yàn)樵趖hen方法內(nèi)部的this并不是指Game這個(gè)實(shí)例,所以需要調(diào)用bind方法修改this綁定,接下來我們來看init方法做了什么。
init() {
return new Promise(resolve => {
const template = `<div class="ew-box" id="ew-box"></div>
<div class="ew-left-source" id="ew-left-source"></div>
<div class="ew-right-source" id="ew-right-source"></div>
<div class="ew-collection" id="ew-collection"></div>`;
const div = this.create('div');
this.bindElement.insertBefore(div, document.body.firstChild);
this.createElement(div, template);
div.remove();
resolve();
})
}
很顯然這個(gè)方法如前所述返回了一個(gè)Promise,內(nèi)部定義了template模板代碼,也就是頁面的結(jié)構(gòu),然后調(diào)用create方法創(chuàng)建一個(gè)容器元素,并且向body元素的首個(gè)子元素之前插入這個(gè)元素,然后在這個(gè)容器元素之前插入創(chuàng)建好的頁面結(jié)構(gòu),刪除這個(gè)容器元素,并且resolve出去,從而達(dá)到將頁面元素添加到body元素內(nèi)部。這里涉及到了兩個(gè)工具函數(shù),我們分別來看看它們,如下:
create(name) {
return this.doc.createElement(name);
}
create方法其實(shí)也就是調(diào)用createElement方法來創(chuàng)建一個(gè)DOM元素,this.doc指的就是document文件對(duì)象,也就是說,create方法只是document.createElement的一個(gè)封裝而已。來看createElement方法。
createElement(el, str) {
return el.insertAdjacentHTML('beforebegin', str);
}
createElement方法傳入2個(gè)參數(shù),第一個(gè)參數(shù)是一個(gè)DOM元素,第二個(gè)參數(shù)是一個(gè)DOM元素字符串,表示在第一個(gè)DOM元素之前插入傳入的模板元素。這個(gè)方法可以參考code-segment。
startHandler函數(shù)實(shí)現(xiàn)
init方法說白了就是動(dòng)態(tài)創(chuàng)建元素的一個(gè)實(shí)現(xiàn),接下來就是startHandler函數(shù)的實(shí)現(xiàn)。
startHandler() {
this.box = this.$('#ew-box');
this.leftSource = this.$('#ew-left-source');
this.rightSource = this.$('#ew-right-source');
this.collection = this.$('#ew-collection');
this.resetHandler();
//后續(xù)還有邏輯
}
startHandler是核心實(shí)現(xiàn),所以不可能只有上面那么點(diǎn)代碼,但是我們要寫一步步的拆分,以上的代碼就做了2個(gè)邏輯,獲取DOM元素和重置。這里涉及到了一個(gè)$方法,如下:
$(selector, el = this.doc) {
return el.querySelector(selector);
}
$方法傳入2個(gè)參數(shù),第一個(gè)參數(shù)為選擇器,是一個(gè)字符串,第二個(gè)參數(shù)為DOM元素,實(shí)際上就是document.querySelector的一個(gè)封裝。當(dāng)然還有一個(gè)$$方法,類似,如下:
$$(selector, el = this.doc) {
return el.querySelectorAll(selector);
}
接下來是resetHandler方法,如下:
resetHandler() {
this.box.innerHTML = '';
this.leftSource.innerHTML = '';
this.rightSource.innerHTML = '';
this.collection.innerHTML = '';
this.temp = {};
this.source = [];
}
可以看到resetHandler方法確實(shí)是如其定義的那樣,就是做重置的,我們要重置用到的數(shù)據(jù)以及DOM元素的子節(jié)點(diǎn)。
讓我們繼續(xù),在startHandler也就是resetHandler方法的后面,添加這樣的代碼:
startHandler() {
this.box = this.$('#ew-box');
this.leftSource = this.$('#ew-left-source');
this.rightSource = this.$('#ew-right-source');
this.collection = this.$('#ew-collection');
this.resetHandler();
for (let i = 0; i < 12; i++) {
this.originSource.forEach((src, index) => {
this.source.push({
src,
index
})
})
}
this.source = this.randomList(this.source);
//后續(xù)還有邏輯
}
可以看到這里實(shí)際上就是對(duì)素材數(shù)據(jù)做了一個(gè)添加和轉(zhuǎn)換操作,randomList方法顧名思義,就是打亂素材列表的順序。
randomList 工具方法
讓我們來看這個(gè)工具方法的源碼:
/**
* 打亂順序
* @param {*} arr
* @returns
*/
randomList(arr) {
const newArr = [...arr];
for (let i = newArr.length - 1; i >= 0; i--) {
const index = Math.floor(Math.random() * i + 1);
const next = newArr[index];
newArr[index] = newArr[i];
newArr[i] = next;
}
return newArr;
}
這個(gè)函數(shù)的作用就是將素材列表隨機(jī)打亂以達(dá)到隨機(jī)的目的,接下來,讓我們繼續(xù)。
startHandler() {
this.box = this.$('#ew-box');
this.leftSource = this.$('#ew-left-source');
this.rightSource = this.$('#ew-right-source');
this.collection = this.$('#ew-collection');
this.resetHandler();
for (let i = 0; i < 12; i++) {
this.originSource.forEach((src, index) => {
this.source.push({
src,
index
})
})
}
this.source = this.randomList(this.source);
//后續(xù)還有邏輯
for (let k = 5; k > 0; k--) {
for (let i = 0; i < 5; i++) {
for (let j = 0; j < k; j++) {
const item = this.create('div');
item.setAttribute('x', i);
item.setAttribute('y', j);
item.setAttribute('z', k);
item.className = `ew-box-item ew-box-${i}-${j}-${k}`;
item.style.position = 'absolute';
const image = this.source.splice(0, 1);
// 1.44為item設(shè)置的寬度與高度
item.style.left = 1.44 * j + Math.random() * .1 * k + 'rem';
item.style.top = 1.44 * i + Math.random() * .1 * k + 'rem';
item.setAttribute('index', image[0].index);
item.style.backgroundImage = `url(${image[0].src})`;
const clickHandler = () => {
// 如果是在收集框里是不能夠點(diǎn)擊的
if(item.parentElement.className === 'ew-collection'){
return;
}
// 沒有陰影效果的元素才能夠點(diǎn)擊
if (!item.classList.contains('ew-shadow')) {
const currentIndex = item.getAttribute('index');
if (this.temp[currentIndex]) {
this.temp[currentIndex] += 1;
} else {
this.temp[currentIndex] = 1;
}
item.style.position = 'static';
this.collection.appendChild(item);
// 重置陰影效果
this.$$('.ew-box-item',this.box).forEach(item => item.classList.remove('ew-shadow'));
this.createShadow();
// 等于3個(gè)就消除掉
if (this.temp[currentIndex] === 3) {
this.$$(`div[index="${currentIndex}"]`, this.collection).forEach(item => item.remove());
this.temp[currentIndex] = 0;
}
let num = 0;
for (let i in this.temp) {
num += this.temp[i];
}
if (num > 7) {
item.removeEventListener('click', clickHandler);
this.gameOver();
}
}
}
item.addEventListener('click', clickHandler)
this.box.append(item);
}
}
}
}
這里的代碼很長,但是總結(jié)下來就二點(diǎn),添加塊元素,并為每個(gè)塊元素綁定點(diǎn)擊事件。我們知道羊了個(gè)羊每一個(gè)消除的塊元素都會(huì)有層疊的效果,那么我們這里也要實(shí)現(xiàn)同樣的效果,如何實(shí)現(xiàn)呢?
答案就是定位,我們應(yīng)該知道定位會(huì)分為層級(jí)關(guān)系,層級(jí)越高就會(huì)占上面,這里也是采用同樣的道理,這里之所以用3個(gè)循環(huán),就是盒子元素是分成5行5列的,所以也就是為什么循環(huán)是5的原因。
然后在循環(huán)內(nèi)部,我們就是創(chuàng)建每一個(gè)塊元素,每個(gè)元素都設(shè)置了x,y,z三個(gè)屬性,并且還添加了ew-box-${i}-${j}-${k}
類名,很顯然這里的x,y,z屬性和這個(gè)類名關(guān)聯(lián)上了,這方便我們后續(xù)對(duì)元素進(jìn)行操作。
同樣的每個(gè)塊元素我們也設(shè)置了樣式,類名是'ew-box-item',同樣的每個(gè)塊元素也設(shè)置為絕對(duì)定位。
PS: 大家可能有些好奇為什么每個(gè)元素我都加一個(gè)'ew-'的前綴,其實(shí)也就是我個(gè)人喜歡給自己寫的代碼加的一個(gè)前綴,代表這是我自己寫的代碼的一個(gè)標(biāo)志。
接下來從素材列表中取出單個(gè)素材,取出的數(shù)據(jù)結(jié)構(gòu)應(yīng)該是{ src:'圖片路徑',index:'索引值' }這樣。然后將該元素設(shè)置背景圖,就是素材列表的圖片路徑,以及index屬性,還有l(wèi)eft和top偏移值,這里的left和top偏移值之所以是隨機(jī)的,也就是因?yàn)槊恳粋€(gè)塊元素都是隨機(jī)的。
接下來是clickHandler也就是點(diǎn)擊塊元素執(zhí)行的回調(diào),這個(gè)我們先不詳細(xì)敘述,我們繼續(xù)往后看,就是為該元素添加事件,利用addEventListener方法,并且將塊元素添加到box盒子元素中。
clickHandler函數(shù)內(nèi)部
讓我們繼續(xù)來看clickHandler函數(shù)內(nèi)部。
首先這里有這樣一個(gè)判斷:
if(item.parentElement.className === 'ew-collection'){
return;
}
很簡單,當(dāng)我們的收集框里面點(diǎn)擊該元素,是不能觸發(fā)點(diǎn)擊事件的,所以這里要做判斷。
然后又是一個(gè)判斷,有陰影效果的都是多了一個(gè)類名'ew-shadow',有陰影效果代表它的層級(jí)最小,被疊加遮蓋住了,所以無法被點(diǎn)擊。
接下來獲取當(dāng)前點(diǎn)擊塊元素的index索引值,這也是為什么在添加塊元素之前會(huì)設(shè)置一個(gè)index屬性的原因。
然后判斷點(diǎn)擊的次數(shù),如果點(diǎn)擊的是同一個(gè),則在temp對(duì)象里面存儲(chǔ)點(diǎn)擊的索引值,否則點(diǎn)擊的是不同的塊元素,索引值就是1。
然后將該元素的定位設(shè)置為靜態(tài)定位,也就是默認(rèn)值,并且添加到收集框容器元素當(dāng)中去。
createShadow方法
接下來就是重置陰影效果,并且重新添加陰影效果。這里有一個(gè)createShadow方法,讓我們來揭開它的神秘面紗。如下:
createShadow(){
this.$$('.ew-box-item',this.box).forEach((item,index) => {
let x = item.getAttribute('x'),
y = item.getAttribute('y'),
z = item.getAttribute('z'),
ele = this.$$(`.ew-box-${x}-${y}-${z - 1}`),
eleOther = this.$$(`.ew-box-${x + 1}-${y + 1}-${z - 1}`);
if (ele.length || eleOther.length) {
item.classList.add('ew-shadow');
}
})
}
這里很顯然通過獲取x,y,z屬性設(shè)置的類名來確定是否需要添加陰影,因?yàn)橥ㄟ^這三個(gè)屬性值可以確定元素的層級(jí),如果不是在最上方,就能夠獲取到該元素,所以就添加陰影。注意$$方法返回的是一個(gè)NodeList集合,所以可以拿到length屬性。
接下來就是通過存儲(chǔ)的索引值等于3個(gè),代表選中了3個(gè)相同的塊,那就要從收集框里面移除掉該三個(gè)塊元素,并且重置對(duì)應(yīng)的index索引值為0。
接下來的for...in循環(huán)所做的操作當(dāng)然是統(tǒng)計(jì)收集框里面的塊元素,如果達(dá)到了7個(gè)代表槽位滿了,然后游戲結(jié)束,并且移除塊元素的點(diǎn)擊事件。我們來看游戲結(jié)束這個(gè)方法的實(shí)現(xiàn):
gameOver() {
const self = this;
ewConfirm({
title: "溫馨提示",
content: "游戲結(jié)束,別灰心,你能行的!",
sureText: "重新開始",
isClickModal:false,
sure(context) {
context.close();
self.startHandler();
}
})
}
這也是最開始提到的彈框插件的用法,在點(diǎn)擊確認(rèn)的回調(diào)里面調(diào)用startHandler方法表示重新開始游戲,這沒什么好說的。
到這里,我們實(shí)現(xiàn)了中間盒子元素的每一個(gè)塊元素與槽位容器元素的對(duì)應(yīng)邏輯,接下來還有2點(diǎn),那就是被遮蓋看不到層級(jí)的兩邊塊元素集合。所以繼續(xù)看startHandler后續(xù)的邏輯。
startHandler后續(xù)的邏輯
startHandler() {
this.box = this.$('#ew-box');
this.leftSource = this.$('#ew-left-source');
this.rightSource = this.$('#ew-right-source');
this.collection = this.$('#ew-collection');
this.resetHandler();
for (let i = 0; i < 12; i++) {
this.originSource.forEach((src, index) => {
this.source.push({
src,
index
})
})
}
this.source = this.randomList(this.source);
for (let k = 5; k > 0; k--) {
for (let i = 0; i < 5; i++) {
for (let j = 0; j < k; j++) {
const item = this.create('div');
item.setAttribute('x', i);
item.setAttribute('y', j);
item.setAttribute('z', k);
item.className = `ew-box-item ew-box-${i}-${j}-${k}`;
item.style.position = 'absolute';
const image = this.source.splice(0, 1);
// 1.44為item設(shè)置的寬度與高度
item.style.left = 1.44 * j + Math.random() * .1 * k + 'rem';
item.style.top = 1.44 * i + Math.random() * .1 * k + 'rem';
item.setAttribute('index', image[0].index);
item.style.backgroundImage = `url(${image[0].src})`;
const clickHandler = () => {
// 如果是在收集框里是不能夠點(diǎn)擊的
if(item.parentElement.className === 'ew-collection'){
return;
}
// 沒有陰影效果的元素才能夠點(diǎn)擊
if (!item.classList.contains('ew-shadow')) {
const currentIndex = item.getAttribute('index');
if (this.temp[currentIndex]) {
this.temp[currentIndex] += 1;
} else {
this.temp[currentIndex] = 1;
}
item.style.position = 'static';
this.collection.appendChild(item);
// 重置陰影效果
this.$$('.ew-box-item',this.box).forEach(item => item.classList.remove('ew-shadow'));
this.createShadow();
// 等于3個(gè)就消除掉
if (this.temp[currentIndex] === 3) {
this.$$(`div[index="${currentIndex}"]`, this.collection).forEach(item => item.remove());
this.temp[currentIndex] = 0;
}
let num = 0;
for (let i in this.temp) {
num += this.temp[i];
}
if (num >= 7) {
item.removeEventListener('click', clickHandler);
this.gameOver();
}
}
}
item.addEventListener('click', clickHandler)
this.box.append(item);
}
}
}
//從這里開始分析
let len = Math.ceil(this.source.length / 2);
this.source.forEach((item, index) => {
let div = this.create('div');
div.classList.add('ew-box-item')
div.setAttribute('index', item.index);
div.style.backgroundImage = `url(${item.src})`;
div.style.position = 'absolute';
div.style.top = 0;
if (index > len) {
div.style.right = `${(5 * (index - len)) / 100}rem`;
this.rightSource.appendChild(div);
} else {
div.style.left = `${(5 * index) / 100}rem`;
this.leftSource.appendChild(div)
}
const clickHandler = () => {
if(div.parentElement.className === 'ew-collection'){
return;
}
const currentIndex = div.getAttribute('index');
if (this.temp[currentIndex]) {
this.temp[currentIndex] += 1;
} else {
this.temp[currentIndex] = 1;
}
div.style.position = 'static';
this.collection.appendChild(div);
if (this.temp[currentIndex] === 3) {
this.$$(`div[index="${currentIndex}"]`, this.collection).forEach(item => item.remove());
this.temp[currentIndex] = 0;
}
let num = 0;
for (let i in this.temp) {
num += this.temp[i];
}
if (num >= 7) {
div.removeEventListener('click', clickHandler);
this.gameOver();
}
}
div.addEventListener('click', clickHandler);
});
this.createShadow();
}
這里很顯然取的是source素材列表的一般來分別生成對(duì)應(yīng)的左右素材列表,同理,這里面的塊元素點(diǎn)擊事件邏輯應(yīng)該是和塊容器元素里面的邏輯是很相似的,所以沒什么好說的。我們主要看以下這段代碼:
let div = this.create('div');
div.classList.add('ew-box-item');
div.setAttribute('index', item.index);
div.style.backgroundImage = `url(${item.src})`;
div.style.position = 'absolute';
div.style.top = 0;
if (index > len) {
div.style.right = `${(5 * (index - len)) / 100}rem`;
this.rightSource.appendChild(div);
} else {
div.style.left = `${(5 * index) / 100}rem`;
this.leftSource.appendChild(div)
}
其實(shí)這里也很好理解,也就是同樣的創(chuàng)建塊元素,這里根據(jù)index > len來確定是添加到右邊素材容器元素還是左邊素材元素,并且它們的top偏移量應(yīng)該是一致的,主要不同在left和right而已,計(jì)算方式也很簡單。
注意這里是不需要設(shè)置x,y,z屬性的,因?yàn)椴恍枰玫皆O(shè)置陰影的函數(shù)。
到此為止,我們一個(gè)《羊了個(gè)羊——動(dòng)物版》的小游戲就完成了。
如有興趣可以參考源碼。
更多關(guān)于jS 羊了個(gè)羊小游戲的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
-
js前端表單數(shù)據(jù)處理表單數(shù)據(jù)校驗(yàn)
這篇文章主要為大家介紹了js前端表單數(shù)據(jù)處理表單數(shù)據(jù)校驗(yàn)示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪 2022-07-07
-
前端AI機(jī)器學(xué)習(xí)在瀏覽器中訓(xùn)練模型
這篇文章主要為大家介紹了前端AI機(jī)器學(xué)習(xí)在瀏覽器中訓(xùn)練模型的示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪 2022-07-07
-
微信小程序獲取循環(huán)元素id以及wx.login登錄操作
這篇文章主要介紹了微信小程序獲取循環(huán)元素id以及wx.login登錄操作的相關(guān)資料,這里提供實(shí)例幫助大家實(shí)現(xiàn)該功能,需要的朋友可以參考下 2017-08-08
-
微信小程序 定位到當(dāng)前城市實(shí)現(xiàn)實(shí)例代碼
這篇文章主要介紹了微信小程序 定位到當(dāng)前城市實(shí)現(xiàn)實(shí)例代碼的相關(guān)資料,需要的朋友可以參考下 2017-02-02
-
網(wǎng)站申請(qǐng)不到支付寶接口、微信接口,免接口收款實(shí)現(xiàn)方式幾種解決辦法
這篇文章主要介紹了網(wǎng)站申請(qǐng)不到支付寶接口、微信接口,免接口收款實(shí)現(xiàn)方式幾種解決辦法的相關(guān)資料,需要的朋友可以參考下 2016-12-12
-
JS輕量級(jí)函數(shù)式編程實(shí)現(xiàn)XDM二
這篇文章主要為大家介紹了JS函數(shù)式編程實(shí)現(xiàn)XDM示例詳解第2/3篇,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪 2022-06-06
最新評(píng)論
引言
這兩天火爆全場的《羊了個(gè)羊》游戲,相信大家都玩過了,那么在玩這個(gè)游戲的同時(shí),我想大家都會(huì)好奇這個(gè)游戲的實(shí)現(xiàn),本文就帶大家使用css,html,js來實(shí)現(xiàn)一個(gè)動(dòng)物版的游戲。
首先我用到了2個(gè)插件,第一個(gè)插件就是flexible.js,這個(gè)插件就是對(duì)不同設(shè)備設(shè)置根元素字體大小,也就是一個(gè)移動(dòng)端的適配方案。
因?yàn)檫@里使用了rem布局,針對(duì)移動(dòng)端做了自適應(yīng),所以這里選擇采用rem布局方案。
rem布局方案
還有一個(gè)彈框插件,我很早自行實(shí)現(xiàn)的,就是popbox.js,關(guān)于這個(gè)插件,本文不打算講解實(shí)現(xiàn)原理,只講解一下使用原理:
popbox.js使用原理
ewConfirm({ title: "溫馨提示", //彈框標(biāo)題 content: "游戲結(jié)束,別灰心,你能行的!", //彈框內(nèi)容 sureText: "重新開始", //確認(rèn)按鈕文本 isClickModal:false, //點(diǎn)擊遮罩層是否關(guān)閉彈框 sure(context) { context.close(); //點(diǎn)擊確認(rèn)按鈕執(zhí)行的邏輯 },//點(diǎn)擊確認(rèn)的事件回調(diào) })
引入了這個(gè)js之后,會(huì)在window對(duì)象上綁定一個(gè)ewConfirm方法,這個(gè)方法傳入一個(gè)自定義對(duì)象,對(duì)象的屬性有title,content,sureText,cancelText,cancel,sure,isClickModal
這幾個(gè)屬性,當(dāng)然這里沒有用到cancel按鈕,所以不細(xì)講。
正如注釋所說,每個(gè)屬性代表的意思,這里不做贅述。
html代碼
<!DOCTYPE html> <html lang="en"> <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>羊了個(gè)羊《動(dòng)物版》</title> <link rel="stylesheet" href="./style.css" rel="external nofollow" > </head> <body> </body> <script src="https://www.eveningwater.com/static/plugin/popbox.min.js"></script> <script src="https://www.eveningwater.com/test/demo/flexible.js"></script> <script src="./script.js"></script> </html>
可以看到html代碼是什么都沒有的,因?yàn)槔锩娴腄OM元素,我們都放在js代碼里面動(dòng)態(tài)生成了,所以script.js這里的代碼是核心,這個(gè)后續(xù)會(huì)講到,接下來看樣式代碼,也比較簡單。
樣式代碼
* { margin: 0; padding: 0; box-sizing: border-box; } body, html { height: 100%; width: 100%; overflow: hidden; } body { background: url('https://www.eveningwater.com/my-web-projects/js/21/img/2.gif') no-repeat center / cover; display: flex; justify-content: center; align-items: center; } .ew-box { position: absolute; width: 8rem; height: 8rem; } .ew-box-item { width: 1.6rem; height: 1.6rem; border-radius: 4px; border: 1px solid #535455; background-position: center; background-size: cover; background-repeat: no-repeat; cursor: pointer; transition: all .4s cubic-bezier(0.075, 0.82, 0.165, 1); } .ew-collection { width: 8rem; height: 2.4rem; display: flex; align-items: center; justify-content: center; padding: 0 1rem; background: url('https://www.eveningwater.com/static/dist/20d6c430c2496590f224.jpg') no-repeat center/cover; position: fixed; margin: auto; overflow: auto; bottom: 10px; } .ew-collection > .ew-box-item { margin-right: 0.3rem; } .ew-left-source, .ew-right-source { width: 2.6rem; height: 1.2rem; position: absolute; top: 0; } .ew-left-source { left: 0; } .ew-right-source { right: 0; } .ew-shadow { box-shadow: 0 0 50px 10px #535455 inset; }
首先是通配選擇器'*'代表匹配所有的元素,并且設(shè)置樣式初始化,然后是html和body元素設(shè)置寬高為100%,并且隱藏溢出的內(nèi)容,然后給body元素設(shè)置了一個(gè)背景圖,并且body元素采用彈性盒子布局,水平垂直居中。
接下來是中間消除的盒子元素box,也很簡單就是設(shè)置定位,和固定寬高為8rem。
接下來是box-item,代表每一個(gè)塊元素,也就是消消樂的每一塊元素,接著羊了個(gè)羊底部有一個(gè)存儲(chǔ)選中塊元素的收集盒子元素,也就是ew-collection,然后是左右的看不到層級(jí)的卡牌容器元素。
最后就是為了讓塊元素看起來有層疊效果而添加的陰影效果。
javascript代碼
css核心代碼也就比較簡單,接下來我們來看javascript代碼。
導(dǎo)入圖片素材列表
在開始之前,我們需要先導(dǎo)入圖片素材列表,這里如下:
const globalImageList = [ 'https://www.eveningwater.com/my-web-projects/jQuery/7/img/1.jpg', 'https://www.eveningwater.com/my-web-projects/jQuery/7/img/2.jpg', 'https://www.eveningwater.com/my-web-projects/jQuery/7/img/3.jpg', 'https://www.eveningwater.com/my-web-projects/jQuery/7/img/4.jpg', 'https://www.eveningwater.com/my-web-projects/jQuery/7/img/5.jpg', 'https://www.eveningwater.com/my-web-projects/jQuery/7/img/6.jpg', 'https://www.eveningwater.com/my-web-projects/jQuery/7/img/7.jpg', 'https://www.eveningwater.com/my-web-projects/jQuery/7/img/8.jpg', 'https://www.eveningwater.com/my-web-projects/jQuery/7/img/9.jpg', 'https://www.eveningwater.com/my-web-projects/jQuery/7/img/10.jpg' ]
然后在onload也就是頁面加載時(shí)調(diào)用我們封裝好的Game類,將這個(gè)素材列表傳入其中。如下:
window.onload = () => { const game = new Game(globalImageList); }
接下來,我們來看Game類的核心代碼吧,首先定義這個(gè)類:
class Game { constructor(originSource, bindElement){ //核心代碼 } }
這個(gè)類名有2個(gè)參數(shù),第一個(gè)參數(shù)就是素材列表,第二個(gè)參數(shù)則是綁定的DOM元素,默認(rèn)如果不傳入的話,就綁定到document.body上。也因此,我們?cè)跇?gòu)造函數(shù)里面初始化一些后續(xù)需要用到的變量。如下:
//constructor內(nèi)部 this.doc = document; this.originSource = originSource; this.bindElement = bindElement || this.doc.body; // 存儲(chǔ)隨機(jī)打亂的元素 this.source = []; // 存儲(chǔ)點(diǎn)擊的元素 this.temp = {}; // dom元素 this.box = null; //存儲(chǔ)消消樂塊元素的容器盒子元素 this.leftSource = null; //左邊素材容器元素 this.rightSource = null; //右邊素材容器元素 this.collection = null; //收集素材的容器元素 // 需要調(diào)用bind方法修改this指向 this.init().then(this.startHandler.bind(this)); //startHandler為游戲開始的核心邏輯函數(shù),init初始化方法
這里存儲(chǔ)了document對(duì)象,存儲(chǔ)了原始素材列表,以及綁定的dom元素,然后還定義了source用來存儲(chǔ)被打亂后的素材列表,以及temp用來存儲(chǔ)點(diǎn)擊的元素,方便做消除,添加陰影這些操作。
還有四個(gè)變量,其實(shí)也就是存儲(chǔ)dom元素的,如注釋所述。
接下來init方法就是做初始化的一些操作,這個(gè)方法返回一個(gè)Promise所以才能調(diào)用then方法,然后startHandler是游戲開始的核心邏輯函數(shù),這個(gè)后面會(huì)講到,注意這里有一個(gè)有意思的點(diǎn),那就是bind(this),因?yàn)樵趖hen方法內(nèi)部的this并不是指Game這個(gè)實(shí)例,所以需要調(diào)用bind方法修改this綁定,接下來我們來看init方法做了什么。
init() { return new Promise(resolve => { const template = `<div class="ew-box" id="ew-box"></div> <div class="ew-left-source" id="ew-left-source"></div> <div class="ew-right-source" id="ew-right-source"></div> <div class="ew-collection" id="ew-collection"></div>`; const div = this.create('div'); this.bindElement.insertBefore(div, document.body.firstChild); this.createElement(div, template); div.remove(); resolve(); }) }
很顯然這個(gè)方法如前所述返回了一個(gè)Promise,內(nèi)部定義了template模板代碼,也就是頁面的結(jié)構(gòu),然后調(diào)用create方法創(chuàng)建一個(gè)容器元素,并且向body元素的首個(gè)子元素之前插入這個(gè)元素,然后在這個(gè)容器元素之前插入創(chuàng)建好的頁面結(jié)構(gòu),刪除這個(gè)容器元素,并且resolve出去,從而達(dá)到將頁面元素添加到body元素內(nèi)部。這里涉及到了兩個(gè)工具函數(shù),我們分別來看看它們,如下:
create(name) { return this.doc.createElement(name); }
create方法其實(shí)也就是調(diào)用createElement方法來創(chuàng)建一個(gè)DOM元素,this.doc指的就是document文件對(duì)象,也就是說,create方法只是document.createElement的一個(gè)封裝而已。來看createElement方法。
createElement(el, str) { return el.insertAdjacentHTML('beforebegin', str); }
createElement方法傳入2個(gè)參數(shù),第一個(gè)參數(shù)是一個(gè)DOM元素,第二個(gè)參數(shù)是一個(gè)DOM元素字符串,表示在第一個(gè)DOM元素之前插入傳入的模板元素。這個(gè)方法可以參考code-segment。
startHandler函數(shù)實(shí)現(xiàn)
init方法說白了就是動(dòng)態(tài)創(chuàng)建元素的一個(gè)實(shí)現(xiàn),接下來就是startHandler函數(shù)的實(shí)現(xiàn)。
startHandler() { this.box = this.$('#ew-box'); this.leftSource = this.$('#ew-left-source'); this.rightSource = this.$('#ew-right-source'); this.collection = this.$('#ew-collection'); this.resetHandler(); //后續(xù)還有邏輯 }
startHandler是核心實(shí)現(xiàn),所以不可能只有上面那么點(diǎn)代碼,但是我們要寫一步步的拆分,以上的代碼就做了2個(gè)邏輯,獲取DOM元素和重置。這里涉及到了一個(gè)$方法,如下:
$(selector, el = this.doc) { return el.querySelector(selector); }
$方法傳入2個(gè)參數(shù),第一個(gè)參數(shù)為選擇器,是一個(gè)字符串,第二個(gè)參數(shù)為DOM元素,實(shí)際上就是document.querySelector的一個(gè)封裝。當(dāng)然還有一個(gè)$$方法,類似,如下:
$$(selector, el = this.doc) { return el.querySelectorAll(selector); }
接下來是resetHandler方法,如下:
resetHandler() { this.box.innerHTML = ''; this.leftSource.innerHTML = ''; this.rightSource.innerHTML = ''; this.collection.innerHTML = ''; this.temp = {}; this.source = []; }
可以看到resetHandler方法確實(shí)是如其定義的那樣,就是做重置的,我們要重置用到的數(shù)據(jù)以及DOM元素的子節(jié)點(diǎn)。
讓我們繼續(xù),在startHandler也就是resetHandler方法的后面,添加這樣的代碼:
startHandler() { this.box = this.$('#ew-box'); this.leftSource = this.$('#ew-left-source'); this.rightSource = this.$('#ew-right-source'); this.collection = this.$('#ew-collection'); this.resetHandler(); for (let i = 0; i < 12; i++) { this.originSource.forEach((src, index) => { this.source.push({ src, index }) }) } this.source = this.randomList(this.source); //后續(xù)還有邏輯 }
可以看到這里實(shí)際上就是對(duì)素材數(shù)據(jù)做了一個(gè)添加和轉(zhuǎn)換操作,randomList方法顧名思義,就是打亂素材列表的順序。
randomList 工具方法
讓我們來看這個(gè)工具方法的源碼:
/** * 打亂順序 * @param {*} arr * @returns */ randomList(arr) { const newArr = [...arr]; for (let i = newArr.length - 1; i >= 0; i--) { const index = Math.floor(Math.random() * i + 1); const next = newArr[index]; newArr[index] = newArr[i]; newArr[i] = next; } return newArr; }
這個(gè)函數(shù)的作用就是將素材列表隨機(jī)打亂以達(dá)到隨機(jī)的目的,接下來,讓我們繼續(xù)。
startHandler() { this.box = this.$('#ew-box'); this.leftSource = this.$('#ew-left-source'); this.rightSource = this.$('#ew-right-source'); this.collection = this.$('#ew-collection'); this.resetHandler(); for (let i = 0; i < 12; i++) { this.originSource.forEach((src, index) => { this.source.push({ src, index }) }) } this.source = this.randomList(this.source); //后續(xù)還有邏輯 for (let k = 5; k > 0; k--) { for (let i = 0; i < 5; i++) { for (let j = 0; j < k; j++) { const item = this.create('div'); item.setAttribute('x', i); item.setAttribute('y', j); item.setAttribute('z', k); item.className = `ew-box-item ew-box-${i}-${j}-${k}`; item.style.position = 'absolute'; const image = this.source.splice(0, 1); // 1.44為item設(shè)置的寬度與高度 item.style.left = 1.44 * j + Math.random() * .1 * k + 'rem'; item.style.top = 1.44 * i + Math.random() * .1 * k + 'rem'; item.setAttribute('index', image[0].index); item.style.backgroundImage = `url(${image[0].src})`; const clickHandler = () => { // 如果是在收集框里是不能夠點(diǎn)擊的 if(item.parentElement.className === 'ew-collection'){ return; } // 沒有陰影效果的元素才能夠點(diǎn)擊 if (!item.classList.contains('ew-shadow')) { const currentIndex = item.getAttribute('index'); if (this.temp[currentIndex]) { this.temp[currentIndex] += 1; } else { this.temp[currentIndex] = 1; } item.style.position = 'static'; this.collection.appendChild(item); // 重置陰影效果 this.$$('.ew-box-item',this.box).forEach(item => item.classList.remove('ew-shadow')); this.createShadow(); // 等于3個(gè)就消除掉 if (this.temp[currentIndex] === 3) { this.$$(`div[index="${currentIndex}"]`, this.collection).forEach(item => item.remove()); this.temp[currentIndex] = 0; } let num = 0; for (let i in this.temp) { num += this.temp[i]; } if (num > 7) { item.removeEventListener('click', clickHandler); this.gameOver(); } } } item.addEventListener('click', clickHandler) this.box.append(item); } } } }
這里的代碼很長,但是總結(jié)下來就二點(diǎn),添加塊元素,并為每個(gè)塊元素綁定點(diǎn)擊事件。我們知道羊了個(gè)羊每一個(gè)消除的塊元素都會(huì)有層疊的效果,那么我們這里也要實(shí)現(xiàn)同樣的效果,如何實(shí)現(xiàn)呢?
答案就是定位,我們應(yīng)該知道定位會(huì)分為層級(jí)關(guān)系,層級(jí)越高就會(huì)占上面,這里也是采用同樣的道理,這里之所以用3個(gè)循環(huán),就是盒子元素是分成5行5列的,所以也就是為什么循環(huán)是5的原因。
然后在循環(huán)內(nèi)部,我們就是創(chuàng)建每一個(gè)塊元素,每個(gè)元素都設(shè)置了x,y,z三個(gè)屬性,并且還添加了ew-box-${i}-${j}-${k}
類名,很顯然這里的x,y,z屬性和這個(gè)類名關(guān)聯(lián)上了,這方便我們后續(xù)對(duì)元素進(jìn)行操作。
同樣的每個(gè)塊元素我們也設(shè)置了樣式,類名是'ew-box-item',同樣的每個(gè)塊元素也設(shè)置為絕對(duì)定位。
PS: 大家可能有些好奇為什么每個(gè)元素我都加一個(gè)'ew-'的前綴,其實(shí)也就是我個(gè)人喜歡給自己寫的代碼加的一個(gè)前綴,代表這是我自己寫的代碼的一個(gè)標(biāo)志。
接下來從素材列表中取出單個(gè)素材,取出的數(shù)據(jù)結(jié)構(gòu)應(yīng)該是{ src:'圖片路徑',index:'索引值' }這樣。然后將該元素設(shè)置背景圖,就是素材列表的圖片路徑,以及index屬性,還有l(wèi)eft和top偏移值,這里的left和top偏移值之所以是隨機(jī)的,也就是因?yàn)槊恳粋€(gè)塊元素都是隨機(jī)的。
接下來是clickHandler也就是點(diǎn)擊塊元素執(zhí)行的回調(diào),這個(gè)我們先不詳細(xì)敘述,我們繼續(xù)往后看,就是為該元素添加事件,利用addEventListener方法,并且將塊元素添加到box盒子元素中。
clickHandler函數(shù)內(nèi)部
讓我們繼續(xù)來看clickHandler函數(shù)內(nèi)部。
首先這里有這樣一個(gè)判斷:
if(item.parentElement.className === 'ew-collection'){ return; }
很簡單,當(dāng)我們的收集框里面點(diǎn)擊該元素,是不能觸發(fā)點(diǎn)擊事件的,所以這里要做判斷。
然后又是一個(gè)判斷,有陰影效果的都是多了一個(gè)類名'ew-shadow',有陰影效果代表它的層級(jí)最小,被疊加遮蓋住了,所以無法被點(diǎn)擊。
接下來獲取當(dāng)前點(diǎn)擊塊元素的index索引值,這也是為什么在添加塊元素之前會(huì)設(shè)置一個(gè)index屬性的原因。
然后判斷點(diǎn)擊的次數(shù),如果點(diǎn)擊的是同一個(gè),則在temp對(duì)象里面存儲(chǔ)點(diǎn)擊的索引值,否則點(diǎn)擊的是不同的塊元素,索引值就是1。
然后將該元素的定位設(shè)置為靜態(tài)定位,也就是默認(rèn)值,并且添加到收集框容器元素當(dāng)中去。
createShadow方法
接下來就是重置陰影效果,并且重新添加陰影效果。這里有一個(gè)createShadow方法,讓我們來揭開它的神秘面紗。如下:
createShadow(){ this.$$('.ew-box-item',this.box).forEach((item,index) => { let x = item.getAttribute('x'), y = item.getAttribute('y'), z = item.getAttribute('z'), ele = this.$$(`.ew-box-${x}-${y}-${z - 1}`), eleOther = this.$$(`.ew-box-${x + 1}-${y + 1}-${z - 1}`); if (ele.length || eleOther.length) { item.classList.add('ew-shadow'); } }) }
這里很顯然通過獲取x,y,z屬性設(shè)置的類名來確定是否需要添加陰影,因?yàn)橥ㄟ^這三個(gè)屬性值可以確定元素的層級(jí),如果不是在最上方,就能夠獲取到該元素,所以就添加陰影。注意$$方法返回的是一個(gè)NodeList集合,所以可以拿到length屬性。
接下來就是通過存儲(chǔ)的索引值等于3個(gè),代表選中了3個(gè)相同的塊,那就要從收集框里面移除掉該三個(gè)塊元素,并且重置對(duì)應(yīng)的index索引值為0。
接下來的for...in循環(huán)所做的操作當(dāng)然是統(tǒng)計(jì)收集框里面的塊元素,如果達(dá)到了7個(gè)代表槽位滿了,然后游戲結(jié)束,并且移除塊元素的點(diǎn)擊事件。我們來看游戲結(jié)束這個(gè)方法的實(shí)現(xiàn):
gameOver() { const self = this; ewConfirm({ title: "溫馨提示", content: "游戲結(jié)束,別灰心,你能行的!", sureText: "重新開始", isClickModal:false, sure(context) { context.close(); self.startHandler(); } }) }
這也是最開始提到的彈框插件的用法,在點(diǎn)擊確認(rèn)的回調(diào)里面調(diào)用startHandler方法表示重新開始游戲,這沒什么好說的。
到這里,我們實(shí)現(xiàn)了中間盒子元素的每一個(gè)塊元素與槽位容器元素的對(duì)應(yīng)邏輯,接下來還有2點(diǎn),那就是被遮蓋看不到層級(jí)的兩邊塊元素集合。所以繼續(xù)看startHandler后續(xù)的邏輯。
startHandler后續(xù)的邏輯
startHandler() { this.box = this.$('#ew-box'); this.leftSource = this.$('#ew-left-source'); this.rightSource = this.$('#ew-right-source'); this.collection = this.$('#ew-collection'); this.resetHandler(); for (let i = 0; i < 12; i++) { this.originSource.forEach((src, index) => { this.source.push({ src, index }) }) } this.source = this.randomList(this.source); for (let k = 5; k > 0; k--) { for (let i = 0; i < 5; i++) { for (let j = 0; j < k; j++) { const item = this.create('div'); item.setAttribute('x', i); item.setAttribute('y', j); item.setAttribute('z', k); item.className = `ew-box-item ew-box-${i}-${j}-${k}`; item.style.position = 'absolute'; const image = this.source.splice(0, 1); // 1.44為item設(shè)置的寬度與高度 item.style.left = 1.44 * j + Math.random() * .1 * k + 'rem'; item.style.top = 1.44 * i + Math.random() * .1 * k + 'rem'; item.setAttribute('index', image[0].index); item.style.backgroundImage = `url(${image[0].src})`; const clickHandler = () => { // 如果是在收集框里是不能夠點(diǎn)擊的 if(item.parentElement.className === 'ew-collection'){ return; } // 沒有陰影效果的元素才能夠點(diǎn)擊 if (!item.classList.contains('ew-shadow')) { const currentIndex = item.getAttribute('index'); if (this.temp[currentIndex]) { this.temp[currentIndex] += 1; } else { this.temp[currentIndex] = 1; } item.style.position = 'static'; this.collection.appendChild(item); // 重置陰影效果 this.$$('.ew-box-item',this.box).forEach(item => item.classList.remove('ew-shadow')); this.createShadow(); // 等于3個(gè)就消除掉 if (this.temp[currentIndex] === 3) { this.$$(`div[index="${currentIndex}"]`, this.collection).forEach(item => item.remove()); this.temp[currentIndex] = 0; } let num = 0; for (let i in this.temp) { num += this.temp[i]; } if (num >= 7) { item.removeEventListener('click', clickHandler); this.gameOver(); } } } item.addEventListener('click', clickHandler) this.box.append(item); } } } //從這里開始分析 let len = Math.ceil(this.source.length / 2); this.source.forEach((item, index) => { let div = this.create('div'); div.classList.add('ew-box-item') div.setAttribute('index', item.index); div.style.backgroundImage = `url(${item.src})`; div.style.position = 'absolute'; div.style.top = 0; if (index > len) { div.style.right = `${(5 * (index - len)) / 100}rem`; this.rightSource.appendChild(div); } else { div.style.left = `${(5 * index) / 100}rem`; this.leftSource.appendChild(div) } const clickHandler = () => { if(div.parentElement.className === 'ew-collection'){ return; } const currentIndex = div.getAttribute('index'); if (this.temp[currentIndex]) { this.temp[currentIndex] += 1; } else { this.temp[currentIndex] = 1; } div.style.position = 'static'; this.collection.appendChild(div); if (this.temp[currentIndex] === 3) { this.$$(`div[index="${currentIndex}"]`, this.collection).forEach(item => item.remove()); this.temp[currentIndex] = 0; } let num = 0; for (let i in this.temp) { num += this.temp[i]; } if (num >= 7) { div.removeEventListener('click', clickHandler); this.gameOver(); } } div.addEventListener('click', clickHandler); }); this.createShadow(); }
這里很顯然取的是source素材列表的一般來分別生成對(duì)應(yīng)的左右素材列表,同理,這里面的塊元素點(diǎn)擊事件邏輯應(yīng)該是和塊容器元素里面的邏輯是很相似的,所以沒什么好說的。我們主要看以下這段代碼:
let div = this.create('div'); div.classList.add('ew-box-item'); div.setAttribute('index', item.index); div.style.backgroundImage = `url(${item.src})`; div.style.position = 'absolute'; div.style.top = 0; if (index > len) { div.style.right = `${(5 * (index - len)) / 100}rem`; this.rightSource.appendChild(div); } else { div.style.left = `${(5 * index) / 100}rem`; this.leftSource.appendChild(div) }
其實(shí)這里也很好理解,也就是同樣的創(chuàng)建塊元素,這里根據(jù)index > len來確定是添加到右邊素材容器元素還是左邊素材元素,并且它們的top偏移量應(yīng)該是一致的,主要不同在left和right而已,計(jì)算方式也很簡單。
注意這里是不需要設(shè)置x,y,z屬性的,因?yàn)椴恍枰玫皆O(shè)置陰影的函數(shù)。
到此為止,我們一個(gè)《羊了個(gè)羊——動(dòng)物版》的小游戲就完成了。
如有興趣可以參考源碼。
更多關(guān)于jS 羊了個(gè)羊小游戲的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
js前端表單數(shù)據(jù)處理表單數(shù)據(jù)校驗(yàn)
這篇文章主要為大家介紹了js前端表單數(shù)據(jù)處理表單數(shù)據(jù)校驗(yàn)示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-07-07前端AI機(jī)器學(xué)習(xí)在瀏覽器中訓(xùn)練模型
這篇文章主要為大家介紹了前端AI機(jī)器學(xué)習(xí)在瀏覽器中訓(xùn)練模型的示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-07-07微信小程序獲取循環(huán)元素id以及wx.login登錄操作
這篇文章主要介紹了微信小程序獲取循環(huán)元素id以及wx.login登錄操作的相關(guān)資料,這里提供實(shí)例幫助大家實(shí)現(xiàn)該功能,需要的朋友可以參考下2017-08-08微信小程序 定位到當(dāng)前城市實(shí)現(xiàn)實(shí)例代碼
這篇文章主要介紹了微信小程序 定位到當(dāng)前城市實(shí)現(xiàn)實(shí)例代碼的相關(guān)資料,需要的朋友可以參考下2017-02-02網(wǎng)站申請(qǐng)不到支付寶接口、微信接口,免接口收款實(shí)現(xiàn)方式幾種解決辦法
這篇文章主要介紹了網(wǎng)站申請(qǐng)不到支付寶接口、微信接口,免接口收款實(shí)現(xiàn)方式幾種解決辦法的相關(guān)資料,需要的朋友可以參考下2016-12-12JS輕量級(jí)函數(shù)式編程實(shí)現(xiàn)XDM二
這篇文章主要為大家介紹了JS函數(shù)式編程實(shí)現(xiàn)XDM示例詳解第2/3篇,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-06-06