zoukankan      html  css  js  c++  java
  • 一起学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动态加载,如下所示:

     1 <template>
     2   <div class="puzzle" :style="{width+'px',height:height+'px'}">
     3     <div
     4       v-for="(item,index) in blockPoints"
     5       :key="item.id"
     6       :style="{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),主要用于逻辑的校验和判断,如下所示:

      1 <script>
      2 export default {
      3   props: {
      4     img: {
      5       // 图片路径
      6       type: String,
      7       required: true,
      8     },
      9      {
     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),主要用于外观样式的设置,如下所示:

     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

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

     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

    组件的调用,如下所示:

    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="20px,height:20px" value="下一关">下一关</button> -->
    6   </div>
    7 </template>
    View Code

     注意事项:

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

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

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

    源码链接

    备注

    浪淘沙令·帘外雨潺潺

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

    帘外雨潺潺,春意阑珊。

    罗衾不耐五更寒。

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

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

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

  • 相关阅读:
    使用 asp.net mvc和 jQuery UI 控件包
    ServiceStack.Redis 使用教程
    HTC T8878刷机手册
    Entity Framework CodeFirst 文章汇集
    2011年Mono发展历程
    日志管理实用程序LogExpert
    使用 NuGet 管理项目库
    WCF 4.0路由服务Routing Service
    精进不休 .NET 4.0 (1) asp.net 4.0 新特性之web.config的改进, ViewStateMode, ClientIDMode, EnablePersistedSelection, 控件的其它一些改进
    精进不休 .NET 4.0 (7) ADO.NET Entity Framework 4.0 新特性
  • 原文地址:https://www.cnblogs.com/hsiang/p/13574867.html
Copyright © 2011-2022 走看看