canvas setInterval 定时器 循环轮播 越执行越快,顺序混乱---实例解析

时间:2024-03-15 21:10:03

实际开发中,小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

canvas setInterval 定时器 循环轮播 越执行越快,顺序混乱---实例解析后台进行了打印,打印src...canvas setInterval 定时器 循环轮播 越执行越快,顺序混乱---实例解析完全不是想要的有序的循环,1.先来看下这个的方法对不对

canvas setInterval 定时器 循环轮播 越执行越快,顺序混乱---实例解析

三目,和后面的逻辑思路也都对,,,2那是不是定时器写多了呢?

canvas setInterval 定时器 循环轮播 越执行越快,顺序混乱---实例解析

只有一个定时器,而且定时器是用回调调用的,特别注意一下回调方法,(回调次数过多是常见犯错),感觉好像快找到大门了,3找到回调方法进行分析,

canvas setInterval 定时器 循环轮播 越执行越快,顺序混乱---实例解析

从分析上貌似也没有错误,但是隐隐约约感觉应该是这里的问题。,,,整理下思路,,如果一个定时器输出的src(数据)规律混乱,第一要么是排序的方法逻辑有问题,此处是三目运算,如果排序的逻辑没问题,那么就是定时器开多了,要考虑定时器的调用方法。,,,现在要怎么测试呢。。。。4.跟着断点走,,,,走个两三遍,看哪里有不该走的地方,或4者直接加打印,在认为可能产生问题的地方:

canvas setInterval 定时器 循环轮播 越执行越快,顺序混乱---实例解析

看下后台,一下就看到了问题,所在

canvas setInterval 定时器 循环轮播 越执行越快,顺序混乱---实例解析

就是这个地方那个的回调执行了很多遍。。。。

5在回来看下这个图片加载功能的实现。

canvas setInterval 定时器 循环轮播 越执行越快,顺序混乱---实例解析

addEventListener() 方法用于向指定元素添加事件句柄

语法:element.addEventListener(eventfunctionuseCapture);推荐一个我适合菜鸟的网站http://www.runoob.com/jsref/met-element-addeventlistener.html,不太懂得可以去看下,

用法上也是对的。但问题就处在这,,,6等下,他好像是把完成加载的图片实例对象 传了出去

canvas setInterval 定时器 循环轮播 越执行越快,顺序混乱---实例解析

这个参数给了imgObj,,,imgObj是回调方法传回来的图片已经加载完的对象。

canvas setInterval 定时器 循环轮播 越执行越快,顺序混乱---实例解析

重要的事情说三遍,,imgObj.其中的对象绑定load的监听事件。imgObj.其中的对象绑定load的监听事件。

imgObj.其中的对象绑定load的监听事件。

当构造函数生成实例对象时,imgObj.beach也就img被传入,赋值给this.img.

canvas setInterval 定时器 循环轮播 越执行越快,顺序混乱---实例解析

在刚开始就说过的海浪的实现是用改变src的地址是实现的,,。。。。。。

原因找到了,,当实现图片切换地址时img改变src...图片加载,因为之前brach.img绑定过load事件,当图片改变加载新src图片完成,触发load事件,调用回调函数,"又"启动一个定时器。。。。。

写这篇文章的目的是  记录在开发中当遇到问题怎找bug的思路,希望你我共勉!