一、元件化開發
1.1元件化概述
頁面特效的製作,特別需要HTML、CSS有固定的佈局,所以說現在越來越流行元件開發的模式,就是用JS寫一個類,當你例項化這個類的時候,頁面上的效果佈局也能自動完成。
new Carousel();
例項化後,頁面中就有一個輪播圖的佈局結構,而且可以通過引數傳遞進去。
這個new裡面封裝了HTML、CSS、JS的業務邏輯。元件開發的規律就是所有按鈕、小圓點、圖片等等都是這個類(的例項的)屬性,自己管理自己。
元件開發的好處就是在用的時候可以高度自定義,在new的時候應該能傳入一個JSON引數進行配置,當你的JSON裡面的屬性改變的時候,我們的UI介面邏輯就要有響應的變化。
物件導向+設計模式組合各種類(通常是中介者模式),就是元件化開發。
本質思想,元件只考慮兩個事情:
l 別人怎麼控制我,要提供很多函式,這個函式可以改變我的狀態。
l 我怎麼給別人提供介面,比如輪播圖被點選時,要提供一個click事件回撥。
元件只需要對自己負責,至於別人怎麼呼叫我的介面,在我提供的介面中做什麼,自己不需要考慮。
特點:
元件都是可以單獨測試的,所有元件都可以單獨上樹,進行測試。
DOM都是動態生成的,元件開發中,現在90%以上的,都是將DOM寫在JS中,在“檢視原始碼”中看見的是空標籤。
元件是巢狀的,往往大元件是小元件的中介者。
優點:
l 方便維護,功能易於插拔,很容易找出BUG的地方。
l 易於複用,比如我們做一個分頁條元件,此時可以非常自由在其他專案使用。
元件開發是一個非常實用的技術,元件開發越來越火,催生了一些元件開發的框架:React、Vue、Angular等。
1.2輪播圖元件開發
JS物件、DOM物件,JS物件的屬性是DOM物件
用輪播圖的物件導向舉例,JS物件中有自己的引數屬性(比如當前顯示圖片的編號、速度、間隔時間、寬度、高度),還DOM屬性。
<script type="text/javascript" src="js/jquery-1.12.3.min.js"></script> <script type="text/javascript" src="js/Carousel.js"></script> <script type="text/javascript"> new Carousel({ "id" : "Carousel", "images" : [ {"picUrl" : "images/0.jpg", "link" : "http://www.iqianduan.cn"}, {"picUrl" : "images/1.jpg", "link" : "http://www.iqianduan.cn"}, {"picUrl" : "images/2.jpg", "link" : "http://www.iqianduan.cn"}, {"picUrl" : "images/3.jpg", "link" : "http://www.iqianduan.cn"}, {"picUrl" : "images/4.jpg", "link" : "http://www.iqianduan.cn"} ], "width" : 560, "height": 300, "speed" : 500, "interval" : 2000 }); </script>
CSS樣式:carousel樣式後面動態建立
#Carousel{ width: 560px; height: 300px; position: relative; overflow: hidden; } .leftBtn,.rightBtn{ position: absolute; top:50%; width: 30px; height: 30px; background-color: orange; } .leftBtn{left: 10px;} .rightBtn{right: 10px;} .circls{ position: absolute; bottom: 10px;left: 100px; list-style: none; } .circls li{ float: left; width: 20px; height: 20px; background-color: orange; margin-right: 10px; } .circls li.cur{background-color: red;}
(function(){ //強行暴露一個變數,一枝紅杏出牆來 window.Carousel = Carousel; //輪播圖類 function Carousel(JSON){ this.$dom = $("#" + JSON.id); //DOM元素 this.$imagesUl = null; this.$imagesUlLis = null; this.width = JSON.width; this.height = JSON.height; this.$leftBtn = null; this.$rightBtn = null; this.$circleOl = null; this.$circleLis = null; this.interval = JSON.interval; this.speed = JSON.speed; //滑動速度 this.idx = 0;//訊號量 this.imagesURLArr = JSON.images;//圖片地址陣列 this.pictureLength = JSON.images.length;//圖片長度 this.init(); this.bindEvent(); this.autoPlay(); //定時器 } //初始化DOM Carousel.prototype.init = function(){ //建立ul節點 this.$imagesUl = $("<ul></ul>"); this.$dom.append(this.$imagesUl); //建立li節點 for(var i = 0; i < this.pictureLength; i++) { $("<li><img src='"+this.imagesURLArr[i].picurl+"'/></li>") .appendTo(this.$imagesUl); }; //獲得li元素引用 this.$imagesUlLis = this.$imagesUl.find("li"); //大盒子的佈局 this.$dom.css({ "width" : this.width, "height" : this.height, "position" : "relative", "overflow" : "hidden" }); //貓膩,讓所有li藏起來(left移動到顯示區域外) this.$imagesUlLis.css({ "position" : "absolute", "left": this.width, "top": 0 }); //只顯示第一張圖 this.$imagesUlLis.eq(0).css("left",0); //建立按鈕 this.$leftBtn = $("<a href='javascript:;' class='leftBtn'></a>"); this.$rightBtn = $("<a href='javascript:;' class='rightBtn'></a>"); this.$leftBtn.appendTo(this.$dom); this.$rightBtn.appendTo(this.$dom); //建立小圓點 this.$circleOl = $("<ol class='circls'></ol>"); this.$circleOl.appendTo(this.$dom); for (var i = 0; i < this.pictureLength; i++) { $("<li></li>").appendTo(this.$circleOl); }; //獲得ol的li元素 this.$circleLis = this.$circleOl.find("li"); //加cur this.$circleLis.eq(0).addClass("cur"); } })();
事件監聽方法:
Carousel.prototype.bindEvent = function(){ var self = this; //右邊按鈕的監聽 this.$rightBtn.click(function(){ if(self.$imagesUlLis.is(":animated")) return; self.showNext(); }); //左邊按鈕的監聽 this.$leftBtn.click(function(){ if(self.$imagesUlLis.is(":animated")) return; self.showPrev(); }); }
showNext() 顯示下一張方法
//展示下一張 Carousel.prototype.showNext = function(){ this.$imagesUlLis.eq(this.idx).animate({"left" : -this.width},this.speed); this.idx++; if(this.idx > this.pictureLength - 1){ this.idx = 0; } this.$imagesUlLis.eq(this.idx).css("left",this.width).animate({"left" : 0},this.speed); //圓點的cur this.changeCirclesCur(); }
changeCirclesCur()小圓點方法
Carousel.prototype.changeCirclesCur = function(){ this.$circleLis.eq(this.idx).addClass("cur").siblings().removeClass("cur"); }
showPrev() 顯示上一張方法
//展示上一張 Carousel.prototype.showPrev = function(){ this.$imagesUlLis.eq(this.idx).animate({"left" : this.width},this.speed); this.idx--; if(this.idx < 0){ this.idx = this.pictureLength - 1; } this.$imagesUlLis.eq(this.idx).css("left",-this.width).animate({"left" : 0},this.speed); //圓點的cur this.changeCirclesCur(); }
//自動輪播 Carousel.prototype.autoPlay = function(){ var self = this; this.timer = setInterval(function(){ self.showNext(); },this.interval); }
bindEvent()
Carousel.prototype.bindEvent = function(){ var self = this; //滑鼠停表 this.$dom.mouseenter(function(){ clearInterval(self.timer); }); //離開開啟 this.$dom.mouseleave(function(){ self.autoPlay(); }); //圓點的監聽 this.$circleLis.click(function(){ self.show($(this).index()); }); }
//小圓點點選展示任意 Carousel.prototype.show = function(number){ var old = this.idx; //舊idx訊號量 this.idx = number; //當前點選的訊號量,改變全域性 //判斷 if(this.idx > old){ //從右到左 this.$imagesUlLis.eq(old).animate({"left" : -this.width},this.speed); this.$imagesUlLis.eq(this.idx).css("left",this.width).animate({"left" : 0},this.speed); }else if(this.idx < old){//從左到右 this.$imagesUlLis.eq(old).animate({"left" : this.width},this.speed); this.$imagesUlLis.eq(this.idx).css("left",-this.width).animate({"left" : 0},this.speed); } //圓點的cur this.changeCirclesCur(); }
二、俄羅斯方塊遊戲開發
2.1先認識方塊
俄羅斯方塊一共有7種:S、Z、J、L、O、I、T
2.2寫二維陣列的JSON表(表示磚塊)
首先做兩種圖形:
// S 、Z 、J 、L 、O 、I 、T
var block_json = { "I":[ //I有2種方向 [ [0,1,0,0], [0,1,0,0], [0,1,0,0], [0,1,0,0] ], [ [0,0,0,0], [0,0,0,0], [1,1,1,1], [0,0,0,0] ] ], "L":[ //L有4種方向 [ [0,1,0,0], [0,1,0,0], [0,1,1,0], [0,0,0,0] ], [ [1,1,1,0], [1,0,0,0], [0,0,0,0], [0,0,0,0] ], [ [1,1,0,0], [0,1,0,0], [0,1,0,0], [0,0,0,0] ], [ [0,0,1,0], [1,1,1,0], [0,0,0,0], [0,0,0,0] ] ], "J":[//J有4種方向 [ [0,1,0,0], [0,1,0,0], [1,1,0,0], [0,0,0,0] ], [ [1,0,0,0], [1,1,1,0], [0,0,0,0], [0,0,0,0] ], [ [1,1,0,0], [1,0,0,0], [1,0,0,0], [0,0,0,0] ], [ [1,1,1,0], [0,0,1,0], [0,0,0,0], [0,0,0,0] ] ], "O":[ //O有1種方向 [ [1,1,0,0], [1,1,0,0], [0,0,0,0], [0,0,0,0] ] ], "Z":[ //Z有2種方向 [ [1,1,0,0], [0,1,1,0], [0,0,0,0], [0,0,0,0] ], [ [0,0,1,0], [0,1,1,0], [0,1,0,0], [0,0,0,0] ] ], "S":[ //S有2種方向 [ [0,1,1,0], [1,1,0,0], [0,0,0,0], [0,0,0,0] ], [ [0,1,0,0], [0,1,1,0], [0,0,1,0], [0,0,0,0] ] ], "T":[//T有4種方向 [ [1,1,1,0], [0,1,0,0], [0,0,0,0], [0,0,0,0] ], [ [0,1,0,0], [1,1,0,0], [0,1,0,0], [0,0,0,0] ], [ [0,1,0,0], [1,1,1,0], [0,0,0,0], [0,0,0,0] ], [ [0,1,0,0], [0,1,1,0], [0,1,0,0], [0,0,0,0] ] ] }
2.3基本佈局【table表格(12*20)】
都是套路,和貪吃蛇沒區別。
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8" /> <title>Document</title> <style type="text/css"> table{ margin:50px auto; } table,tr,td{border: 1px solid #000;border-collapse:collapse;} td{width: 18px;height: 18px;} </style> </head> <body> <div id="app"></div> </body> <script type="text/javascript" src="js/Game.js"></script> <script type="text/javascript" src="js/Block.js"></script> <script type="text/javascript"> var game = new Game() </script> </html>
迴圈建立12*20的table表格,原因:為了方塊能居中。
(function(){ window.Game = function(){ this.init() } //20 * 12建立表格 Game.prototype.init = function(){ this.dom = document.createElement('table'); document.getElementById("app").appendChild(this.dom); var tr,td; //迴圈插入行 for(var i = 0;i < 20;i++){ tr = document.createElement('tr'); this.dom.appendChild(tr); for(var j = 0;j < 12;j++){ //迴圈插入列 td = document.createElement('td'); tr.appendChild(td); } } } })();
//如果別的類修改Game類的表格顏色,儘量提供一個方法給其他類呼叫,不要讓其他類修改自己的屬性 //設定table表格的顏色 Game.prototype.setClass = function(row, col, classname){ this.dom.getElementsByTagName('tr')[row].getElementsByTagName('td')[col].className = classname }
在index.html中寫兩個類:
.L{background: skyblue;}
.I{background: pink;}
(function(){ window.Block = function(){ //在所有的形狀中,選擇一個磚塊形狀 var arr = ["I","L","J"]; this.allType = arr[~~(Math.random() * arr.length)] console.log(this.allType) //自己所有的方向個數 this.allDirectionNumber = block_json[this.allType].length; //隨意一個方向 this.direction = ~~(Math.random() * this.allDirectionNumber); //得到形狀,馬上渲染圖形的而進行code碼 this.code = block_json[this.allType][this.direction]; //4 * 4小方塊的初始位置 this.row = 0; this.col = 4; //保證方塊從中間出現 } })();
2.4磚塊渲染
(function(){ window.Block = function(){ ... } //渲染磚塊 Block.prototype.render = function(){ for(var i = 0; i < 4;i++){ for(var j = 0; j < 4;j++){ //顯示4 * 4矩陣顏色,寫class類 game.setClass(this.row + i, this.col + j, "gray"); if(this.code[i][j] == 1){ //如果4 * 4 二維陣列編碼中有1就渲染顏色,0就沒色 game.setClass(this.row + i, this.col + j, this.allType) } } } } })();
別忘記在Game類中新增定時器並render block(渲染方塊)
(function(){ window.Game = function(){ this.init(); this.start(); //例項化磚塊類 this.block = new Block(); } Game.prototype.start = function(){ var self = this; setInterval(function(){ //渲染磚塊 self.block.render(); },30); } })();
2.5磚塊下落
//磚塊下落 Block.prototype.down = function(){ this.row++; } //清屏方法 Game.prototype.clearClass = function(){ for(var i = 0; i < 20;i++){ for(var j = 0; j < 12;j++){ game.setClass(i, j, ""); } } } Game.prototype.start = function(){ this.f = 0; var self = this; setInterval(function(){ self.f++; //清屏 self.clearClass() //渲染磚塊 self.block.render(); //每間隔20幀下落 self.f % 20 == 0 && self.block.down(); },30); }
磚塊就能下落了
2.6 Map地圖類
Map地圖類儲存死亡的方塊
(function(){ window.Map = function(){ this.code = [ [0,0,0,0,0,0,0,0,0,0,0,0], [0,0,0,0,0,0,0,0,0,0,0,0], [0,0,0,0,0,0,0,0,0,0,0,0], [0,0,0,0,0,0,0,0,0,0,0,0], [0,0,0,0,0,0,0,0,0,0,0,0], [0,0,0,0,0,0,0,0,0,0,0,0], [0,0,0,0,0,0,0,0,0,0,0,0], [0,0,0,0,0,0,0,0,0,0,0,0], [0,0,0,0,0,0,0,0,0,0,0,0], [0,0,0,0,0,0,0,0,0,0,0,0], [0,0,0,0,0,0,0,0,0,0,0,0], [0,0,0,0,0,0,0,0,0,0,0,0], [0,0,0,0,0,0,0,0,0,0,0,0], [0,0,0,0,0,0,0,0,0,0,0,0], [0,0,0,0,0,0,0,0,0,0,0,0], [0,0,0,0,0,0,0,0,0,0,0,0], [0,0,0,0,0,0,0,0,0,0,0,0], [0,0,0,0,0,0,0,0,0,0,0,0], [0,0,0,0,0,0,0,0,0,0,0,0], [0,0,0,0,0,0,0,0,0,0,0,0] ] } })()
用for迴圈更優雅的建立二維陣列
this.code = (function(){ var arr = []; for(var i = 0;i < 20;i++){ arr.push([]); for(var j = 0;j < 12;j++){ arr[i].push(0) } } return arr; })();
ES6語法,寫一個二維陣列:
new Array(20).fill(new Array(12).fill(0)
(function(){ window.Map = function(){ this.code = (function(){ var arr = []; for(var i = 0;i < 20;i++){ arr.push([]); for(var j = 0;j < 12;j++){ arr[i].push(0) } } //寫一個“一柱擎天”方便測試 arr[10][5] = "L"; arr[11][5] = "L"; arr[12][5] = "L"; arr[13][5] = "L"; arr[14][5] = "L"; arr[15][5] = "L"; arr[16][5] = "L"; arr[17][5] = "L"; arr[18][5] = "L"; arr[19][5] = "L"; return arr; })(); console.log(this.code) } })()
Map地圖類渲染:
//地圖渲染 Map.prototype.render = function(){ for(var i = 0;i < 20; i++){ for(var j = 0;j < 12; j++){ //如果地圖中二維陣列中有非0的,就渲染方塊 if(this.code[i][j] != 0){ game.setClass(i, j, this.code[i][j]) } } } }
2.7下落的磚塊卡住判斷
看下一行能不能進取決於兩個條件:
1、地圖類的下一行不能是非“0”
2、方塊的下一行不能是“1”
//檢測碰撞,提供check方法,返回值true或false Block.prototype.check = function(row,col){ for(var i = 0; i < 4; i++){ for(var j = 0; j < 4; j++){ if(this.code[i][j] != 0 && game.map.code[row + i][col + j] != 0){ return false; //如果不能進,返回false } } } return true; //能進返回true }
//磚塊下落 Block.prototype.down = function(){ //呼叫check方法,如果為真就繼續row++往下落 console.log(this.check(this.row+1,this.col)) if(this.check(this.row+1,this.col)){ this.row++; } }
到這來碰撞檢測完成。
當刪除測試的“一柱擎天”後,block下落到底的時候,報錯了,因為下標越界了,map類最下面沒有非0的code碼,檢測block.check方法中迴圈不到第i第j列沒有非0的值了。
解決辦法:手動在最後補一行非0的陣列值即可。
arr.push([1,1,1,1,1,1,1,1,1,1,1,1]);
arr.push(Array(12).fill(1));
2.8新增鍵盤事件監聽
//鍵盤事件監聽 Game.prototype.BindEvent = function(){ var self = this; document.onkeyup = function(event){ if(event.keyCode == 37){ self.block.left(); }else if(event.keyCode == 38){ self.block.rotate(); }else if(event.keyCode == 39){ self.block.right(); }else if(event.keyCode == 40){ self.block.goDown(); } } } //向左 Block.prototype.left = function(){ if(this.check(this.row,this.col-1)){ this.col--; } } // 向右 Block.prototype.right = function(){ if(this.check(this.row,this.col+1)){ this.col++; } } //一鍵到底 Block.prototype.goDown = function(){ while(this.check(this.row+1,this.col)){ this.row++; } }
可以左右、向下控制磚塊了。
只要碰到map中的死屍磚塊就新增。
//新增死亡方塊 Block.prototype.addDie = function(){ for(var i = 0; i < 4;i++){ for(var j = 0; j < 4;j++){ // console.log(this.row+i,this.col+j) if(this.code[i][j] != 0){ //如果不是0表示有顏色(有磚塊) //將隨機出來的字母類名,寫在地圖類的code中 game.map.code[this.row + i][this.col + j] = this.allType; } } } }
//磚塊下落 Block.prototype.down = function(){ //呼叫check方法,如果為真就繼續row++往下落 if(this.check(this.row+1,this.col)){ this.row++; }else{ //如果為假,表示碰到非0的磚塊了,將自己新增到map地圖類中 this.addDie(); //同時new一個新的磚塊出來 game.block = new Block(); } }
2.10旋轉
//旋轉 Block.prototype.rotate = function(){ //備份舊方向 var oldDirection = this.direction; //如果旋轉的值已經等於自己方向的個數,就回到0,重新翻轉 if(this.direction == this.allDirectionNumber - 1){ this.direction = 0; }else{ // 否則繼續加,可以旋轉 this.direction++; } //得到自己的方向下標後,馬上渲染圖形的二維陣列的code碼 this.code = block_json[this.allType][this.direction]; if(!this.check(this.row,this.col)){ //已經碰到了 //如果不可以旋轉,就撤回來 this.direction = oldDirection //改為剛剛隨機出來的舊方向。 this.code = block_json[this.allType][this.direction]; } }
2.11消行判斷
//消行判斷 Block.prototype.remove = function(){ //判斷map地圖類中的code中某一行是不是沒有0,如果沒有0,就消行 for(var i = 0;i < 20;i++){ if(!game.map.code[i].includes(0)){ //如果沒有0,就刪除行 game.map.code.splice(i,1); //刪除行之後,再重新在頭部填充一行全0的 game.map.code.unshift(new Array(12).fill(0)); } } }
//磚塊下落 Block.prototype.down = function(){ //呼叫check方法,如果為真就繼續row++往下落 if(this.check(this.row+1,this.col)){ this.row++; }else{ //如果為假,表示碰到非0的磚塊了,將自己新增到map地圖類中 this.addDie(); //同時new一個新的磚塊出來 game.block = new Block(); //沒碰到一次檢測是否需要消行 this.remove(); } }
2.12遊戲結束判斷
//磚塊下落 Block.prototype.down = function(){ //判斷陣列的第0行,有沒有不等於0的項,如果有,遊戲結束 game.map.code[0].forEach(function(item){ if(item != 0){ clearInterval(game.timer); alert`遊戲結束`; } }); //呼叫check方法,如果為真就繼續row++往下落 if(this.check(this.row+1,this.col)){ this.row++; }else{ //如果為假,表示碰到非0的磚塊了,將自己新增到map地圖類中 this.addDie(); //同時new一個新的磚塊出來 game.block = new Block(); //沒碰到一次檢測是否需要消行 this.remove(); } }
2.13新增音效和美化
<html> <head> <style type="text/css"> *{margin: 0;padding: 0;} body{ height: 100%; background: url(img/bg.jpg) no-repeat 0 0; background-size:cover; } table{ margin:50px auto; background: rgba(0, 0, 0,0.5); } table,tr,td{ border: 1px solid #000; border-collapse:collapse; } td{ width: 20px; height: 20px; } .L{ background: linear-gradient(to right bottom, #800080, #ffc0cb);} .I{ background: linear-gradient(to right bottom, #00f260, #0575e6);} .S{ background: linear-gradient(to right bottom, #fc4a1a, #f7b733);} .Z{ background: linear-gradient(to right bottom, #22c1c3, #fdbb2d);} .J{ background: linear-gradient(to right bottom, #ff9966, #ff5e62);} .O{ background: linear-gradient(to right bottom, #7f00ff, #e100ff);} .T{ background: linear-gradient(to right bottom, #c0392b, #8e44ad);} .B{ background:yellow; } .A{ background:yellowgreen; } h1,h2{color:#fff;text-align:center;} </style> </head> <body> <audio src="audio/bg.wav" autoplay id="bg"></audio> <audio src="audio/一鍵到底.wav" id="goDown"></audio> <audio src="audio/旋轉.wav" id="rotate"></audio> <audio src="audio/移動.wav" id="move"></audio> <audio src="audio/消行.wav" id="goDie"></audio> <h1 id="score"></h1> <h2 id="info"></h2> <div id="app"></div> </body> </html> <script type="text/javascript" src="js/Block_json.js"></script> <script type="text/javascript" src="js/Game.js"></script> <script type="text/javascript" src="js/Block.js"></script> <script type="text/javascript" src="js/Map.js"></script> <script type="text/javascript"> document.getElementById("bg").volume = 0.2; var game = new Game(); </script>
//主迴圈,遊戲定時器 Game.prototype.start = function(){ var self = this; this.f = 0; this.score = 0; this.timer = setInterval(function(){ self.f++; document.getElementById("info").innerHTML = "幀編號:"+ self.f; document.getElementById("score").innerHTML = "分數:"+ self.score; //先清屏,再渲染 self.clearClass(); //渲染小方塊 self.block.render(); //每隔20幀,方塊下落 self.f % 30 == 0 && self.block.down(); //地圖方塊渲染 self.map.render(); },20); }
音效:
//旋轉 Block.prototype.rotate = function(){ document.getElementById("rotate").play(); } //一鍵下落 Block.prototype.goDown = function(){ document.getElementById("goDown").play(); } //向左 Block.prototype.left = function(){ document.getElementById("move").play(); } //向右 Block.prototype.right = function(){ document.getElementById("move").play(); }
//消行判斷、加分 Block.prototype.remove = function(){ //判斷map類中的code中某一行是不是沒有0,如果沒有0,就消行 for (var i = 0; i < 20; i++) { if(!game.map.code[i].includes(0)){ game.score++; document.getElementById("goDie").play(); } }; }