一起学Vue自定义组件之拼图小游戏
Posted Alan.hsiang
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了一起学Vue自定义组件之拼图小游戏相关的知识,希望对你有一定的参考价值。
通过学习Vue自定义组件,可以开发一些小功能,自娱自乐,巩固学习的基础知识,本文以一个简单的拼图小游戏的例子,简述Vue自定义组件的开发,调用等基本流程,仅供学习分享使用,如有不足之处,还请指正。
涉及知识点
关于Vue组件的基础知识,前篇已有介绍,本例涉及知识点如下:
- 拼图游戏,只有相邻的元素才可以交换位置,那如何判断两个元素相邻,方法如下:
- 左右相邻:y轴坐标相同,x轴相减的绝对值等于一个元素的宽度。
- 上下相邻:x轴坐标相同,y轴相减的绝对值等于一个元素的高度。
- 如何判断拼图中的可以与之交换位置的空白,方法如下:
- 通过ref引用属性,将空白属性,定义为empty,其他定义为block,以便区分。
- 如何将一张图放到每一个元素上,并只显示一块内容,方法如下:
- 将背景图的位置和元素的坐标起始位置关联起来,即将图片的向左上方平移即可。
- 元素之间的切换平滑过渡。在本例中,通过css样式设置,所有元素的移动都在0.3s内完成,达到平滑过渡的效果。
示例效果图
本例中拼图游戏一共分5关,分别是3*3,4*4等,难度逐级增加,所用图片的均是500px*500px大小,如下图所示:
当拼图完成时,询问是否进行下一关,如下所示:
下一关,效果如下所示:
其他效果图类似,只是分的行和列递增,拼图难度增加,但是处理逻辑都是相同的。
核心源码
关于Puzzle.vue源码,如下所示:
模板部分(template),主要是元素的布局,本例采用v-for动态加载,如下所示:
<template>
<div class="puzzle" :style="{width:width+'px',height:height+'px'}">
<div
v-for="(item,index) in blockPoints"
:key="item.id"
:style="{width:blockWidth+'px',
height:blockHeight+'px',
left:item.x+'px',top:item.y+'px',
backgroundImage:`url(${img})`,
backgroundPosition:`-${correctPoints[index].x}px -${correctPoints[index].y}px`,
opacity: index===blockPoints.length-1 && 0 }"
v-on:click="handleClick"
class="puzzle__block"
:ref="index===blockPoints.length-1?'empty':'block'"
:data-correctX="correctPoints[index].x"
:data-correctY="correctPoints[index].y"
></div>
</div>
</template>
脚本部分(Script),主要用于逻辑的校验和判断,如下所示:
<script>
export default {
props: {
img: {
// 图片路径
type: String,
required: true,
},
width: {
// 图片总宽度
type: Number,
default: 500,
},
height: {
// 图片总高度
type: Number,
default: 500,
},
row: {
// 行数
type: Number,
default: 3,
},
col: {
// 列数
type: Number,
default: 3,
},
},
data() {
return {
status: {
type: String,
default: "进行中......",
},
};
},
methods: {
handleClick(e) {
const blockDom = e.target;
const empthDom = this.$refs.empty[0];
const { left, top } = blockDom.style;
if (!this.isAdjacent(blockDom, empthDom)) {
return;
}
//交换元素
blockDom.style.left = empthDom.style.left;
blockDom.style.top = empthDom.style.top;
empthDom.style.left = left;
empthDom.style.top = top;
const winFlag = this.winCheck();
if (winFlag) {
// console.log('success');
this.winGame(empthDom);
}
},
isAdjacent(blockDom, empthDom) {
// 判断是否相邻
const { left: blockLeft, top: blockTop, width, height } = blockDom.style;
const { left: emptyLeft, top: emptyTop } = empthDom.style;
const xDis = Math.floor(
Math.abs(parseFloat(blockLeft) - parseFloat(emptyLeft))
);
const yDis = Math.floor(
Math.abs(parseFloat(blockTop) - parseFloat(emptyTop))
);
const flag =
(blockLeft === emptyLeft && yDis === parseInt(height)) ||
(blockTop === emptyTop && xDis === parseInt(width));
console.log(flag);
return flag;
},
winCheck() {
// 判断是否完成
const blockDomArr = this.$refs.block;
return blockDomArr.every((dom) => {
const { left: domLeft, top: domTop } = dom.style;
const { correctx: correctX, correcty: correctY } = dom.dataset;
const flag =
parseInt(domLeft) === parseInt(correctX) &&
parseInt(domTop) === parseInt(correctY);
return flag;
});
// console.log(blockDomArr.length);
},
winGame(empthDom) {
//通关
setTimeout(() => {
this.status = "胜利";
alert("恭喜通关");
empthDom.style.opacity = 1;
this.$emit("getStatus");
setTimeout(() => {
this.goToNextLevel();
}, 300);
}, 300);
},
goToNextLevel() {
const answerFlag = window.confirm("现在进行下一关么?");
if (answerFlag) {
this.status = "进行中......";
this.$emit("next");
}
},
},
computed: {
blockWidth() {
return this.width / this.col;
},
blockHeight() {
return this.height / this.row;
},
correctPoints() {
const { row, col, blockWidth, blockHeight } = this;
const arr = [];
for (let i = 0; i < row; i++) {
for (let j = 0; j < col; j++) {
arr.push({
x: j * blockWidth,
y: i * blockHeight,
id: new Date().getTime() + Math.random() * 100,
});
}
}
return arr;
},
blockPoints() {
const points = this.correctPoints;
const length = points.length; //数组的长度
const lastEle = points[length - 1]; //最后一个元素
const newArr = [...points];
newArr.length = length - 1;
//打乱顺序
newArr.sort(() => Math.random() - 0.5);
newArr.push(lastEle);
return newArr;
},
},
};
</script>
样式部分(Style),主要用于外观样式的设置,如下所示:
<style>
.puzzle {
box-sizing: content-box;
border: 2px solid #cccccc;
position: relative;
}
.puzzle__block {
border: 1px solid #ffffff;
box-sizing: border-box;
/* background-color: rebeccapurple; */
position: absolute;
transition: all 0.3s;
}
</style>
拼图组件的调用App.vue
首先组件需要引入和注册,采用使用,如下所示:
<script>
import puzzle from "./Puzzle";
export default {
components: {
puzzle,
},
data() {
return {
level: 0,
puzzleConfig: [
{ img: "./img/001.jpg", row: 3, col: 3 },
{ img: "./img/002.jpg", row: 4, col: 4 },
{ img: "./img/003.jpg", row: 5, col: 5 },
{ img: "./img/004.jpg", row: 6, col: 6 },
{ img: "./img/005.jpg", row: 7, col: 7 },
],
status: "进行中......",
};
},
methods: {
handleNext() {
console.log("next");
this.status = this.$refs.dpuzzle.status;
this.level++;
if (this.level == this.puzzleConfig.length - 1) {
const answerFlag = window.confirm("已经是最后一关了,需要重新开始么?");
if (answerFlag) {
this.level = 0;
}
}
},
getStatus() {
this.status = this.$refs.dpuzzle.status;
},
},
};
</script>
组件的调用,如下所示:
<template>
<div>
<h3>[拼图游戏]当前是第{{level+1}}关,当前状态[{{status}}]</h3>
<puzzle ref="dpuzzle" @getStatus="getStatus" @next="handleNext" v-bind="puzzleConfig[level]" />
<!-- <button @click="handleNext" style="width:20px,height:20px" value="下一关">下一关</button> -->
</div>
</template>
注意事项:
如果获取组件内部的元素的值,在组件调用时采用ref属性,然后获取组件内的data属性值。
组件内如果调用父组件的方法,本文采用触发注册事件的方式this.$emit("next");
如果需要学习参考源码的朋友,可以点击源码链接进行下载。
备注
浪淘沙令·帘外雨潺潺
作者:李煜【五代十国南唐后主】
帘外雨潺潺,春意阑珊。
罗衾不耐五更寒。
梦里不知身是客,一晌贪欢。
独自莫凭栏,无限江山,别时容易见时难。
流水落花春去也,天上人间。
以上是关于一起学Vue自定义组件之拼图小游戏的主要内容,如果未能解决你的问题,请参考以下文章
LVGL开发 | lv_lib_100ask之lvgl数字对拼图小游戏(lv_100ask_memory_game)-接口简单使用便捷
LVGL开发 | lv_lib_100ask之lvgl数字对拼图小游戏(lv_100ask_memory_game)-接口简单使用便捷
LVGL开发 | lv_lib_100ask之lvgl数字对拼图小游戏(lv_100ask_memory_game)-接口简单使用便捷