前端笔记之JavaScript面向对象(四)组件化开发&轮播图|俄罗斯方块实战
一、组件化开发
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(); } }; }