初學(xué)前端用代碼實(shí)現(xiàn)一個(gè)網(wǎng)頁(yè)老虎機(jī)游戲

作者:紅領(lǐng)巾小莫
https://juejin.cn/post/6890206996008534030
簡(jiǎn)介
前兩天小編在B站看到一個(gè)AE MG動(dòng)畫(huà),動(dòng)畫(huà)的內(nèi)容如下:

這個(gè)動(dòng)畫(huà)還是挺有意思的,但是小編第一個(gè)反應(yīng)這要是哪一天某位ui姐姐或產(chǎn)品姐姐給小編提了這樣子的需求,那小編豈不是當(dāng)場(chǎng)要自閉?我本著自己的好奇心,實(shí)現(xiàn)了一個(gè)簡(jiǎn)易版的老虎機(jī):

老虎機(jī)的整體外觀樣式還是比較好寫(xiě)的,對(duì)老虎機(jī)的外觀布局如果有興趣的話可以直接參考代碼,小編就不一一介紹了。文章主要介紹的是老虎機(jī)中間三個(gè)小格子和機(jī)身動(dòng)畫(huà)的一些實(shí)現(xiàn)要點(diǎn)。(讀者想實(shí)操的話也可以自己找一張背景圖當(dāng)作老虎機(jī)的背景,這里小編只是自己好奇而已所以就用各種基礎(chǔ)布局和樣式實(shí)現(xiàn)的老虎機(jī))
格子中數(shù)字的真面目是什么?
數(shù)字列表滾動(dòng)前的要點(diǎn)
格子中的數(shù)字列表是怎么滾動(dòng)的?
“無(wú)限滾動(dòng)”是怎么實(shí)現(xiàn)的?
第二個(gè)和第三個(gè)格子的延遲滾動(dòng)怎么實(shí)現(xiàn)的?
隨機(jī)的滾動(dòng)結(jié)果是怎么實(shí)現(xiàn)的?
游戲機(jī)是怎么抖動(dòng)起來(lái)的?
重置游戲的實(shí)現(xiàn)及要注意的點(diǎn)
怎么獲取老虎機(jī)的游戲結(jié)果?
格子中數(shù)字的真面目是什么?
這個(gè)問(wèn)題其實(shí)很簡(jiǎn)單,格子中的內(nèi)容單純只是一個(gè)ul列表,我是只是給格子添加了overflow:hidden使格子外的數(shù)字進(jìn)行了隱藏。咱們先把一個(gè)格子的數(shù)字列表拿出來(lái)講,我們可以看到整個(gè)滾動(dòng)過(guò)程中只有“1~6”共6個(gè)數(shù)字,我們可以先把這6個(gè)數(shù)字的列表給實(shí)現(xiàn)下。
我們利用的是ul 和 li標(biāo)簽做出的列表,在布局中小編只寫(xiě)了ul,雖然效果圖中只有1~6 6種情況,但是后面可能會(huì)有更多的游戲結(jié)果選項(xiàng),所以li標(biāo)簽就不寫(xiě)死在頁(yè)面中,li標(biāo)簽通過(guò)javaScript的形式添加到ul標(biāo)簽中。
(這里為了方便讀者讀懂代碼我先解釋一下,小編在一開(kāi)始考慮老虎機(jī)里面的內(nèi)容以后可能是圖片而不是數(shù)字,所以在很多地方變量或者className的命名都名為與“images”相關(guān)。)
// 初始的選項(xiàng)列表let initImagesArr = [6, 5, 4, 3, 2, 1];// 獲取第一個(gè)ul列表const firstImagesList = document.getElementsByClassName('images')[0];// 構(gòu)造列表li添加到ul標(biāo)簽中去initImagesArr.forEach(item => {const li = document.createElement('li');li.innerHTML = item;firstImagesList.appendChild(li);});
這樣子我們就可以獲得這樣的一個(gè)ul列表:
6 5 4 3 2 1
同理,第二個(gè)格子和第三個(gè)格子也可以利用相同的方式構(gòu)造出相同的數(shù)字列表。通過(guò)給ul和li添加樣式之后的效果如圖:

數(shù)字列表滾動(dòng)前的要點(diǎn)
tip: 讀者可以留意一下,firstImagesList代表的是第一個(gè)數(shù)字列表,secondsImagesList為第二個(gè)數(shù)字列表,thirdImagesList為第三個(gè)數(shù)字列表。其中代碼:
const firstImagesList = document.getElementsByClassName('images')[0];const secondsImagesList = document.getElementsByClassName('images')[1];??const?thirdImagesList?=?document.getElementsByClassName('images')[2];
我們可以從動(dòng)畫(huà)中看出數(shù)字是從上往下開(kāi)始滾動(dòng)的。其實(shí)滾動(dòng)的原理利用的是CSS3的transform:translateY()進(jìn)行移動(dòng)。那有人可能就有疑問(wèn)了(小編你不是說(shuō)從上往下滾動(dòng)嗎,按照你列表這樣的布局你從上往下不是一移動(dòng)就結(jié)束了嗎)。
所以我們?cè)诹斜硪苿?dòng)之前要做一件事情,我們要把這個(gè)列表的初始化布局給調(diào)整一下,將全部列表都向上移動(dòng),使數(shù)字“1”移動(dòng)到格子中去。
我們先聲明一個(gè)初始化三個(gè)數(shù)字列表定位方法,其中參數(shù)startTranslateYHeight代表整個(gè)列表要向上移動(dòng)的距離。
function initPosition(startTranslateYHeight) {firstImagesList.style.transform = `translateY(${startTranslateYHeight}px)`;secondsImagesList.style.transform = `translateY(${startTranslateYHeight}px)`;thirdImagesList.style.transform = `translateY(${startTranslateYHeight}px)`;}
有了移動(dòng)的方法之后,我們要計(jì)算出列表要向上移動(dòng)的距離startTranslateYHeight。獲取向上移動(dòng)距離的步驟包括:
聲明列表單獨(dú)一項(xiàng)也就是單個(gè)li的高度,已知li標(biāo)簽的高度為136px;
獲取一列的高度,也就是整個(gè)ul標(biāo)簽的高度,這個(gè)三個(gè)列表的高度都一樣大,所以我們?nèi)〉谝粋€(gè)列表利用dom的內(nèi)置屬性scrollHeight獲得列表的高度;
因?yàn)檎麄€(gè)列表向上移動(dòng)到最后數(shù)字1會(huì)留在格子中,列表中全部有6個(gè)數(shù)字,我們只需要向上移動(dòng)5個(gè)數(shù)字的高度即可。也就是整個(gè)整個(gè)列表的高度減去一個(gè)li標(biāo)簽的高度就是我們要列表向上移動(dòng)的距離。
const imageHeight = 136;const listHeight = firstImagesList.scrollHeight;??const?startTranslateYHeight?=?-?listHeight?+?imageHeight;
有了向上移動(dòng)的具體距離后,我們只要執(zhí)行我們的初始化定位方法initPosition()方法即可。

格子中的數(shù)字列表是怎么滾動(dòng)的?
前面我們也提到了其實(shí)滾動(dòng)的原理利用的是CSS3的transform:translateY()進(jìn)行移動(dòng)。因?yàn)槲覀冇螒蚴峭ㄟ^(guò)點(diǎn)擊手柄開(kāi)始的,所以我們給手柄添加一個(gè)點(diǎn)擊事件,并在事件中給列表進(jìn)行滾動(dòng),我們暫時(shí)默認(rèn)滾動(dòng)到最后一個(gè)數(shù)字,不考慮隨機(jī)結(jié)果的情況。
如果只是滾動(dòng)到最后一個(gè)數(shù)字那還是比較容易的,那我們只需要將向上移動(dòng)的距離還原為0就可以了,這樣子就能達(dá)到向下移動(dòng)的效果。
// 點(diǎn)擊游戲手柄開(kāi)始游戲的方法function start() {firstImagesList.style.transform = 'translateY(0)';secondsImagesList.style.transform = 'translateY(0)';thirdImagesList.style.transform = 'translateY(0)';}
我們來(lái)看一下現(xiàn)階段的效果:

是不是少了點(diǎn)什么對(duì)吧?沒(méi)錯(cuò),少了滾動(dòng)動(dòng)畫(huà)。我們只需要在游戲開(kāi)始時(shí)給列表加上過(guò)渡效果即可??赡苡腥藭?huì)問(wèn)為什么要在游戲開(kāi)始時(shí)再加而不是一開(kāi)始寫(xiě)樣式時(shí)先寫(xiě)上transtion過(guò)渡。原因是這樣子阿:因?yàn)樾【幒罄m(xù)要考慮到重置游戲的問(wèn)題,重置過(guò)程列表會(huì)回到最開(kāi)始的定位處,如果說(shuō)重置過(guò)程也有過(guò)渡樣式那是不太合理的,為了能夠保證過(guò)渡樣式是可控的小編就定義了一個(gè)添加過(guò)渡的方法,還有一個(gè)刪除過(guò)渡的方法,方便我們想要有過(guò)渡動(dòng)畫(huà)就加上,不想有過(guò)渡動(dòng)畫(huà)就刪除。
下面的代碼意思就是分別給每個(gè)列表添加/刪除過(guò)渡樣式類(lèi)名(className),刪除過(guò)渡我們會(huì)在重置動(dòng)畫(huà)中使用到。
//過(guò)渡效果.transtion {transition: all ease 2s;}
// 全部列表添加過(guò)渡效果function addTranstion() {firstImagesList.classList.add('transtion');secondsImagesList.classList.add('transtion');thirdImagesList.classList.add('transtion');}// 全部列表刪除過(guò)渡效果function removeTranstion(imagesList) {firstImagesList.classList.remove('transtion');secondsImagesList.classList.remove('transtion');thirdImagesList.classList.remove('transtion');}
然后我們只需要只開(kāi)始游戲方法中調(diào)用添加過(guò)渡方法即可:
function start() {// 游戲開(kāi)始給全部數(shù)字列表添加過(guò)渡效果addTranstion();firstImagesList.style.transform = 'translateY(0)';secondsImagesList.style.transform = 'translateY(0)';thirdImagesList.style.transform = 'translateY(0)';}

到這里我們已經(jīng)實(shí)現(xiàn)了數(shù)字列表的滾動(dòng)效果,但是我們只是做了個(gè)簡(jiǎn)單的從1~6的滾動(dòng),并沒(méi)有做到從頭開(kāi)始的效果。簡(jiǎn)單來(lái)說(shuō)就是滾動(dòng)得沒(méi)有像效果圖中那么“持久”。我們接下來(lái)就是來(lái)實(shí)現(xiàn)一下“從頭開(kāi)始”,“無(wú)限滾動(dòng)”的效果。
“無(wú)限滾動(dòng)”是怎么實(shí)現(xiàn)的?
效果圖中我們可以看到當(dāng)數(shù)字6滾動(dòng)結(jié)束之后應(yīng)該會(huì)重?cái)?shù)字1開(kāi)始重新滾動(dòng),話不多說(shuō)我們直接揭開(kāi)謎底。

可能有的小伙伴看到這里就明白小編是怎么實(shí)現(xiàn)的了。其實(shí)我這里并沒(méi)有實(shí)現(xiàn)所謂的“無(wú)限滾動(dòng)”,我只是把初始化的數(shù)組按倍數(shù)給擴(kuò)充了很多分,使得整個(gè)列表變得非常得長(zhǎng),以至于在短時(shí)間內(nèi)的過(guò)渡效果中整個(gè)列表看著像是在“無(wú)限滾動(dòng)”。
[6,5,4,3,2,1,6,5,4,3,2,1,6,5,4,3,2,1,6,5,4,3,2,1,6,5,4,3,2,1,6,5,4,3,2,1]// 初始的選項(xiàng)列表let initImagesArr = [6, 5, 4, 3, 2, 1];let imagesArr = [6, 5, 4, 3, 2, 1];// 加長(zhǎng)整個(gè)選項(xiàng)列表,以完成一個(gè)虛假的無(wú)限滾動(dòng)的效果new Array(20).fill('').forEach(() => {imagesArr = imagesArr.concat(...initImagesArr);??})
那么此時(shí)前面添加li標(biāo)簽的代碼就得修改一下了,將initImageArr修改為新的選項(xiàng)列表imagesArr
// 構(gòu)造列表li添加到ul標(biāo)簽中去imagesArr.forEach(item => {const li = document.createElement('li');li.innerHTML = item;firstImagesList.appendChild(li);??});
至此我們虛假的無(wú)限滾動(dòng)就已經(jīng)實(shí)現(xiàn)完成了(如果打滅了你們對(duì)無(wú)限滾動(dòng)的期待的話請(qǐng)不要打小編,小編心里也苦,真正的無(wú)限滾動(dòng)好像不太好寫(xiě),有感興趣的小伙伴要是知道怎么無(wú)限滾動(dòng)就告訴我哈,小編也來(lái)學(xué)習(xí)學(xué)習(xí))。
第二個(gè)和第三個(gè)格子的延遲滾動(dòng)怎么實(shí)現(xiàn)的?
無(wú)限滾動(dòng)介紹完之后我們來(lái)介紹一下延遲滾動(dòng)的問(wèn)題,我們可以看到效果圖中第二個(gè)格子是等第一個(gè)格子滾動(dòng)一小會(huì)兒后才開(kāi)始滾動(dòng)的,第三個(gè)格子也是一樣的。
其實(shí)延遲滾動(dòng)實(shí)現(xiàn)也很簡(jiǎn)單,我們只需要給第二個(gè)數(shù)字列表和第三個(gè)數(shù)字列表各自的滾動(dòng)方法中設(shè)置個(gè)定時(shí)器即可。
// 點(diǎn)擊游戲手柄開(kāi)始游戲的方法function start() {// 游戲開(kāi)始給全部數(shù)字列表添加過(guò)渡效果addTranstion();firstImagesList.style.transform = 'translateY(0)';// 列表2延遲0.5s后滾動(dòng)timeout1 = setTimeout(() => {secondsImagesList.style.transform = 'translateY(0)';},500)// 列表3延遲1s后滾動(dòng)timeout2 = setTimeout(() => {thirdImagesList.style.transform = 'translateY(0)';},1000)??}

隨機(jī)的滾動(dòng)結(jié)果是怎么實(shí)現(xiàn)的?
隨機(jī)的滾動(dòng)結(jié)果解釋起來(lái)可能會(huì)比較難以理解一點(diǎn)。我們?cè)倩仡櫼幌拢谏厦嫖覀儗?shí)現(xiàn)向下滾動(dòng)的原理是將向上移動(dòng)的距離還原為0('translateY(0))來(lái)實(shí)現(xiàn)的。那試想一下如果我們還原的結(jié)果不是0,而是一個(gè)數(shù)字的高度呢?
Tip: 一個(gè)數(shù)字的高度也就是一個(gè)li標(biāo)簽的高度,前面我們已知一個(gè)li標(biāo)簽高度是136px
我們來(lái)改寫(xiě)代碼試試:
firstImagesList.style.transform = 'translateY(-136px)';// 列表2延遲0.5s后滾動(dòng)timeout1 = setTimeout(() => {secondsImagesList.style.transform = 'translateY(-136px)';},500)// 列表3延遲1s后滾動(dòng)timeout2 = setTimeout(() => {thirdImagesList.style.transform = 'translateY(-136px)';??},1000)
效果如下:

我們可以看到,如果我們將定位只還原到translateY(-136px),那滾動(dòng)的結(jié)果會(huì)是5。以此類(lèi)推,如果我們只還原到0、-136、-136 * 2、-136 * 3、-136 * 4、-136 * 5(單位都為px)、那么我們就可以在數(shù)字列表滾動(dòng)中得到6,5,4,3,2,1共6中結(jié)果。
現(xiàn)在我們已經(jīng)能夠通過(guò)改變不同的還原距離translateY()從而達(dá)到滾動(dòng)結(jié)果的不同,那還有一個(gè)問(wèn)題,從上面6個(gè)數(shù)中隨機(jī)出一個(gè)數(shù)來(lái)怎么做呢?滾動(dòng)的結(jié)果在這里不應(yīng)該是由我們?nèi)藶榭刂频摹P【幭肓艘幌?,還好這里最起碼的要求是結(jié)果應(yīng)該是從-136的倍數(shù)0,1,2,3,4,5種隨機(jī)出一個(gè)數(shù)來(lái)。我們通過(guò)倍數(shù)的變化就能獲取到相應(yīng)的隨機(jī)值。
這里我利用了js種Math對(duì)象的Math.random()方法,Math.random()方法會(huì)返回介于 0(包含) ~ 1(不包含) 之間的一個(gè)隨機(jī)數(shù), 那如果我將Math.random的結(jié)果乘以6,那我不就得到0~6(不包含6)之間的一個(gè)隨機(jī)數(shù),并且我將獲取的隨機(jī)數(shù)通過(guò)Math.floor()做一個(gè)向下取整,那我不就得到0,1,2,3,4,5的隨機(jī)數(shù)了。
目前整個(gè)游戲的開(kāi)始方法整理如下:
// 初始的選項(xiàng)列表initImagesArr中有6個(gè)值,也就是單列數(shù)據(jù)列表總的情況會(huì)有6種中間內(nèi)容省略。。。
// 單個(gè)數(shù)字的高度,即單個(gè)li標(biāo)簽的高度const imageHeight = 136;// resultNum為滾動(dòng)結(jié)果情況的個(gè)數(shù),這里有6個(gè)數(shù)字,也就是有6種情況// translateY(${-imageHeight * radom1}px)為列表1最終還原的位置,以此類(lèi)推function start(resultNum) {// 游戲開(kāi)始給全部數(shù)字列表添加過(guò)渡效果addTranstion();// 每個(gè)列表滾動(dòng)的隨機(jī)結(jié)果0~resultNumlet radom1 = Math.floor(Math.random()*resultNum);let radom2 = Math.floor(Math.random()*resultNum);let radom3 = Math.floor(Math.random()*resultNum);// 列表1開(kāi)始滾動(dòng)firstImagesList.style.transform = `translateY(${-imageHeight * radom1}px)`;// 列表2延遲0.5s后滾動(dòng)timeout1 = setTimeout(() => {secondsImagesList.style.transform = `translateY(${-imageHeight * radom2}px)`;},500)// 列表3延遲1s后滾動(dòng)timeout2 = setTimeout(() => {thirdImagesList.style.transform = `translateY(${-imageHeight * radom3}px)`;},1000)}
注意點(diǎn)1:方法中resultNum為單個(gè)數(shù)字列表滾動(dòng)結(jié)果的全部可能性。另外因?yàn)槿拷Y(jié)果的情況總數(shù)由初始數(shù)字列表initImagesArr: [1,2,3,4,5,6]中的數(shù)字個(gè)數(shù)所決定的,所以只需要將initImagesArr.length作為參數(shù)傳給start()方法即可。
注意點(diǎn)2:三個(gè)數(shù)字列表的隨機(jī)結(jié)果都不同,其中radom1為列表1的隨機(jī)結(jié)果,radom2為列表2的隨機(jī)結(jié)果,radom3為列表3的隨機(jī)結(jié)果。Math.random()*resultNum的值為 0 * resultNum ~ 1 * resultNum(不包含1 * resultNum)。本例中resultNum為6,則結(jié)果為0 ~ 5.999999999 經(jīng)過(guò)Math.floor()向下取整過(guò)后的結(jié)果為0 ~ 5。
經(jīng)過(guò)以上的處理之后,我們的隨機(jī)結(jié)果就已經(jīng)成功實(shí)現(xiàn)了。
游戲機(jī)是怎么抖動(dòng)起來(lái)的?
前面可能會(huì)復(fù)雜一點(diǎn),這里就我們聊個(gè)稍微簡(jiǎn)單易懂的東西。從效果圖中我們可以看出老虎機(jī)從開(kāi)始游戲到游戲快結(jié)束時(shí)一直是在抖動(dòng)的,關(guān)于這個(gè)我也給大家稍微分享一下怎么實(shí)現(xiàn)的。

其實(shí)就一個(gè)東西,加個(gè)動(dòng)畫(huà)。這里我先直接貼上代碼:
.shake {animation: shake 0.1s infinite;}@keyframes shake {25% {left: 49.7%;}50% {top: 49.7%;}75% {left: 50%;}100% {top: 50%;}??}
在寫(xiě)樣式時(shí)小編通過(guò)相對(duì)定位的形式position:absoulte 配合{left:50%;top:50%;translate(-50%,-50%)}的形式實(shí)現(xiàn)老虎機(jī)相對(duì)可視區(qū)域水平垂直居中對(duì)齊的效果。
// 老虎機(jī)相對(duì)可視區(qū)域水平垂直居中對(duì)齊的效果。.machine {position: absolute;top: 50%;left: 50%;transform: translate(-50%,-50%);}
解釋一下代碼,小編通過(guò)給整個(gè)老虎機(jī)從各個(gè)方向都移動(dòng)一下,并且以很快的速度完成(這里用的是0.1s完成的動(dòng)畫(huà)),動(dòng)畫(huà)的循環(huán)次數(shù)為無(wú)限次infinite,從而實(shí)現(xiàn)了老虎機(jī)一直在抖的效果。
不過(guò)要稍微留意一點(diǎn),我們這里抖動(dòng)應(yīng)該也是要可控的,因?yàn)槔匣C(jī)在游戲快結(jié)束時(shí)會(huì)停止抖動(dòng)。跟我們之前做過(guò)渡效果可控的方式一樣,我們也給動(dòng)畫(huà)聲明一個(gè)添加抖動(dòng)和移除抖動(dòng)的方法:
// 給老虎機(jī)添加抖動(dòng)效果function startShake() {document.getElementsByClassName('machine')[0].classList.add('shake');}// 移除老虎機(jī)抖動(dòng)效果function stopShake() {document.getElementsByClassName('machine')[0].classList.remove('shake');??}
之后我們?cè)谟螒蜷_(kāi)始的時(shí)候調(diào)用startShake(),然后在游戲快結(jié)束時(shí)調(diào)用stopShake()。不過(guò)在下面代碼我們可以看到移除抖動(dòng)效果是在2.6s之后才執(zhí)行的,原因是第三個(gè)數(shù)字列表需要等到游戲開(kāi)始1s才開(kāi)始滾動(dòng),而且滾動(dòng)的過(guò)渡時(shí)間為2s,那等到第三個(gè)數(shù)字列表滾動(dòng)到結(jié)束總共需要3s,這里小編想要在第三個(gè)數(shù)字列表滾動(dòng)結(jié)束之前將老虎機(jī)停止抖動(dòng),所以將移除抖動(dòng)方法在游戲開(kāi)始2.6s之后才執(zhí)行。
// 單個(gè)數(shù)字的高度,即單個(gè)li標(biāo)簽的高度const imageHeight = 136;function start(resultNum) {// 游戲開(kāi)始給全部數(shù)字列表添加過(guò)渡效果addTranstion();// 游戲開(kāi)始給老虎機(jī)添加抖動(dòng)效果startShake();// 每個(gè)列表滾動(dòng)的隨機(jī)結(jié)果0~resultNumlet radom1 = Math.floor(Math.random()*resultNum);let radom2 = Math.floor(Math.random()*resultNum);let radom3 = Math.floor(Math.random()*resultNum);// 列表1開(kāi)始滾動(dòng)firstImagesList.style.transform = `translateY(${-imageHeight * radom1}px)`;// 列表2延遲0.5s后滾動(dòng)timeout1 = setTimeout(() => {secondsImagesList.style.transform = `translateY(${-imageHeight * radom2}px)`;},500)// 列表3延遲1s后滾動(dòng)timeout2 = setTimeout(() => {thirdImagesList.style.transform = `translateY(${-imageHeight * radom3}px)`;},1000)// 老虎機(jī)在延遲2.6妙后移除抖動(dòng)效果timeout3 = setTimeout(() => {stopShake();},2600)}
重置游戲的實(shí)現(xiàn)及要注意的點(diǎn)
老虎機(jī)從開(kāi)始游戲到結(jié)束游戲的整個(gè)環(huán)節(jié)我們都已經(jīng)實(shí)現(xiàn)完成了。但是呢,小編還想再玩一把,然后我在第二次點(diǎn)擊開(kāi)始手柄之前就想到了這個(gè)老虎機(jī)存在的缺陷,總結(jié)起來(lái)包括以下幾點(diǎn):
游戲從開(kāi)始到結(jié)束之后,再次點(diǎn)擊開(kāi)始手柄,應(yīng)該將游戲進(jìn)行重置
重置游戲的過(guò)程中不應(yīng)該出現(xiàn)過(guò)渡效果
重置游戲的過(guò)程中機(jī)器不應(yīng)該繼續(xù)在搖晃
如果將游戲進(jìn)行重置了,第一次開(kāi)始游戲方法中的定時(shí)器方法應(yīng)該清空。
小編給這臺(tái)老虎機(jī)做了個(gè)設(shè)定,當(dāng)游戲手柄點(diǎn)擊第一下時(shí),游戲開(kāi)始。當(dāng)游戲手柄點(diǎn)擊第二下時(shí),游戲要結(jié)束并重置游戲。當(dāng)游戲手柄點(diǎn)擊第三下時(shí),游戲又再度重新開(kāi)始。
這里可能有個(gè)疑惑就是開(kāi)始游戲手柄的點(diǎn)擊事件只綁定了一個(gè)開(kāi)始游戲start()的方法,那怎么判斷游戲是開(kāi)始還是重置?
中間內(nèi)容省略。。。這里小編給start寫(xiě)了個(gè)flag,這個(gè)當(dāng)flag為true時(shí),會(huì)執(zhí)行開(kāi)始游戲的方法,當(dāng)flag為false時(shí),會(huì)執(zhí)行重置游戲的方法,每次點(diǎn)擊時(shí)將flag的值重新賦為flag的反向值即可。
// 游戲是否開(kāi)始 true為開(kāi)始游戲,false為重置游戲let isStart = false;function start(imagesArrLength) {// flag取反isStart = !isStart;// 開(kāi)始游戲if (isStart) {// 開(kāi)始游戲就給列表加過(guò)渡效果addTranstion();startShake();// 每個(gè)列表滾動(dòng)的隨機(jī)結(jié)果let radom1 = Math.floor(Math.random()*imagesArrLength);let radom2 = Math.floor(Math.random()*imagesArrLength);let radom3 = Math.floor(Math.random()*imagesArrLength);firstImagesList.style.transform = `translateY(${-imageHeight * radom1}px)`;// 列表2延遲0.5s后滾動(dòng)timeout1 = setTimeout(() => {secondsImagesList.style.transform = `translateY(${-imageHeight * radom2}px)`;},500)// 列表3延遲1s后滾動(dòng)timeout2 = setTimeout(() => {thirdImagesList.style.transform = `translateY(${-imageHeight * radom3}px)`;},1000)timeout3 = setTimeout(() => {stopShake();},2600)}else {// 重置游戲,這里實(shí)現(xiàn)重置游戲的方法}??}
那重置游戲需要我們做什么呢?
1、首先當(dāng)然是將所有的數(shù)字列表回到初始位置。我們?cè)谇懊妗皵?shù)字列表滾動(dòng)前的要點(diǎn)”中已經(jīng)實(shí)現(xiàn)了初始化定位方法initPosition()方法,在上面else代碼塊中我們只要調(diào)用initPosition(startTranslateYHeight)即可將全部數(shù)字列表回到初始的位置。Tip:在前面“數(shù)字列表滾動(dòng)前的要點(diǎn)”中我們已經(jīng)介紹了startTranslateYHeight為列表向上移動(dòng)的距離。
function initPosition(startTranslateYHeight) {firstImagesList.style.transform = `translateY(${startTranslateYHeight}px)`;secondsImagesList.style.transform = `translateY(${startTranslateYHeight}px)`;thirdImagesList.style.transform = `translateY(${startTranslateYHeight}px)`;??}
2、 重置游戲時(shí)數(shù)字列表在回到初始位置的過(guò)程中,由于游戲開(kāi)始過(guò)程中數(shù)字列表添加了過(guò)渡動(dòng)畫(huà),會(huì)導(dǎo)致數(shù)字列表在回到初始位置的過(guò)程也會(huì)存在過(guò)渡動(dòng)畫(huà),因此我們需要調(diào)用之前先聲明好的removeTranstion()來(lái)刪除全部數(shù)字列表的過(guò)渡效果。

3、 重置游戲時(shí)由于第一次開(kāi)始游戲過(guò)程中給老虎機(jī)添加了抖動(dòng)效果,重置時(shí)應(yīng)該移除抖動(dòng)效果。在else代碼塊中調(diào)用stopShake()即可。
4、 假如在開(kāi)始游戲后在很短的時(shí)間內(nèi)又點(diǎn)擊了重置游戲,這時(shí)候開(kāi)始游戲中未執(zhí)行的定時(shí)器中的方法應(yīng)該通過(guò)clearTimeout()給予一一清除,否則在重置游戲時(shí)還是會(huì)執(zhí)行開(kāi)始游戲中的方法。

那么else代碼塊中的代碼應(yīng)該為:
else {clearTimeout(timeout1);clearTimeout(timeout2);clearTimeout(timeout3);// 停止抖動(dòng)stopShake();// 重置時(shí)因?yàn)榱斜頃?huì)重新移動(dòng)到初始位置,所以要清除掉過(guò)渡效果removeTranstion();// 各個(gè)列表回到最初的位置initPosition(startTranslateYHeight);}
怎么獲取老虎機(jī)的游戲結(jié)果
是個(gè)游戲總會(huì)有個(gè)結(jié)果的。這里我們實(shí)現(xiàn)在老虎機(jī)游戲結(jié)束之后,將游戲結(jié)果給打印出來(lái)。由于我們的結(jié)果是通過(guò)radom1,radom2,radom3隨機(jī)結(jié)果得出,當(dāng)radom1為0時(shí)結(jié)果為6,random1為1時(shí)結(jié)果為5,依次類(lèi)推。我們可以得到最終的結(jié)果應(yīng)該為 initImagesArr[radom1], initImagesArr[radom2], initImagesArr[radom3]。
前面我們也有提到游戲結(jié)束的整個(gè)過(guò)程需要經(jīng)歷3s,所以我們定義一個(gè)定時(shí)器在開(kāi)始游戲3s后執(zhí)行并打印出游戲結(jié)果即可。
代碼附錄
html:
<body><div class="machine"><div class="machine-body"><div class="rotary-table"><div class="screen"><ul class="images">ul>div><div class="screen"><ul class="images">ul>div><div class="screen"><ul class="images">ul>div>div><div class="machine-logo"><img src="./index.png">div>div><div class="base"><div class="handle" onclick="start(initImagesArr.length)"><div class="handle-ball">div><div class="handle-bar">div><div class="handle-shaft">div>div>div>div>body>
scss:
{width: 484px;height: 513px;position: absolute;top: 50%;left: 50%;transform: translate(-50%,-50%);display: flex;{width: 400px;: #c8d0e3;height: 513px;border: 14px solid #2e2a27;: 34px;: border-box;{width: 278px;border: 16px solid #2e2a27;: 16px;margin: 54px auto 0;display: flex;{width: 82px;height: 136px;overflow: hidden;{: none;margin: 0;padding: 0;border: none;li {margin: 0;padding: 0;border: 0;: center;height: 136px;: 136px;: 30px;: wheat;}}{transition: all ease 2s;}}}:nth-child(1) {: 16px solid #2e2a27;}:nth-child(2) {: 16px solid #2e2a27;}{width: 240px;margin: 20px auto 0;img {width: 100%;height: auto;}}}{width: 34px;height: 130px;: #b1b8d4;border: 14px solid #2e2a27;: none;: border-box;: 24px;: 24px;transform: translateY(250px);position: relative;{width: 64px;position: absolute;left: 30px;bottom: 28px;cursor: pointer;{width: 34px;height: 34px;: #ff6169;: 50%;border: 15px solid #2e2a27;transform: translateY(2px);}{width: 16px;height: 106px;margin: 0 auto;: #2e2a27;transform: translateY(1px);}{width: 56px;height: 48px;border: 15px solid #2e2a27;: none;: 25px;: 25px;: #c8d0e3;: border-box;margin: 0 auto;}}}}{animation: shake 0.1s infinite;}shake {{left: 49.7%;}{top: 49.7%;}{left: 50%;}{top: 50%;}}
javaScript:
// 以下的images命名可能是考慮老虎機(jī)里的內(nèi)容可能會(huì)被替換成水果等其他圖片而非數(shù)字,所以將相關(guān)的內(nèi)容變量命名為與images相關(guān)// 頁(yè)面剛加載時(shí)三個(gè)列表的初始定位function initPosition(startTranslateYHeight) {firstImagesList.style.transform = `translateY(${startTranslateYHeight}px)`;secondsImagesList.style.transform = `translateY(${startTranslateYHeight}px)`;thirdImagesList.style.transform = `translateY(${startTranslateYHeight}px)`;}// 全部列表添加過(guò)渡效果function addTranstion() {firstImagesList.classList.add('transtion');secondsImagesList.classList.add('transtion');thirdImagesList.classList.add('transtion');}// 全部列表刪除過(guò)渡效果function removeTranstion(imagesList) {firstImagesList.classList.remove('transtion');secondsImagesList.classList.remove('transtion');thirdImagesList.classList.remove('transtion');}// 給老虎機(jī)添加搖晃動(dòng)畫(huà)function startShake() {document.getElementsByClassName('machine')[0].classList.add('shake');}// 停止老虎機(jī)搖晃動(dòng)畫(huà)function stopShake() {document.getElementsByClassName('machine')[0].classList.remove('shake');}// 點(diǎn)擊第一個(gè)是開(kāi)始,點(diǎn)擊第二次是充值游戲// startTranslateYHeight 列表的初始化時(shí)translateY的距離// imageHeight列表的每一項(xiàng)的高度// 列表數(shù)組的長(zhǎng)度function start(imagesArrLength) {isStart = !isStart;// 開(kāi)始游戲if (isStart) {// 開(kāi)始游戲就給列表加過(guò)渡效果addTranstion();startShake();// 每個(gè)列表滾動(dòng)的隨機(jī)結(jié)果let radom1 = Math.floor(Math.random()*imagesArrLength);let radom2 = Math.floor(Math.random()*imagesArrLength);let radom3 = Math.floor(Math.random()*imagesArrLength);firstImagesList.style.transform = `translateY(${-imageHeight * radom1}px)`;// 列表2延遲0.5s后滾動(dòng)timeout1 = setTimeout(() => {secondsImagesList.style.transform = `translateY(${-imageHeight * radom2}px)`;},500)// 列表3延遲1s后滾動(dòng)timeout2 = setTimeout(() => {thirdImagesList.style.transform = `translateY(${-imageHeight * radom3}px)`;},1000)// 延遲2.6秒后停止抖動(dòng)timeout3 = setTimeout(() => {stopShake();},2600)// 游戲結(jié)束后打印結(jié)果timeout4 = setTimeout(() => {console.log(initImagesArr[radom1],initImagesArr[radom2],initImagesArr[radom3]);},3000)// 重置游戲}else {// 取消上一次未執(zhí)行完的方法clearTimeout(timeout1);clearTimeout(timeout2);clearTimeout(timeout3);clearTimeout(timeout4);stopShake();// 重置時(shí)因?yàn)榱斜頃?huì)重新移動(dòng)到初始位置,所以要清除掉過(guò)渡效果removeTranstion();// 各個(gè)列表回到最初的位置initPosition(startTranslateYHeight);}}// 初始的選項(xiàng)列表let initImagesArr = [6, 5, 4, 3, 2, 1];let imagesArr = [6, 5, 4, 3, 2, 1];// 加長(zhǎng)整個(gè)選項(xiàng)列表,以完成一個(gè)虛假的滾動(dòng)的效果new Array(20).fill('').forEach(() => {imagesArr = imagesArr.concat(...initImagesArr);})// 獲取第一個(gè)列表的domconst firstImagesList = document.getElementsByClassName('images')[0];const secondsImagesList = document.getElementsByClassName('images')[1];const thirdImagesList = document.getElementsByClassName('images')[2];// 構(gòu)造列表li添加到ul標(biāo)簽中去imagesArr.forEach(item => {const li = document.createElement('li');const li2 = document.createElement('li');const li3 = document.createElement('li');li.innerHTML = item;li2.innerHTML = item;li3.innerHTML = item;firstImagesList.appendChild(li);secondsImagesList.appendChild(li2);thirdImagesList.appendChild(li3);});// 列表單獨(dú)一項(xiàng)的高度const imageHeight = 136;// 獲取一列的高度const listHeight = firstImagesList.scrollHeight;// 初始化列表tranlateY的高度const startTranslateYHeight = - listHeight + imageHeight;// 游戲是否已經(jīng)開(kāi)始let isStart = false;// 三個(gè)setTimeout的返回表示符,前兩個(gè)是第二列第三列列表開(kāi)始滾動(dòng)的延時(shí)方法,timeout3是機(jī)器停止搖晃動(dòng)畫(huà)的延時(shí)方法let timeout1 = null;let timeout2 = null;let timeout3 = null;// 頁(yè)面剛進(jìn)來(lái)時(shí)列表位置初始化initPosition(startTranslateYHeight);
結(jié)語(yǔ)
看到這里,小編想說(shuō)讀者你們也辛苦了,應(yīng)該費(fèi)了不少勁來(lái)看我的文章。雖然小編覺(jué)得這種需求在工作中幾乎不會(huì)遇到,但是小編覺(jué)得這是一個(gè)鍛煉自己思維能力的一個(gè)過(guò)程,如果讀者們對(duì)代碼有疑問(wèn)或建議隨時(shí)可以提出來(lái),小編會(huì)耐心解答或虛心接受。如果喜歡文章的話也給小編點(diǎn)點(diǎn)贊支持一下,碼字不易,很感謝你們的支持,最后祝大家工作順利。
最后
歡迎加我微信(winty230),拉你進(jìn)技術(shù)群,長(zhǎng)期交流學(xué)習(xí)...
歡迎關(guān)注「前端Q」,認(rèn)真學(xué)前端,做個(gè)專(zhuān)業(yè)的技術(shù)人...


