前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >初学前端用代码实现一个网页老虎机游戏

初学前端用代码实现一个网页老虎机游戏

作者头像
winty
发布2021-01-22 10:50:41
5K0
发布2021-01-22 10:50:41
举报
文章被收录于专栏:前端Q前端Q

简介

前两天小编在B站看到一个AE MG动画,动画的内容如下:

这个动画还是挺有意思的,但是小编第一个反应这要是哪一天某位ui姐姐或产品姐姐给小编提了这样子的需求,那小编岂不是当场要自闭?我本着自己的好奇心,实现了一个简易版的老虎机:

老虎机的整体外观样式还是比较好写的,对老虎机的外观布局如果有兴趣的话可以直接参考代码,小编就不一一介绍了。文章主要介绍的是老虎机中间三个小格子和机身动画的一些实现要点。(读者想实操的话也可以自己找一张背景图当作老虎机的背景,这里小编只是自己好奇而已所以就用各种基础布局和样式实现的老虎机)

  • 格子中数字的真面目是什么?
  • 数字列表滚动前的要点
  • 格子中的数字列表是怎么滚动的?
  • “无限滚动”是怎么实现的?
  • 第二个和第三个格子的延迟滚动怎么实现的?
  • 随机的滚动结果是怎么实现的?
  • 游戏机是怎么抖动起来的?
  • 重置游戏的实现及要注意的点
  • 怎么获取老虎机的游戏结果?

格子中数字的真面目是什么?

这个问题其实很简单,格子中的内容单纯只是一个ul列表,我是只是给格子添加了overflow:hidden使格子外的数字进行了隐藏。咱们先把一个格子的数字列表拿出来讲,我们可以看到整个滚动过程中只有“1~6”共6个数字,我们可以先把这6个数字的列表给实现下。

我们利用的是ul 和 li标签做出的列表,在布局中小编只写了ul,虽然效果图中只有1~6 6种情况,但是后面可能会有更多的游戏结果选项,所以li标签就不写死在页面中,li标签通过javaScript的形式添加到ul标签中。

(这里为了方便读者读懂代码我先解释一下,小编在一开始考虑老虎机里面的内容以后可能是图片而不是数字,所以在很多地方变量或者className的命名都名为与“images”相关。)

  <ul class="images"><ul/>
 // 初始的选项列表
  let initImagesArr = [6, 5, 4, 3, 2, 1];
  // 获取第一个ul列表
  const firstImagesList = document.getElementsByClassName('images')[0];
   // 构造列表li添加到ul标签中去
  initImagesArr.forEach(item => {
    const li = document.createElement('li');
    li.innerHTML = item;
    firstImagesList.appendChild(li);
  });

这样子我们就可以获得这样的一个ul列表:

 <ul class="images">
      <li>6</li>
      <li>5</li>
      <li>4</li>
      <li>3</li>
      <li>2</li>
      <li>1</li>
  <ul/>

同理,第二个格子和第三个格子也可以利用相同的方式构造出相同的数字列表。通过给ul和li添加样式之后的效果如图:

数字列表滚动前的要点

tip: 读者可以留意一下,firstImagesList代表的是第一个数字列表,secondsImagesList为第二个数字列表,thirdImagesList为第三个数字列表。其中代码:

  const firstImagesList = document.getElementsByClassName('images')[0];
  const secondsImagesList = document.getElementsByClassName('images')[1];
  const thirdImagesList = document.getElementsByClassName('images')[2];
我们可以从动画中看出数字是从上往下开始滚动的。其实滚动的原理利用的是CSS3的transform:translateY()进行移动。那有人可能就有疑问了(小编你不是说从上往下滚动吗,按照你列表这样的布局你从上往下不是一移动就结束了吗)。

所以我们在列表移动之前要做一件事情,我们要把这个列表的初始化布局给调整一下,将全部列表都向上移动,使数字“1”移动到格子中去。

我们先声明一个初始化三个数字列表定位方法,其中参数startTranslateYHeight代表整个列表要向上移动的距离。

function initPosition(startTranslateYHeight) {
    firstImagesList.style.transform = `translateY(${startTranslateYHeight}px)`;
    secondsImagesList.style.transform = `translateY(${startTranslateYHeight}px)`;
    thirdImagesList.style.transform = `translateY(${startTranslateYHeight}px)`;
}

有了移动的方法之后,我们要计算出列表要向上移动的距离startTranslateYHeight。获取向上移动距离的步骤包括:

  1. 声明列表单独一项也就是单个li的高度,已知li标签的高度为136px;
  2. 获取一列的高度,也就是整个ul标签的高度,这个三个列表的高度都一样大,所以我们取第一个列表利用dom的内置属性scrollHeight获得列表的高度;
  3. 因为整个列表向上移动到最后数字1会留在格子中,列表中全部有6个数字,我们只需要向上移动5个数字的高度即可。也就是整个整个列表的高度减去一个li标签的高度就是我们要列表向上移动的距离。
const imageHeight = 136;
  const listHeight = firstImagesList.scrollHeight;
  const startTranslateYHeight = - listHeight + imageHeight;

有了向上移动的具体距离后,我们只要执行我们的初始化定位方法initPosition()方法即可。

格子中的数字列表是怎么滚动的?

前面我们也提到了其实滚动的原理利用的是CSS3的transform:translateY()进行移动。因为我们游戏是通过点击手柄开始的,所以我们给手柄添加一个点击事件,并在事件中给列表进行滚动,我们暂时默认滚动到最后一个数字,不考虑随机结果的情况。

如果只是滚动到最后一个数字那还是比较容易的,那我们只需要将向上移动的距离还原为0就可以了,这样子就能达到向下移动的效果。

// 点击游戏手柄开始游戏的方法
function start() {
  firstImagesList.style.transform = 'translateY(0)';
  secondsImagesList.style.transform = 'translateY(0)';
  thirdImagesList.style.transform = 'translateY(0)';
}

我们来看一下现阶段的效果:

是不是少了点什么对吧?没错,少了滚动动画。我们只需要在游戏开始时给列表加上过渡效果即可。可能有人会问为什么要在游戏开始时再加而不是一开始写样式时先写上transtion过渡。原因是这样子阿:因为小编后续要考虑到重置游戏的问题,重置过程列表会回到最开始的定位处,如果说重置过程也有过渡样式那是不太合理的,为了能够保证过渡样式是可控的小编就定义了一个添加过渡的方法,还有一个删除过渡的方法,方便我们想要有过渡动画就加上,不想有过渡动画就删除。

下面的代码意思就是分别给每个列表添加/删除过渡样式类名(className),删除过渡我们会在重置动画中使用到。

   //过渡效果 
  .transtion {
    transition: all ease 2s;
  }
  // 全部列表添加过渡效果
  function addTranstion() {
    firstImagesList.classList.add('transtion');
    secondsImagesList.classList.add('transtion');
    thirdImagesList.classList.add('transtion');
  }

  // 全部列表删除过渡效果
  function removeTranstion(imagesList) {
    firstImagesList.classList.remove('transtion');
    secondsImagesList.classList.remove('transtion');
    thirdImagesList.classList.remove('transtion');
  }

然后我们只需要只开始游戏方法中调用添加过渡方法即可:

function start() {
  // 游戏开始给全部数字列表添加过渡效果
  addTranstion();
  firstImagesList.style.transform = 'translateY(0)';
  secondsImagesList.style.transform = 'translateY(0)';
  thirdImagesList.style.transform = 'translateY(0)';
}

到这里我们已经实现了数字列表的滚动效果,但是我们只是做了个简单的从1~6的滚动,并没有做到从头开始的效果。简单来说就是滚动得没有像效果图中那么“持久”。我们接下来就是来实现一下“从头开始”,“无限滚动”的效果。

“无限滚动”是怎么实现的?

效果图中我们可以看到当数字6滚动结束之后应该会重数字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,6,5,4,3,2,1]
  // 初始的选项列表
  let initImagesArr = [6, 5, 4, 3, 2, 1];
  let imagesArr = [6, 5, 4, 3, 2, 1];
  // 加长整个选项列表,以完成一个虚假的无限滚动的效果
  new Array(20).fill('').forEach(() => {
    imagesArr = imagesArr.concat(...initImagesArr);
  })

那么此时前面添加li标签的代码就得修改一下了,将initImageArr修改为新的选项列表imagesArr

   // 构造列表li添加到ul标签中去
  imagesArr.forEach(item => {
    const li = document.createElement('li');
    li.innerHTML = item;
    firstImagesList.appendChild(li);
  });

至此我们虚假的无限滚动就已经实现完成了(如果打灭了你们对无限滚动的期待的话请不要打小编,小编心里也苦,真正的无限滚动好像不太好写,有感兴趣的小伙伴要是知道怎么无限滚动就告诉我哈,小编也来学习学习)。

第二个和第三个格子的延迟滚动怎么实现的?

无限滚动介绍完之后我们来介绍一下延迟滚动的问题,我们可以看到效果图中第二个格子是等第一个格子滚动一小会儿后才开始滚动的,第三个格子也是一样的。

其实延迟滚动实现也很简单,我们只需要给第二个数字列表和第三个数字列表各自的滚动方法中设置个定时器即可。

  // 点击游戏手柄开始游戏的方法
  function start() {
    // 游戏开始给全部数字列表添加过渡效果
    addTranstion();
    firstImagesList.style.transform = 'translateY(0)';
    // 列表2延迟0.5s后滚动
    timeout1 = setTimeout(() => {
      secondsImagesList.style.transform = 'translateY(0)';
    },500)
    // 列表3延迟1s后滚动
    timeout2 = setTimeout(() => {
      thirdImagesList.style.transform = 'translateY(0)';
    },1000)
  }

随机的滚动结果是怎么实现的?

随机的滚动结果解释起来可能会比较难以理解一点。我们再回顾一下,在上面我们实现向下滚动的原理是将向上移动的距离还原为0('translateY(0))来实现的。那试想一下如果我们还原的结果不是0,而是一个数字的高度呢?

Tip: 一个数字的高度也就是一个li标签的高度,前面我们已知一个li标签高度是136px

我们来改写代码试试:

  firstImagesList.style.transform = 'translateY(-136px)';
  // 列表2延迟0.5s后滚动
  timeout1 = setTimeout(() => {
    secondsImagesList.style.transform = 'translateY(-136px)';
  },500)
  // 列表3延迟1s后滚动
  timeout2 = setTimeout(() => {
    thirdImagesList.style.transform = 'translateY(-136px)';
  },1000)

效果如下:

我们可以看到,如果我们将定位只还原到translateY(-136px),那滚动的结果会是5。以此类推,如果我们只还原到0、-136、-136 * 2、-136 * 3、-136 * 4、-136 * 5(单位都为px)、那么我们就可以在数字列表滚动中得到6,5,4,3,2,1共6中结果。

现在我们已经能够通过改变不同的还原距离translateY()从而达到滚动结果的不同,那还有一个问题,从上面6个数中随机出一个数来怎么做呢?滚动的结果在这里不应该是由我们人为控制的。小编想了一下,还好这里最起码的要求是结果应该是从-136的倍数0,1,2,3,4,5种随机出一个数来。我们通过倍数的变化就能获取到相应的随机值。

这里我利用了js种Math对象的Math.random()方法,Math.random()方法会返回介于 0(包含) ~ 1(不包含) 之间的一个随机数, 那如果我将Math.random的结果乘以6,那我不就得到0~6(不包含6)之间的一个随机数,并且我将获取的随机数通过Math.floor()做一个向下取整,那我不就得到0,1,2,3,4,5的随机数了。

目前整个游戏的开始方法整理如下:

//  初始的选项列表initImagesArr中有6个值,也就是单列数据列表总的情况会有6种
<div class="handle" onclick="start(initImagesArr.length)">
  中间内容省略。。。
</div>
 // 单个数字的高度,即单个li标签的高度
 const imageHeight = 136;
 // resultNum为滚动结果情况的个数,这里有6个数字,也就是有6种情况
 // translateY(${-imageHeight * radom1}px)为列表1最终还原的位置,以此类推
 function start(resultNum) {
  // 游戏开始给全部数字列表添加过渡效果
  addTranstion();
  // 每个列表滚动的随机结果0~resultNum
  let radom1 = Math.floor(Math.random()*resultNum);
  let radom2 = Math.floor(Math.random()*resultNum);
  let radom3 = Math.floor(Math.random()*resultNum);
  // 列表1开始滚动
  firstImagesList.style.transform = `translateY(${-imageHeight * radom1}px)`;
  // 列表2延迟0.5s后滚动
  timeout1 = setTimeout(() => {
    secondsImagesList.style.transform = `translateY(${-imageHeight * radom2}px)`;
  },500)
  // 列表3延迟1s后滚动
  timeout2 = setTimeout(() => {
    thirdImagesList.style.transform = `translateY(${-imageHeight * radom3}px)`;
  },1000)
}

注意点1:方法中resultNum为单个数字列表滚动结果的全部可能性。另外因为全部结果的情况总数由初始数字列表initImagesArr: [1,2,3,4,5,6]中的数字个数所决定的,所以只需要将initImagesArr.length作为参数传给start()方法即可。

注意点2:三个数字列表的随机结果都不同,其中radom1为列表1的随机结果,radom2为列表2的随机结果,radom3为列表3的随机结果。Math.random()*resultNum的值为 0 * resultNum ~ 1 * resultNum(不包含1 * resultNum)。本例中resultNum为6,则结果为0 ~ 5.999999999 经过Math.floor()向下取整过后的结果为0 ~ 5。

经过以上的处理之后,我们的随机结果就已经成功实现了。

游戏机是怎么抖动起来的?

前面可能会复杂一点,这里就我们聊个稍微简单易懂的东西。从效果图中我们可以看出老虎机从开始游戏到游戏快结束时一直是在抖动的,关于这个我也给大家稍微分享一下怎么实现的。

其实就一个东西,加个动画。这里我先直接贴上代码:

  .shake {
    animation: shake 0.1s infinite;
  }

  @keyframes shake {
    25% {
      left: 49.7%;
    }
    50% {
      top: 49.7%;
    }
    75% {
      left: 50%;
    }
    100% {
      top: 50%;
    }
  }

在写样式时小编通过相对定位的形式position:absoulte 配合{left:50%;top:50%;translate(-50%,-50%)}的形式实现老虎机相对可视区域水平垂直居中对齐的效果。

// 老虎机相对可视区域水平垂直居中对齐的效果。
.machine {
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%,-50%);
}

解释一下代码,小编通过给整个老虎机从各个方向都移动一下,并且以很快的速度完成(这里用的是0.1s完成的动画),动画的循环次数为无限次infinite,从而实现了老虎机一直在抖的效果。

不过要稍微留意一点,我们这里抖动应该也是要可控的,因为老虎机在游戏快结束时会停止抖动。跟我们之前做过渡效果可控的方式一样,我们也给动画声明一个添加抖动和移除抖动的方法:

  // 给老虎机添加抖动效果
  function startShake() {
    document.getElementsByClassName('machine')[0].classList.add('shake');
  }
 // 移除老虎机抖动效果
  function stopShake() {
    document.getElementsByClassName('machine')[0].classList.remove('shake');
  }

之后我们在游戏开始的时候调用startShake(),然后在游戏快结束时调用stopShake()。不过在下面代码我们可以看到移除抖动效果是在2.6s之后才执行的,原因是第三个数字列表需要等到游戏开始1s才开始滚动,而且滚动的过渡时间为2s,那等到第三个数字列表滚动到结束总共需要3s,这里小编想要在第三个数字列表滚动结束之前将老虎机停止抖动,所以将移除抖动方法在游戏开始2.6s之后才执行。

 
// 单个数字的高度,即单个li标签的高度
 const imageHeight = 136;
 function start(resultNum) {
  // 游戏开始给全部数字列表添加过渡效果
  addTranstion();
  // 游戏开始给老虎机添加抖动效果
  startShake();
  // 每个列表滚动的随机结果0~resultNum
  let radom1 = Math.floor(Math.random()*resultNum);
  let radom2 = Math.floor(Math.random()*resultNum);
  let radom3 = Math.floor(Math.random()*resultNum);
  // 列表1开始滚动
  firstImagesList.style.transform = `translateY(${-imageHeight * radom1}px)`;
  // 列表2延迟0.5s后滚动
  timeout1 = setTimeout(() => {
    secondsImagesList.style.transform = `translateY(${-imageHeight * radom2}px)`;
  },500)
  // 列表3延迟1s后滚动
  timeout2 = setTimeout(() => {
    thirdImagesList.style.transform = `translateY(${-imageHeight * radom3}px)`;
  },1000)
  // 老虎机在延迟2.6妙后移除抖动效果
  timeout3 = setTimeout(() => {
    stopShake();
  },2600)
}

重置游戏的实现及要注意的点

老虎机从开始游戏到结束游戏的整个环节我们都已经实现完成了。但是呢,小编还想再玩一把,然后我在第二次点击开始手柄之前就想到了这个老虎机存在的缺陷,总结起来包括以下几点:

  • 游戏从开始到结束之后,再次点击开始手柄,应该将游戏进行重置
  • 重置游戏的过程中不应该出现过渡效果
  • 重置游戏的过程中机器不应该继续在摇晃
  • 如果将游戏进行重置了,第一次开始游戏方法中的定时器方法应该清空。

小编给这台老虎机做了个设定,当游戏手柄点击第一下时,游戏开始。当游戏手柄点击第二下时,游戏要结束并重置游戏。当游戏手柄点击第三下时,游戏又再度重新开始。

这里可能有个疑惑就是开始游戏手柄的点击事件只绑定了一个开始游戏start()的方法,那怎么判断游戏是开始还是重置?

<div class="handle" onclick="start(initImagesArr.length)">
  中间内容省略。。。
</div>

这里小编给start写了个flag,这个当flag为true时,会执行开始游戏的方法,当flag为false时,会执行重置游戏的方法,每次点击时将flag的值重新赋为flag的反向值即可。

  
// 游戏是否开始 true为开始游戏,false为重置游戏
  let isStart = false;
  function start(imagesArrLength) {
    // flag取反
    isStart = !isStart;
    // 开始游戏
    if (isStart) {
      // 开始游戏就给列表加过渡效果
      addTranstion();
      startShake();

      // 每个列表滚动的随机结果
      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后滚动
      timeout1 = setTimeout(() => {
        secondsImagesList.style.transform = `translateY(${-imageHeight * radom2}px)`;
      },500)

      // 列表3延迟1s后滚动
      timeout2 = setTimeout(() => {
        thirdImagesList.style.transform = `translateY(${-imageHeight * radom3}px)`;
      },1000)

      timeout3 = setTimeout(() => {
        stopShake();
      },2600)
    }else {
      // 重置游戏,这里实现重置游戏的方法
    }
  }

那重置游戏需要我们做什么呢?

1、首先当然是将所有的数字列表回到初始位置。我们在前面“数字列表滚动前的要点”中已经实现了初始化定位方法initPosition()方法,在上面else代码块中我们只要调用initPosition(startTranslateYHeight)即可将全部数字列表回到初始的位置。Tip:在前面“数字列表滚动前的要点”中我们已经介绍了startTranslateYHeight为列表向上移动的距离。

  function initPosition(startTranslateYHeight) {
      firstImagesList.style.transform = `translateY(${startTranslateYHeight}px)`;
      secondsImagesList.style.transform = `translateY(${startTranslateYHeight}px)`;
      thirdImagesList.style.transform = `translateY(${startTranslateYHeight}px)`;
  }

2、 重置游戏时数字列表在回到初始位置的过程中,由于游戏开始过程中数字列表添加了过渡动画,会导致数字列表在回到初始位置的过程也会存在过渡动画,因此我们需要调用之前先声明好的removeTranstion()来删除全部数字列表的过渡效果。

3、 重置游戏时由于第一次开始游戏过程中给老虎机添加了抖动效果,重置时应该移除抖动效果。在else代码块中调用stopShake()即可。

4、 假如在开始游戏后在很短的时间内又点击了重置游戏,这时候开始游戏中未执行的定时器中的方法应该通过clearTimeout()给予一一清除,否则在重置游戏时还是会执行开始游戏中的方法。

那么else代码块中的代码应该为:

else {
  clearTimeout(timeout1);
  clearTimeout(timeout2);
  clearTimeout(timeout3);
  // 停止抖动
  stopShake();
  // 重置时因为列表会重新移动到初始位置,所以要清除掉过渡效果
  removeTranstion();
  // 各个列表回到最初的位置
  initPosition(startTranslateYHeight);
}

怎么获取老虎机的游戏结果

是个游戏总会有个结果的。这里我们实现在老虎机游戏结束之后,将游戏结果给打印出来。由于我们的结果是通过radom1,radom2,radom3随机结果得出,当radom1为0时结果为6,random1为1时结果为5,依次类推。我们可以得到最终的结果应该为 initImagesArr[radom1], initImagesArr[radom2], initImagesArr[radom3]。

前面我们也有提到游戏结束的整个过程需要经历3s,所以我们定义一个定时器在开始游戏3s后执行并打印出游戏结果即可。

代码附录

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:

.machine {
  width: 484px;
  height: 513px;
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%,-50%);
  display: flex;
  .machine-body {
    width: 400px;
    background-color: #c8d0e3;
    height: 513px;
    border: 14px solid #2e2a27;
    border-radius: 34px;
    box-sizing: border-box;
    .rotary-table {
      width: 278px;
      border: 16px solid #2e2a27;
      border-radius: 16px;
      margin: 54px auto 0;
      display: flex;
      .screen {
        width: 82px;
        height: 136px;
        overflow: hidden;
        .images {
          list-style: none;
          margin: 0;
          padding: 0;
          border: none;
          li {
            margin: 0;
            padding: 0;
            border: 0;
            text-align: center;
            height: 136px;
            line-height: 136px;
            font-size: 30px;
            background-color: wheat;
          }
        }
        .transtion {
          transition: all ease 2s;
        }
      }
    }
    .rotary-table :nth-child(1) {
      border-right: 16px solid #2e2a27;
    }
    .rotary-table :nth-child(2) {
      border-right: 16px solid #2e2a27;
    }
    .machine-logo {
      width: 240px;
      margin: 20px auto 0;
      img {
        width: 100%;
        height: auto;
      }
    }
  }
  .base {
    width: 34px;
    height: 130px;
    background-color: #b1b8d4;
    border: 14px solid #2e2a27;
    border-left: none;
    box-sizing: border-box;
    border-top-right-radius: 24px;
    border-bottom-right-radius: 24px;
    transform: translateY(250px);
    position: relative;
    .handle {
      width: 64px;
      position: absolute;
      left: 30px;
      bottom: 28px;
      cursor: pointer;
      .handle-ball {
        width: 34px;
        height: 34px;
        background-color: #ff6169;
        border-radius: 50%;
        border: 15px solid #2e2a27;
        transform: translateY(2px);
      }
      .handle-bar {
        width: 16px;
        height: 106px;
        margin: 0 auto;
        background-color: #2e2a27;
        transform: translateY(1px);
      }
      .handle-shaft {
        width: 56px;
        height: 48px;
        border: 15px solid #2e2a27;
        border-left: none;
        border-top-right-radius: 25px;
        border-bottom-right-radius: 25px;
        background-color: #c8d0e3;
        box-sizing: border-box;
        margin: 0 auto;
      }
    }
  }
}

.shake {
  animation: shake 0.1s infinite;
}

@keyframes shake {
  25% {
    left: 49.7%;
  }
  50% {
    top: 49.7%;
  }
  75% {
    left: 50%;
  }
  100% {
    top: 50%;
  }
}

javaScript:

  
// 以下的images命名可能是考虑老虎机里的内容可能会被替换成水果等其他图片而非数字,所以将相关的内容变量命名为与images相关
  // 页面刚加载时三个列表的初始定位
  function initPosition(startTranslateYHeight) {
    firstImagesList.style.transform = `translateY(${startTranslateYHeight}px)`;
    secondsImagesList.style.transform = `translateY(${startTranslateYHeight}px)`;
    thirdImagesList.style.transform = `translateY(${startTranslateYHeight}px)`;
  }

  // 全部列表添加过渡效果
  function addTranstion() {
    firstImagesList.classList.add('transtion');
    secondsImagesList.classList.add('transtion');
    thirdImagesList.classList.add('transtion');
  }

  // 全部列表删除过渡效果
  function removeTranstion(imagesList) {
    firstImagesList.classList.remove('transtion');
    secondsImagesList.classList.remove('transtion');
    thirdImagesList.classList.remove('transtion');
  }

  // 给老虎机添加摇晃动画
  function startShake() {
    document.getElementsByClassName('machine')[0].classList.add('shake');
  }
  // 停止老虎机摇晃动画
  function stopShake() {
    document.getElementsByClassName('machine')[0].classList.remove('shake');
  }

  // 点击第一个是开始,点击第二次是充值游戏
  // startTranslateYHeight 列表的初始化时translateY的距离
  // imageHeight列表的每一项的高度
  // 列表数组的长度
  function start(imagesArrLength) {
    isStart = !isStart;
    // 开始游戏
    if (isStart) {
      // 开始游戏就给列表加过渡效果
      addTranstion();
      startShake();

      // 每个列表滚动的随机结果
      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后滚动
      timeout1 = setTimeout(() => {
        secondsImagesList.style.transform = `translateY(${-imageHeight * radom2}px)`;
      },500)

      // 列表3延迟1s后滚动
      timeout2 = setTimeout(() => {
        thirdImagesList.style.transform = `translateY(${-imageHeight * radom3}px)`;
      },1000)
      // 延迟2.6秒后停止抖动
      timeout3 = setTimeout(() => {
        stopShake();
      },2600)
      // 游戏结束后打印结果
      timeout4 = setTimeout(() => {
        console.log(initImagesArr[radom1],initImagesArr[radom2],initImagesArr[radom3]);
      },3000)
      // 重置游戏
    }else {
      // 取消上一次未执行完的方法
      clearTimeout(timeout1);
      clearTimeout(timeout2);
      clearTimeout(timeout3);
      clearTimeout(timeout4);
      stopShake();
      // 重置时因为列表会重新移动到初始位置,所以要清除掉过渡效果
      removeTranstion();
      // 各个列表回到最初的位置
      initPosition(startTranslateYHeight);
    }
  }

  // 初始的选项列表
  let initImagesArr = [6, 5, 4, 3, 2, 1];
  let imagesArr = [6, 5, 4, 3, 2, 1];
  // 加长整个选项列表,以完成一个虚假的滚动的效果
  new Array(20).fill('').forEach(() => {
    imagesArr = imagesArr.concat(...initImagesArr);
  })

  // 获取第一个列表的dom
  const firstImagesList = document.getElementsByClassName('images')[0];
  const secondsImagesList = document.getElementsByClassName('images')[1];
  const thirdImagesList = document.getElementsByClassName('images')[2];
  // 构造列表li添加到ul标签中去
  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);
  });


  // 列表单独一项的高度
  const imageHeight = 136;

  // 获取一列的高度
  const listHeight = firstImagesList.scrollHeight;

  // 初始化列表tranlateY的高度
  const startTranslateYHeight = - listHeight + imageHeight;

  // 游戏是否已经开始
  let isStart = false;

  // 三个setTimeout的返回表示符,前两个是第二列第三列列表开始滚动的延时方法,timeout3是机器停止摇晃动画的延时方法
  let timeout1 = null;
  let timeout2 = null;
  let timeout3 = null;

  // 页面刚进来时列表位置初始化
  initPosition(startTranslateYHeight);

结语

看到这里,小编想说读者你们也辛苦了,应该费了不少劲来看我的文章。虽然小编觉得这种需求在工作中几乎不会遇到,但是小编觉得这是一个锻炼自己思维能力的一个过程,如果读者们对代码有疑问或建议随时可以提出来,小编会耐心解答或虚心接受。如果喜欢文章的话也给小编点点赞支持一下,码字不易,很感谢你们的支持,最后祝大家工作顺利。

声明:文章著作权归作者所有,如有侵权,请联系小编删除。

最后

  • 欢迎加我微信(winty230),拉你进技术群,长期交流学习...
  • 欢迎关注「前端Q」,认真学前端,做个专业的技术人...
本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2021-01-13,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 前端Q 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 简介
  • 格子中数字的真面目是什么?
  • 数字列表滚动前的要点
  • 格子中的数字列表是怎么滚动的?
  • “无限滚动”是怎么实现的?
  • 第二个和第三个格子的延迟滚动怎么实现的?
  • 随机的滚动结果是怎么实现的?
  • 游戏机是怎么抖动起来的?
  • 重置游戏的实现及要注意的点
  • 怎么获取老虎机的游戏结果
  • 代码附录
  • 结语
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档