Photo by Nada Gamal on Unsplash
如果你刚开始学习 Vue,想巩固基础知识,那么你可以试试通过这个有趣的练习来创建一个好玩的游戏。
在这篇文章中,我将逐步教你用 Vue.js 创建一个记忆卡片游戏。
这篇文章会介绍以下知识点:
- 使用 v-for 命令循环遍历一个数组对象
- 使用 v-bind 指令动态控制类名和样式
- 添加 Methods 和 Computed 属性
- 通过 Vue.set 方法向一个对象动态添加属性
- 使用 setTimeout 方法延迟 JavaScript 插件加载
- JavaScript 对象的浅拷贝和深拷贝
- 使用 Lodash 工具库
我们开始学习吧。
准备(包括项目所需的库)
第一步很简单,从 CDN 导入库到我们的 HTML5 基础代码中,这样就可以开始我们的小型项目了。
html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Memory Card Gametitle>
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/css/bootstrap.min.css"integrity="sha384-Vkoo8x4CGsO3+Hhxv8T/Q5PaXtkKtu6ug5TOeNV6gBiFeWPGFN9MuhOf23Q9Ifjh" crossorigin="anonymous">
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js">script>
head>
<body>
body>
html>
允许用户看到卡片网格
接下来,我们定义一些必要的 HTML 页面结构、CSS 样式和一个基础的 Vue 实例,这样用户就看得到这些卡片网格了。
Vue 实例
创建一个 Vue 的实例,在 data 属性内部定义一个 cards 属性用于存放卡片列表。
let app = new Vue({
el: '#app',
data:{
cards: [
{
name: 'Apple',
img: 'apple.gif',
},
{
name: 'Banana',
img: 'banana.gif',
},
{
name: 'Orange',
img: 'orange.jpg',
},
{
name: 'Pineapple',
img: 'pineapple.png',
},
{
name: 'Strawberry',
img: 'strawberry.png',
},
{
name: 'watermelon',
img: 'watermelon.jpg',
},
],
},
});
数组内的每个对象包含两个属性:图片的名字(用于匹配)和卡片上的图片。
HTML
我们已经在 Vue 实例中准备好了数据,可以在 VueJS 中通过 v-for 指令循环遍历它们。
<div id="app">
<div class="row">
<div class="col-md-6 col-lg-6 col-xl-5 mx-auto">
<div class="row justify-content-md-center">
<div v-for="card in cards" class="col-auto mb-3 flip-container">
<div class="memorycard">
<div class="front border rounded shadow"><img width="100" height="150" src="/assets/images/memorycard/pattern3.jpeg">div>
<div class="back rounded border"><img width="100" height="150" :src="'/assets/images/memorycard/'+card.img">div>
div>
div>
div>
div>
div>
div>
我们使用了一些基础的 Bootstrap 框架内容搭配 VueJS 的 v-for 指令来循环遍历这些卡片,让它们以网格的形式展示出来。
每张记忆卡片由两部分组成:
- 正面:这里是一张所有卡片都会用到的公共图片(默认卡片显示的样子)
- 背面:这里包含每张卡片实际的图片(默认设置为隐藏)
添加一些基础的 CSS 样式,这样我们就只展示卡片的正面(默认卡片显示的样子):
.flip-container {
-webkit-perspective: 1000;
-moz-perspective: 1000;
-o-perspective: 1000;
perspective: 1000;
min-height: 120px;
cursor: pointer;
}
.front,
.back {
-webkit-backface-visibility: hidden;
-moz-backface-visibility: hidden;
-o-backface-visibility: hidden;
backface-visibility: hidden;
-webkit-transition: 0.6s;
-webkit-transform-style: preserve-3d;
-moz-transition: 0.6s;
-moz-transform-style: preserve-3d;
-o-transition: 0.6s;
-o-transform-style: preserve-3d;
-ms-transition: 0.6s;
-ms-transform-style: preserve-3d;
transition: 0.6s;
transform-style: preserve-3d;
top: 0;
left: 0;
width: 100%;
}
.back {
-webkit-transform: rotateY(-180deg);
-moz-transform: rotateY(-180deg);
-o-transform: rotateY(-180deg);
-ms-transform: rotateY(-180deg);
transform: rotateY(-180deg);
position: absolute;
}
刷新页面,然后你应该看到 6 张正面的卡片以网格的形式展示,而每张卡片上的图片隐藏在背面。
卡片的正面(通过 v-for 循环指令展示)
翻转卡片
接下来,给卡片绑定一个事件,这样每当我们点击时,它应该翻转并显示背面的图片。
在原始的卡片数组的基础上添加另一个属性。这将确定当前卡片是否被翻转。
添加下面的 CSS 样式。当类名 flipped 添加到卡片的类名上时,将展示卡片的背面。同时,该样式可以设置一个好看的翻转动效。
.flip-container.flipped .back {
-webkit-transform: rotateY(0deg);
-moz-transform: rotateY(0deg);
-o-transform: rotateY(0deg);
-ms-transform: rotateY(0deg);
transform: rotateY(0deg);
}
.flip-container.flipped .front {
-webkit-transform: rotateY(180deg);
-moz-transform: rotateY(180deg);
-o-transform: rotateY(180deg);
-ms-transform: rotateY(180deg);
transform: rotateY(180deg);
}
使用 Vue 的 created 生命周期函数添加新的属性,然后添加一个 flipCard 方法来翻转卡片。
created(){
this.cards.forEach((card) => {
card.isFlipped = false;
});
},
methods:{
flipCard(card){
card.isFlipped = true;
}
}
给卡片绑定点击事件,调用 flipCard 方法,然后使用 v-bind 指令给卡片绑定 flipped 类。
...
for="card in cards" class="col-auto mb-3 flip-container" :class="{ 'flipped': card.isFlipped }" @click="flipCard(card)">
...
似乎还不错,我们看看点击一下卡片是否会翻转。
点击卡片未翻转
这个方法行不通,为什么呢?
回到刚才在生命周期函数中,我们在那里遍历卡片列表,并添加了一个新的属性,isFlipped——看上去没问题,但是 Vue 不喜欢这样。
想要让一个对象上新的属性生效,你需要使用 Vue.set 方法将它们添加到对象中。
created(){
this.cards.forEach((card) => {
Vue.set(card,'isFlipped',false)
});
},
现在卡片应该在点击时会翻转了:
挺好的!我们继续写代码。
匹配和洗牌
对,没错!使用这些卡片制作一个记忆游戏,我们需要每张卡片都正好有跟它匹配的卡片。同时我们也需要在游戏开始时清洗卡片的排列顺序。
在 Vue 实例中添加一个 memoryCards 属性,存放翻转过的卡片(也就是说,每张翻转后的卡片,都有另一张卡片和它背面的图片)。
...
memoryCards: [],
...
匹配
将卡片数组拼接起来,赋值给 memoryCards 属性,以实现每两张卡片背面的图片相同。
改变在 HTML 中的 v-for 指令遍历对象,让它从原来的 cards 数组改变到 memoryCards 数组。
for="card in memoryCards" class="col-auto mb-3 flip-container" :class="{ 'flipped': card.isFlipped }" @click="flipCard(card)">
然后,修改 created 方法,将数组拼接到 memoryCards 中:
created(){
this.cards.forEach((card) => {
Vue.set(card,'isFlipped',false)
});
var cards1 = this.cards;
var cards2 = this.cards;
this.memoryCards = this.memoryCards.concat(cards1, cards2);
},
是不是挺简单?
但这么做有两个问题:
- 直接让 cards1 等于 this.cards,并不能产生一个新的用于匹配的 cards 对象,cards1 指向的仍然是原始对象。
- cards1 和 cards2 指的是同一个对象。
改变 memoryCards 对象中的任何属性会引起两个匹配的数组同时变化。
我们可以试试用深拷贝来解决这个问题。
什么是深拷贝?
对于对象或数组中包含其他对象和数组的情况,要想拷贝这些元素需要通过深拷贝。否则,当改变嵌套引用上的数据时,原始对象和数组中的数据也会发生改变。
进行深拷贝的方法有很多,我们将使用最简单也是最常用的方法,使用 Lodash 库。
那么,什么是 Lodash 库?
Lodash 处理了对数组、数字、对象、字符串等类型的一些复杂操作,让 JavaScript 变得更方便使用。
在这个例子中,Lodash 有一个方法能让深拷贝的操作变得极其简单。
首先,通过下载源码或者使用 CDN 引用将 Lodash 包含在页面中。