經過學習Vue自定義組件,能夠開發一些小功能,自娛自樂,鞏固學習的基礎知識,本文以一個簡單的拼圖小遊戲的例子,簡述Vue自定義組件的開發,調用等基本流程,僅供學習分享使用,若有不足之處,還請指正。css
關於Vue組件的基礎知識,前篇已有介紹,本例涉及知識點以下:vue
本例中拼圖遊戲一共分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>
腳本部分(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>
樣式部分(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>
拼圖組件的調用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>
組件的調用,以下所示:
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>
注意事項:
若是獲取組件內部的元素的值,在組件調用時採用ref屬性,而後獲取組件內的data屬性值。
組件內若是調用父組件的方法,本文采用觸發註冊事件的方式this.$emit("next");
若是須要學習參考源碼的朋友,能夠點擊源碼連接進行下載。
浪淘沙令·簾外雨潺潺
做者:李煜【五代十國南唐後主】
簾外雨潺潺,春意闌珊。
羅衾不耐五更寒。
夢裏不知身是客,一晌貪歡。
獨自莫憑欄,無限江山,別時容易見時難。
流水落花春去也,天上人間。