一塊兒學Vue自定義組件之拼圖小遊戲

經過學習Vue自定義組件,能夠開發一些小功能,自娛自樂,鞏固學習的基礎知識,本文以一個簡單的拼圖小遊戲的例子,簡述Vue自定義組件的開發,調用等基本流程,僅供學習分享使用,若有不足之處,還請指正。css

涉及知識點

關於Vue組件的基礎知識,前篇已有介紹,本例涉及知識點以下:vue

  • 拼圖遊戲,只有相鄰的元素才能夠交換位置,那如何判斷兩個元素相鄰,方法以下:
    • 左右相鄰:y軸座標相同,x軸相減的絕對值等於一個元素的寬度。
    • 上下相鄰:x軸座標相同,y軸相減的絕對值等於一個元素的高度。
  • 如何判斷拼圖中的能夠與之交換位置的空白,方法以下:
    • 經過ref引用屬性,將空白屬性,定義爲empty,其餘定義爲block,以便區分。
  • 如何將一張圖放到每個元素上,並只顯示一塊內容,方法以下:
    • 將背景圖的位置和元素的座標起始位置關聯起來,即將圖片的向左上方平移便可。
  • 元素之間的切換平滑過渡。在本例中,經過css樣式設置,全部元素的移動都在0.3s內完成,達到平滑過渡的效果。

示例效果圖

本例中拼圖遊戲一共分5關,分別是3*3,4*4等,難度逐級增長,所用圖片的均是500px*500px大小,以下圖所示:數組

當拼圖完成時,詢問是否進行下一關,以下所示:dom

下一關,效果以下所示:ide

其餘效果圖相似,只是分的行和列遞增,拼圖難度增長,可是處理邏輯都是相同的。佈局

核心源碼

關於Puzzle.vue源碼,以下所示:學習

模板部分(template),主要是元素的佈局,本例採用v-for動態加載,以下所示:ui

 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),主要用於邏輯的校驗和判斷,以下所示:this

  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),主要用於外觀樣式的設置,以下所示:url

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

 注意事項:

若是獲取組件內部的元素的值,在組件調用時採用ref屬性,而後獲取組件內的data屬性值。

組件內若是調用父組件的方法,本文采用觸發註冊事件的方式this.$emit("next");

若是須要學習參考源碼的朋友,能夠點擊源碼連接進行下載。

源碼連接

備註

浪淘沙令·簾外雨潺潺

 

做者:李煜【五代十國南唐後主】

簾外雨潺潺,春意闌珊。

羅衾不耐五更寒。

夢裏不知身是客,一晌貪歡。

獨自莫憑欄,無限江山,別時容易見時難。

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

相關文章
相關標籤/搜索