通过在30分钟内构建一个记忆游戏来学习JS,CSS和HTML!
本教程介绍了一些基本的关于HTML5,CSS3和JavaScript概念。 我们将讨论数据属性,定位,透视,转换,flexbox,事件处理,超时和三元表达式。 读懂此文章不需要大家有许多编程方面的知识。 如果您已经知道HTML,CSS和JS的用途,那就绰绰有余了!
目录:
- 项目结构
- HTML
- CSS
- 翻转卡片
- 匹配卡片
- 锁定
- 点击同一卡片
- 洗牌
- 结语
一、项目结构
让我们在终端中创建项目文件:
![abfb36ab1797533d9c4eac84ab23f301.png](https://i-blog.csdnimg.cn/blog_migrate/aa2a53ac30578475e4995523a826abee.jpeg)
二、HTML
连接css和js文件的初始页面模板。
![cba9ab168782ae7e9e802c3b33f91d29.png](https://i-blog.csdnimg.cn/blog_migrate/27485f5b217f1d77e8a9c7057055b3b0.jpeg)
这个游戏有12张卡片。每个卡片由一个名为.memory-card的容器div组成,其中包含两个img元素。第一个代表卡片的front-face(意为正面),第二个代表卡片的back-face(意为背面)。
![3d23df74c1e988b6467327a677b78436.png](https://i-blog.csdnimg.cn/blog_migrate/0d6eadbef26ce12953fa395af46ad80c.jpeg)
![11c415760d128876a2b522451d574804.png](https://i-blog.csdnimg.cn/blog_migrate/dde6c810a52da570341c816eb3ff6881.jpeg)
我们可以在Memory Game Repo下载该项目的资源文件。 这组卡片将被包装在section容器元素中。最终代码结果是这样的:
![d0cb464c4a8b11c7d68beab612d1dff2.png](https://i-blog.csdnimg.cn/blog_migrate/4f9a2a317ce1e383fb3929b7e76baa76.jpeg)
![3389d412e2365107a5cd9d273828e3f0.png](https://i-blog.csdnimg.cn/blog_migrate/9a86ad11b04eb9038d885b82da511e5e.jpeg)
![07ec5a816ec8cd47535f5dd534b8a73e.png](https://i-blog.csdnimg.cn/blog_migrate/465beec67062a9c661f33ff0645c441a.jpeg)
三、CSS
我们将使用一个简单但非常有用的重置,适用于所有项目:
![0c51a58b5aa9250d9b73d7eed67210fb.png](https://i-blog.csdnimg.cn/blog_migrate/e8d05edf47c773b54b39ff994eacc9e6.jpeg)
box-sizing: border-box属性使元素充满整个边框,因此我们可以跳过数学计算。
通过设置display: flex到body和margin: auto到.memory-game容器,它将垂直地和水平地居中。 .memory-game也将是一个flex-container。默认情况下,里面的元素会缩小宽度来适应这个容器。通过将flex-wrap设置为wrap,flex-items会根据弹性元素的大小进行自适应。
![b03916a5c51b3beca4bf87e246366aed.png](https://i-blog.csdnimg.cn/blog_migrate/94e4963ad25a75a4c65c760577a238c0.jpeg)
每个卡片的width(意为宽度)和 height(意为高度)都是用calc() CSS函数计算的。我们将width设置为25%,height设置为33.333% ,并从margin(意为边距)中减去10px,来制作三行四张牌。
对于.memory-card子元素,我们添加position: relative,这样我们就可以相对它进行子元素的绝对定位。
把属性front-face和back-face的属性设置为position: absolute,这样就可以从原始位置移除元素,并使它们堆叠在一起。
![ed6975c38dcc99001ea1ff61a5af9e87.png](https://i-blog.csdnimg.cn/blog_migrate/15f720cdf3ccafadce866ca233eb9fce.jpeg)
这时页面模版看上去应该是这样:
![ba5a1e1069b56606b5312f586d2c7b52.png](https://i-blog.csdnimg.cn/blog_migrate/e3b8c623228989c929a54835f8954e00.jpeg)
让我们也添加一个点击效果。:active伪类将在每次点击元素时触发。它引发一个 0.2秒的过渡:
![ac18d678dacec8fde848fb66902aa081.png](https://i-blog.csdnimg.cn/blog_migrate/7f434ebe47cb9d0bf5ad1cd4d90d0feb.jpeg)
![cd4a2c8ace7bac8e258f6f43921fd5ae.gif](https://i-blog.csdnimg.cn/blog_migrate/66411ad83a4a735e5442a464ad59b9cc.gif)
四、翻转卡片
要在单击时翻转卡片,我们需要向元素添加类别flip(意为翻转)。 为此,让我们使用document.querySelectorAll选择所有的memory-card元素。 然后使用forEach循环遍历它们并附加一个事件监听器。 每次卡片被点击时,都会触发flipCard(意为翻转卡片)功能。 this变量表示被单击的卡片。 该函数访问元素的classList并切换flip类:
![c2e1e78b919aea60ed129669d2343fe7.png](https://i-blog.csdnimg.cn/blog_migrate/ed3346aff0fd9a2d27747a0e64f5312d.jpeg)
![1f3490bb735d1b8c86ae50527a0406ce.png](https://i-blog.csdnimg.cn/blog_migrate/f087b15b83f8eaa1cf9f3b8816cd7af2.jpeg)
在CSS里flip类把卡片旋转了180度:
![216a0bce9b43085f0d9dfc5575dea3ae.png](https://i-blog.csdnimg.cn/blog_migrate/d0e631a891660951aaba631e10e5304b.jpeg)
为了产生3D翻转效果,我们将把perspective属性添加到.memory-game中。这个属性用来设置对象与用户在 z轴上的距离。数值越低,透视效果越大。为了达到最佳的效果,让我们设置为1000px:
![6a9c536249290266f319160731bb74cf.png](https://i-blog.csdnimg.cn/blog_migrate/8f137496cb6ea25241da91fdb13c8faa.jpeg)
对于.memory-card元素,我们添加transform-style: preserve-3d属性,这样就把卡片置于在父节点中创建的3D空间中,而不是将其平铺在z = 0平面上(transform-style)。
![d6d0bb42d0012b091602575cc4a67581.png](https://i-blog.csdnimg.cn/blog_migrate/57f2f72cad1b1ced07f8bff0f11d3b0b.jpeg)
现在,我们需要把transition属性的值设置为transform就可以生成动态效果了:
![22c1c68df4dfff1b15c17b5473b7e84a.png](https://i-blog.csdnimg.cn/blog_migrate/b4bd0e140cc47d200e339a3671eaef61.jpeg)
所以,我们使得卡片可以3D翻转了,耶! 但为什么卡片的另一面不出现呢? 现在,.front-face和.back-face都堆叠在一起,因为它们被绝对定位了。 每个元素都有一个back face(意为背面),这是它front face(意为正面)的镜像。 属性backface-visibility默认为visible(意为可见的),因此当我们翻转卡片时,我们得到的是背面的JS徽章。
![aded775452d3a53b26870cb16834fff2.gif](https://i-blog.csdnimg.cn/blog_migrate/c3ee4e50ea438bc96a3b7399fc9061b3.gif)
为了显示它背面的图像,让我们把backface-visibility: hidden应用到.front-face和.back-face。
![f715b31cee0d84b4bc8ebab62b8d29ab.png](https://i-blog.csdnimg.cn/blog_migrate/38acc676de0323f2800824b31aaaa749.jpeg)
如果我们刷新页面并翻转一张卡片,它就消失了!
![2c04bd97e8f24161dcefd4662a7dd21c.gif](https://i-blog.csdnimg.cn/blog_migrate/8eb194fc195e6363161c65b559ac27c3.gif)
因为我们把两个图像都隐藏在了背面,所以另一面什么也没有。接下来我们需要把.front-face旋转180度:
![72fed3d73a64dc2474d1bb37fffacbcf.png](https://i-blog.csdnimg.cn/blog_migrate/e5e7dff6f36134280dfd54b5f8041ca3.jpeg)
现在,我们有了想要的翻转效果!
![a3d634ffc861075cd502ca2346dc2738.gif](https://i-blog.csdnimg.cn/blog_migrate/876a5ef767a382f27f7e0d77f0d2b615.gif)
五、匹配卡片
现在我们已经完成翻转卡片的功能之后,接下来我们来处理匹配的逻辑。 当我们点击第一张牌时,它需要等待另一张牌被翻转。变量hasFlippedCard和flippedCard将管理翻转状态。如果没有翻转的卡片,hasFlippedCard被设置为true, flippedCard被设置为已点击的卡片。让我们切换toggle方法来add(意为添加):
![4a39350d29197e426ab76ed9c600b494.png](https://i-blog.csdnimg.cn/blog_migrate/29f1ef3434abfd7d3feacac4d32dcc35.jpeg)
现在,当用户点击第二张牌时,我们将进入else块。我们会检查一下它们是否匹配。为了做到这一点,我们需要做到能够识别每一张卡片。
每当我们想向HTML元素添加额外的信息时,我们就可以使用数据属性。通过使用以下语法:data-*,其中,*可以是任何单词,该属性将插入元素的dataset属性中。所以,让我们为每张卡片添加一个data-framework:
![703a3c7e2b2dc5714b6ac9e9d07ccafa.png](https://i-blog.csdnimg.cn/blog_migrate/62ddafae0f514189cddba1f21768318f.jpeg)
![c5c690d95640e593394a15c537669708.png](https://i-blog.csdnimg.cn/blog_migrate/3e7afade12a23c24a3ed30d7bb3b9b5a.jpeg)
![f49bbe6ca092ec21eb86d197dea3d27a.png](https://i-blog.csdnimg.cn/blog_migrate/ea84ee27202e26be2e782e0209fb8098.jpeg)
所以现在我们可以通过访问两个卡片数据集来检查匹配。 让我们将匹配逻辑提取到它自己的方法checkForMatch(),并将hasFlippedCard设置为false。 如果匹配,则调用disableCards()并分离两个卡片上的事件侦听器,以防止再一次翻转。 否则,unflipCards()会将两张卡都恢复成超过1500毫秒的超时,从而删除.flip类: 把全部代码组合在一起:
![64663115fe52d2cdf82433a296cfb527.png](https://i-blog.csdnimg.cn/blog_migrate/1c56650041b47f21f1c30708355950de.jpeg)
![2b71df626ab8e7494057491fc685b286.png](https://i-blog.csdnimg.cn/blog_migrate/604714334527b6cbde48637524a660e7.jpeg)
编写匹配条件的更简练的方法是使用三元运算符。 它由三个块组成。 第一个块是要判断的条件。 如果条件符合就执行第二个块,否则执行的块是第三个:
![266a0d3312e98cd47b8d114365fad9ea.png](https://i-blog.csdnimg.cn/blog_migrate/44da12c058e17e5b083906eb2c615ffa.jpeg)
六、锁定
现在我们已经完成了匹配逻辑,接着为了避免同时转动两组卡片,我们还需要锁定它们,否则翻转将会失败。 我们先声明一个lockBoard变量。 当玩家点击第二张卡片时,lockBoard将设置为true,条件 if (lockBoard) return;在卡片被隐藏或匹配之前会阻止其他卡片翻转:
![eda58a1c46f7cab19ef36ca6196bdbad.png](https://i-blog.csdnimg.cn/blog_migrate/85fea2e76e87c9a0c158ae1c5d2f4745.jpeg)
![594039c3f48eb2c358de5ea96d03b7bd.png](https://i-blog.csdnimg.cn/blog_migrate/6884f6c8ea2f531da878af1216afa6c0.jpeg)
七、点击同一卡片
可能会出现玩家在同一张卡上点击两次的情况。 如果匹配条件判断为true,从该卡片上删除事件侦听器。
![fa5dfdc8de3e8f0b1ecdc85d7ce3b127.gif](https://i-blog.csdnimg.cn/blog_migrate/4b8d399e2664679703d23239254542da.gif)
为了防止这种情况,需要检查当前点击的卡片是否等于firstCard,如果是肯定的则返回。
![8b5884c6ac8e5dde352a10e4d635d04d.png](https://i-blog.csdnimg.cn/blog_migrate/cafbbce725599069cdc5f9ae14f0996a.jpeg)
变量 firstCard和 secondCard需要在每一轮之后被重置,所以让我们将它提取到一个新方法 resetBoard()中, 再其中写上 hasFlippedCard = false;和 lockBoard = false。ES6的解构赋值功能 [var1, var2] = ['value1', 'value2']允许我们把代码写得超短:
![e6d6c443ac35d15d2860d9b88b203a71.png](https://i-blog.csdnimg.cn/blog_migrate/66e8d54b76321cf1edb10fa6a51259e5.jpeg)
接着我们调用新方法disableCards()和unflipCards():
![d7cdfa1df4b70c070697fc9a590b86a5.png](https://i-blog.csdnimg.cn/blog_migrate/981b910c1d1ac20b6cc1d1e370f0f38b.jpeg)
![9c82510789a2d1958f287ad446199c9f.png](https://i-blog.csdnimg.cn/blog_migrate/066cb8c6a5a7f0c3ce46bcd012cf8ad3.jpeg)
八、洗牌
我们的游戏现在看起来相当不错,但是如果不能洗牌就失去了乐趣,所以我们现在来处理这个功能。 当 display: flex在容器上被声明时,flex-items会按照组和源的顺序进行排序。 每个组由order属性定义,该属性包含正整数或负整数。 默认情况下,每个flex-item都将其order属性设置为0,这意味着它们都属于同一个组,并将按源的顺序排列。 如果有多个组,则首先按组升序顺序排列。
游戏中有12张牌,因此我们将迭代它们,生成0到12之间的随机数并将其分配给flex-item order属性:
![b1278be95fffedc6443acb1678d1a88e.png](https://i-blog.csdnimg.cn/blog_migrate/fc778b72bc55f2bf7c3135180ea054e4.jpeg)
为了调用shuffle函数,让它成为一个立即调用函数表达式(IIFE),这意味着它将在声明后立即执行。 脚本应如下所示:
![a02b15724eb4073ba6efa698fca4f76d.png](https://i-blog.csdnimg.cn/blog_migrate/d36fa0c8238b6430ee8df489e6f45fae.jpeg)
![5c70e9deb18754624c055b0ef5852b9c.png](https://i-blog.csdnimg.cn/blog_migrate/d4dd04cf57e10fa8e275ca2889350e54.jpeg)
![71446f2074d5fe33f9cf4142455271a2.png](https://i-blog.csdnimg.cn/blog_migrate/08ff67598f60c61747a8b78c898e5f7d.jpeg)
结语
点赞,让更多的人也能看到这篇内容(收藏不点赞,都是耍流氓-_-)
关注 我 ,享受文章首发体验!
每周重点攻克一个前端技术难点。如有问题可以私信我!