实际开发中,小X在写一个canvas原生小游戏时,遇到一个问题,要实现一个海水流动的效果,一个主背景下有四张图片进行循环轮播(轮播其实就是去更换小图的src,当更换的频率小于人眼的视觉暂留时间时,看上去就是连续的动画),但是出现一个问题,水流的效果越执行越混乱,甚至闪烁,后台打印图片渲染的src发现是乱的。先来看下她的代码:
可以直接从gitHub上下载https://github.com/rainningLovexiang/learngit.git
<!------------------------源码----------------------------------->
html
<!DOCTYPE html>
<html lang="en">
<head>
<meta http-equiv="Content-Type" content="text/html;charset=utf-8" />
<meta name="page-view-size" content="1280*720"/>
<title>海</title>
<style>
body {
padding: 0;
margin: 0;
}
#myCanvas {
/*overflow: hidden;*/
}
img {
padding: 0;
margin: 0;
}
</style>
</head>
<body>
<canvas id="myCanvas"></canvas>
<script>
</script>
<!--引入js文件-->
<script src="./js/Basics.js"></script>
<script src="./js/game.js"></script>
</body>
</html>
Basics.js
var myCanvas = document.getElementById("myCanvas");
var ctx = myCanvas.getContext("2d");
myCanvas.width = 1280;
myCanvas.height = 720;
/**
* Object {Basics}图片监听事件
* param {loadImage:function}图片监听和回调
* param {ctx:context}浅层复制
*/
var Basics = {
extend: function (obj1,obj2) {
for(var key in obj2){
if(obj2.hasOwnProperty(key)){
obj1[key] = obj2[key];
}
}
},
/**
* param {imgObj:object}按照key、val的形式存储所有要加载的图像地址
* param{callback:Function}当所有的图片加载完毕之后,就会被调用
**/
loadImage: function (imgObj,callback) {
var transmitImg = {};
var loaded = 0;
var imgLength = 0;
var templateImg;
for(var key in imgObj){
imgLength++;
templateImg = new Image();
templateImg.addEventListener("load",function () {
loaded++;
if(loaded >= imgLength){
if(callback){
callback(transmitImg);
}
}
});
templateImg.src = imgObj[key];
transmitImg[key] = templateImg;
}
}
};
/*
* 绘制背景--底层海景
* constructor { Sea } 背景构造函数
* param { ctx: Context } 绘制环境
* param { img: Image } 背景图像
* param { speed: number } 背景速度
* */
function Sea(ctx,img,speed) {
this.ctx = ctx;
this.img = img;
this.speed = speed;
this.width = 1280;
this.height = myCanvas.height;
this.x = 0;
this.y = 0;
}
Sea.prototype = {
constructor: Sea,
draw: function () {
this.ctx.drawImage(this.img,this.x,this.y,this.width,this.height);
}
};
/*
* 绘制背景---海浪
* constructor { Beach } 背景构造函数
* param { ctx: Context } 绘制环境
* param { img: Image } 背景图像
* param { speed: number } 背景速度
* */
function Beach(ctx,img,speed) {
this.ctx = ctx;
this.img = img;
this.speed = speed;
this.width = myCanvas.width;
this.height = this.img.height;
this.x = 0;
this.y = 117;
this.currentFrame = 0;
this.imgFrame = 4;
}
Beach.prototype = {
constructor: Beach,
draw: function () {
this.ctx.drawImage(this.img,0,117,1280,48);
},
update: function () {
// 绘制下一帧
this.currentFrame = ++this.currentFrame >= this.imgFrame? 0 : this.currentFrame;
var a = this.getSrc(this.currentFrame);
console.log(a);
this.img.src= a;
},
getSrc: function (n) {
var arr = this.img.src.split(".")[0];
var str = arr.slice(0,arr.length-1);
return str+n+".png"
}
};
game.js
/*
* 主要用来实现逻辑
*
* */
//实现底层的海底
Basics.loadImage({
"sea":"./img/BG_game.jpg",
// "beach":"./img/beach/beach.png"
"beach":"./img/beach/beach0.png"
},function (imgObj) {
var beach= new Beach(ctx,imgObj.beach);
var sea = new Sea(ctx,imgObj.sea);
sea.draw();
beach.draw();
var timer = setInterval(function () {
beach.update();
beach.draw();
},500);
});
function setTime() {
clearInterval(timer);
}
<!------------------------------------代码部分结束--------------------------->
解析:
update方法是用来更新图片地址的,定时器每执行一次就执行一次更换src
在后台进行了打印,打印src...完全不是想要的有序的循环,1.先来看下这个的方法对不对
三目,和后面的逻辑思路也都对,,,2那是不是定时器写多了呢?
只有一个定时器,而且定时器是用回调调用的,特别注意一下回调方法,(回调次数过多是常见犯错),感觉好像快找到大门了,3找到回调方法进行分析,
从分析上貌似也没有错误,但是隐隐约约感觉应该是这里的问题。,,,整理下思路,,如果一个定时器输出的src(数据)规律混乱,第一要么是排序的方法逻辑有问题,此处是三目运算,如果排序的逻辑没问题,那么就是定时器开多了,要考虑定时器的调用方法。,,,现在要怎么测试呢。。。。4.跟着断点走,,,,走个两三遍,看哪里有不该走的地方,或4者直接加打印,在认为可能产生问题的地方:
看下后台,一下就看到了问题,所在
就是这个地方那个的回调执行了很多遍。。。。
5在回来看下这个图片加载功能的实现。
addEventListener() 方法用于向指定元素添加事件句柄
语法:element.addEventListener(event, function, useCapture);推荐一个我适合菜鸟的网站http://www.runoob.com/jsref/met-element-addeventlistener.html,不太懂得可以去看下,
用法上也是对的。但问题就处在这,,,6等下,他好像是把完成加载的图片实例对象 传了出去
这个参数给了imgObj,,,imgObj是回调方法传回来的图片已经加载完的对象。
重要的事情说三遍,,imgObj.其中的对象绑定load的监听事件。imgObj.其中的对象绑定load的监听事件。
imgObj.其中的对象绑定load的监听事件。
当构造函数生成实例对象时,imgObj.beach也就img被传入,赋值给this.img.
在刚开始就说过的海浪的实现是用改变src的地址是实现的,,。。。。。。
原因找到了,,当实现图片切换地址时img改变src...图片加载,因为之前brach.img绑定过load事件,当图片改变加载新src图片完成,触发load事件,调用回调函数,"又"启动一个定时器。。。。。
写这篇文章的目的是 记录在开发中当遇到问题怎找bug的思路,希望你我共勉!