一起学Vue自定义组件之拼图小游戏

  • A+
所属分类:Web前端
摘要

通过学习Vue自定义组件,可以开发一些小功能,自娱自乐,巩固学习的基础知识,本文以一个简单的拼图小游戏的例子,简述Vue自定义组件的开发,调用等基本流程,仅供学习分享使用,如有不足之处,还请指正。

通过学习Vue自定义组件,可以开发一些小功能,自娱自乐,巩固学习的基础知识,本文以一个简单的拼图小游戏的例子,简述Vue自定义组件的开发,调用等基本流程,仅供学习分享使用,如有不足之处,还请指正。

涉及知识点

关于Vue组件的基础知识,前篇已有介绍,本例涉及知识点如下:

  • 拼图游戏,只有相邻的元素才可以交换位置,那如何判断两个元素相邻,方法如下:
    • 左右相邻:y轴坐标相同,x轴相减的绝对值等于一个元素的宽度。
    • 上下相邻:x轴坐标相同,y轴相减的绝对值等于一个元素的高度。
  • 如何判断拼图中的可以与之交换位置的空白,方法如下:
    • 通过ref引用属性,将空白属性,定义为empty,其他定义为block,以便区分。
  • 如何将一张图放到每一个元素上,并只显示一块内容,方法如下:
    • 将背景图的位置和元素的坐标起始位置关联起来,即将图片的向左上方平移即可。
  • 元素之间的切换平滑过渡。在本例中,通过css样式设置,所有元素的移动都在0.3s内完成,达到平滑过渡的效果。

示例效果图

本例中拼图游戏一共分5关,分别是3*3,4*4等,难度逐级增加,所用图片的均是500px*500px大小,如下图所示:

一起学Vue自定义组件之拼图小游戏一起学Vue自定义组件之拼图小游戏

当拼图完成时,询问是否进行下一关,如下所示:

一起学Vue自定义组件之拼图小游戏

下一关,效果如下所示:

一起学Vue自定义组件之拼图小游戏一起学Vue自定义组件之拼图小游戏

其他效果图类似,只是分的行和列递增,拼图难度增加,但是处理逻辑都是相同的。

核心源码

关于Puzzle.vue源码,如下所示:

模板部分(template),主要是元素的布局,本例采用v-for动态加载,如下所示:

一起学Vue自定义组件之拼图小游戏一起学Vue自定义组件之拼图小游戏

 1 <template>  2   <div class="puzzle" :style="{width:width+'px',height:height+'px'}">  3     <div  4       v-for="(item,index) in blockPoints"  5       :key="item.id"  6       :style="{width:blockWidth+'px',  7         height:blockHeight+'px',  8         left:item.x+'px',top:item.y+'px',  9         backgroundImage:`url(${img})`, 10         backgroundPosition:`-${correctPoints[index].x}px -${correctPoints[index].y}px`, 11         opacity: index===blockPoints.length-1 && 0 }" 12       v-on:click="handleClick" 13       class="puzzle__block" 14       :ref="index===blockPoints.length-1?'empty':'block'" 15       :data-correctX="correctPoints[index].x" 16       :data-correctY="correctPoints[index].y" 17     ></div> 18   </div> 19 </template>

View Code

脚本部分(Script),主要用于逻辑的校验和判断,如下所示:

一起学Vue自定义组件之拼图小游戏一起学Vue自定义组件之拼图小游戏

  1 <script>   2 export default {   3   props: {   4     img: {   5       // 图片路径   6       type: String,   7       required: true,   8     },   9     width: {  10       // 图片总宽度  11       type: Number,  12       default: 500,  13     },  14     height: {  15       // 图片总高度  16       type: Number,  17       default: 500,  18     },  19     row: {  20       // 行数  21       type: Number,  22       default: 3,  23     },  24     col: {  25       // 列数  26       type: Number,  27       default: 3,  28     },  29   },  30   data() {  31     return {  32       status: {  33         type: String,  34         default: "进行中......",  35       },  36     };  37   },  38   methods: {  39     handleClick(e) {  40       const blockDom = e.target;  41       const empthDom = this.$refs.empty[0];  42       const { left, top } = blockDom.style;  43       if (!this.isAdjacent(blockDom, empthDom)) {  44         return;  45       }  46       //交换元素  47       blockDom.style.left = empthDom.style.left;  48       blockDom.style.top = empthDom.style.top;  49       empthDom.style.left = left;  50       empthDom.style.top = top;  51       const winFlag = this.winCheck();  52       if (winFlag) {  53         //   console.log('success');  54         this.winGame(empthDom);  55       }  56     },  57     isAdjacent(blockDom, empthDom) {  58       // 判断是否相邻  59       const { left: blockLeft, top: blockTop, width, height } = blockDom.style;  60       const { left: emptyLeft, top: emptyTop } = empthDom.style;  61       const xDis = Math.floor(  62         Math.abs(parseFloat(blockLeft) - parseFloat(emptyLeft))  63       );  64       const yDis = Math.floor(  65         Math.abs(parseFloat(blockTop) - parseFloat(emptyTop))  66       );  67       const flag =  68         (blockLeft === emptyLeft && yDis === parseInt(height)) ||  69         (blockTop === emptyTop && xDis === parseInt(width));  70       console.log(flag);  71       return flag;  72     },  73     winCheck() {  74       // 判断是否完成  75       const blockDomArr = this.$refs.block;  76       return blockDomArr.every((dom) => {  77         const { left: domLeft, top: domTop } = dom.style;  78         const { correctx: correctX, correcty: correctY } = dom.dataset;  79         const flag =  80           parseInt(domLeft) === parseInt(correctX) &&  81           parseInt(domTop) === parseInt(correctY);  82         return flag;  83       });  84       // console.log(blockDomArr.length);  85     },  86     winGame(empthDom) {  87       //通关  88       setTimeout(() => {  89         this.status = "胜利";  90         alert("恭喜通关");  91         empthDom.style.opacity = 1;  92         this.$emit("getStatus");  93         setTimeout(() => {  94           this.goToNextLevel();  95         }, 300);  96       }, 300);  97     },  98     goToNextLevel() {  99       const answerFlag = window.confirm("现在进行下一关么?"); 100       if (answerFlag) { 101         this.status = "进行中......"; 102         this.$emit("next"); 103       } 104     }, 105   }, 106   computed: { 107     blockWidth() { 108       return this.width / this.col; 109     }, 110     blockHeight() { 111       return this.height / this.row; 112     }, 113     correctPoints() { 114       const { row, col, blockWidth, blockHeight } = this; 115       const arr = []; 116       for (let i = 0; i < row; i++) { 117         for (let j = 0; j < col; j++) { 118           arr.push({ 119             x: j * blockWidth, 120             y: i * blockHeight, 121             id: new Date().getTime() + Math.random() * 100, 122           }); 123         } 124       } 125       return arr; 126     }, 127     blockPoints() { 128       const points = this.correctPoints; 129       const length = points.length; //数组的长度 130       const lastEle = points[length - 1]; //最后一个元素 131       const newArr = [...points]; 132       newArr.length = length - 1; 133       //打乱顺序 134       newArr.sort(() => Math.random() - 0.5); 135       newArr.push(lastEle); 136       return newArr; 137     }, 138   }, 139 }; 140 </script>

View Code

样式部分(Style),主要用于外观样式的设置,如下所示:

一起学Vue自定义组件之拼图小游戏一起学Vue自定义组件之拼图小游戏

 1 <style>  2 .puzzle {  3   box-sizing: content-box;  4   border: 2px solid #cccccc;  5   position: relative;  6 }  7 .puzzle__block {  8   border: 1px solid #ffffff;  9   box-sizing: border-box; 10   /* background-color: rebeccapurple; */ 11   position: absolute; 12   transition: all 0.3s; 13 } 14 </style>

View Code

 拼图组件的调用App.vue

首先组件需要引入和注册,采用使用,如下所示:

一起学Vue自定义组件之拼图小游戏一起学Vue自定义组件之拼图小游戏

 1 <script>  2 import puzzle from "./Puzzle";  3 export default {  4   components: {  5     puzzle,  6   },  7   data() {  8     return {  9       level: 0, 10       puzzleConfig: [ 11         { img: "./img/001.jpg", row: 3, col: 3 }, 12         { img: "./img/002.jpg", row: 4, col: 4 }, 13         { img: "./img/003.jpg", row: 5, col: 5 }, 14         { img: "./img/004.jpg", row: 6, col: 6 }, 15         { img: "./img/005.jpg", row: 7, col: 7 }, 16       ], 17       status: "进行中......", 18     }; 19   }, 20   methods: { 21     handleNext() { 22       console.log("next"); 23       this.status = this.$refs.dpuzzle.status; 24       this.level++; 25       if (this.level == this.puzzleConfig.length - 1) { 26         const answerFlag = window.confirm("已经是最后一关了,需要重新开始么?"); 27         if (answerFlag) { 28           this.level = 0; 29         } 30       } 31     }, 32     getStatus() { 33       this.status = this.$refs.dpuzzle.status; 34     }, 35   }, 36 }; 37 </script>

View Code

组件的调用,如下所示:

一起学Vue自定义组件之拼图小游戏一起学Vue自定义组件之拼图小游戏

1 <template> 2   <div> 3     <h3>[拼图游戏]当前是第{{level+1}}关,当前状态[{{status}}]</h3> 4     <puzzle ref="dpuzzle" @getStatus="getStatus" @next="handleNext" v-bind="puzzleConfig[level]" /> 5     <!-- <button @click="handleNext" style="width:20px,height:20px" value="下一关">下一关</button> --> 6   </div> 7 </template>

View Code

 注意事项:

如果获取组件内部的元素的值,在组件调用时采用ref属性,然后获取组件内的data属性值。

组件内如果调用父组件的方法,本文采用触发注册事件的方式this.$emit("next");

如果需要学习参考源码的朋友,可以点击源码链接进行下载。

源码链接

备注

浪淘沙令·帘外雨潺潺

 

作者:李煜【五代十国南唐后主】

帘外雨潺潺,春意阑珊。

罗衾不耐五更寒。

梦里不知身是客,一晌贪欢。

独自莫凭栏,无限江山,别时容易见时难。

流水落花春去也,天上人间。