通過學(xué)習(xí)Vue自定義組件,可以開發(fā)一些小功能,自娛自樂,鞏固學(xué)習(xí)的基礎(chǔ)知識(shí),本文以一個(gè)簡單的拼圖小游戲的例子,簡述Vue自定義組件的開發(fā),調(diào)用等基本流程,僅供學(xué)習(xí)分享使用,如有不足之處,還請(qǐng)指正。
關(guān)于Vue組件的基礎(chǔ)知識(shí),前篇已有介紹,本例涉及知識(shí)點(diǎn)如下:
拼圖游戲,只有相鄰的元素才可以交換位置,那如何判斷兩個(gè)元素相鄰,方法如下:
左右相鄰:y軸坐標(biāo)相同,x軸相減的絕對(duì)值等于一個(gè)元素的寬度。
上下相鄰:x軸坐標(biāo)相同,y軸相減的絕對(duì)值等于一個(gè)元素的高度。
如何判斷拼圖中的可以與之交換位置的空白,方法如下:
通過ref引用屬性,將空白屬性,定義為empty,其他定義為block,以便區(qū)分。
如何將一張圖放到每一個(gè)元素上,并只顯示一塊內(nèi)容,方法如下:
將背景圖的位置和元素的坐標(biāo)起始位置關(guān)聯(lián)起來,即將圖片的向左上方平移即可。
元素之間的切換平滑過渡。在本例中,通過css樣式設(shè)置,所有元素的移動(dòng)都在0.3s內(nèi)完成,達(dá)到平滑過渡的效果。
本例中拼圖游戲一共分5關(guān),分別是3*3,4*4等,難度逐級(jí)增加,所用圖片的均是500px*500px大小,如下圖所示:
當(dāng)拼圖完成時(shí),詢問是否進(jìn)行下一關(guān),如下所示:
下一關(guān),效果如下所示:
其他效果圖類似,只是分的行和列遞增,拼圖難度增加,但是處理邏輯都是相同的。
關(guān)于Puzzle.vue源碼,如下所示:
模板部分(template),主要是元素的布局,本例采用v-for動(dòng)態(tài)加載,如下所示:
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),主要用于邏輯的校驗(yàn)和判斷,如下所示:
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 // 行數(shù) 21 type: Number, 22 default: 3, 23 }, 24 col: { 25 // 列數(shù) 26 type: Number, 27 default: 3, 28 }, 29 }, 30 data() { 31 return { 32 status: { 33 type: String, 34 default: "進(jìn)行中......", 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 //通關(guān) 88 setTimeout(() => { 89 this.status = "勝利"; 90 alert("恭喜通關(guān)"); 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("現(xiàn)在進(jìn)行下一關(guān)么?");100 if (answerFlag) {101 this.status = "進(jìn)行中......";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; //數(shù)組的長度130 const lastEle = points[length - 1]; //最后一個(gè)元素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),主要用于外觀樣式的設(shè)置,如下所示:
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>
拼圖組件的調(diào)用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: "進(jìn)行中......",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("已經(jīng)是最后一關(guān)了,需要重新開始么?");27 if (answerFlag) {28 this.level = 0;29 }30 }31 },32 getStatus() {33 this.status = this.$refs.dpuzzle.status;34 },35 },36 };37 </script>
組件的調(diào)用,如下所示:
1 <template>2 <div>3 <h3>[拼圖游戲]當(dāng)前是第{{level+1}}關(guān),當(dāng)前狀態(tài)[{{status}}]</h3>4 <puzzle ref="dpuzzle" @getStatus="getStatus" @next="handleNext" v-bind="puzzleConfig[level]" />5 <!-- <button @click="handleNext" style="width:20px,height:20px" value="下一關(guān)">下一關(guān)</button> -->6 </div>7 </template>
注意事項(xiàng):
如果獲取組件內(nèi)部的元素的值,在組件調(diào)用時(shí)采用ref屬性,然后獲取組件內(nèi)的data屬性值。
組件內(nèi)如果調(diào)用父組件的方法,本文采用觸發(fā)注冊事件的方式this.$emit("next");
如果需要學(xué)習(xí)參考源碼的朋友,可以點(diǎn)擊源碼鏈接進(jìn)行下載。
浪淘沙令·簾外雨潺潺
作者:李煜【五代十國南唐后主】
簾外雨潺潺,春意闌珊。
羅衾不耐五更寒。
夢里不知身是客,一晌貪歡。
獨(dú)自莫憑欄,無限江山,別時(shí)容易見時(shí)難。
流水落花春去也,天上人間。
聯(lián)系客服