chrome断网小游戏实现

代码部分都加了详细注释,我就不再详细多说了,主要就是几个主要的函数
Obstacle障碍物
NightMode昼夜更替
Cloud云朵
HorizonLine地面
Runner运动的配置
GameOverPanel游戏结束界面
DistanceMeter分数以及高分记录
CollisionBox碰撞检测
Trex恐龙

<script type="text/javascript">




    var div = document.querySelector('div');
    var spriteDefinition = {
        // 地面在精灵图中的位置
        HORIZON: { x: 2, y: 54 },
        // 云朵在精灵图中的位置
        CLOUD: { x: 86, y: 2 },
        STAR: { x: 645, y: 2 },//星星
        MOON: { x: 484, y: 2 },//月亮
        CACTUS_LARGE: { x: 332, y: 2 },	//大仙人掌
        CACTUS_SMALL: { x: 228, y: 2 },//小仙人掌
        PTERODACTYL: { x: 134, y: 2 }//翼龙
    },
        gameFrame = 600;
    FPS = 60,
        DEFAULT_WIDTH = 600,
        imgSprite = document.getElementById('sprite');

    // 障碍物,仙人掌和翼龙
    Obstacle.obstacles = [];//存储障碍物的数组
    Obstacle.obstacleHistory = [];//记录障碍物数组中障碍物的类型
    // 障碍物不能太密集也不能太稀疏,两个障碍物之间也应该有一个间距以供弹跳后的落脚
    // 因此设置一个障碍物最大间距系数
    Obstacle.MAX_GAP_COEFFICIENT = 1.5;
    // 每组障碍物的最大数量
    Obstacle.MAX_OBSTACLE_LENGTH = 3;
    // 相邻的障碍物类型的最大重复数
    Obstacle.MAX_OBSTACLE_DUPLICATION = 2;
    Obstacle.types = [
        {
            type: 'CACTUS_SMALL',//小仙人掌
            width: 17,//宽
            height: 35,//高
            yPos: 105,//在画布上的y坐标
            multipleSpeed: 4,
            minGap: 120,    //最小间距
            minSpeed: 0    //最低速度
        },
        {
            type: 'CACTUS_LARGE',//大仙人掌
            width: 25,
            height: 50,
            yPos: 90,
            multipleSpeed: 7,
            minGap: 120,
            minSpeed: 0
        },
        {
            type: 'PTERODACTYL',//翼龙
            width: 46,
            height: 40,
            yPos: [100, 75, 50],//有高,中,低三个高度
            multipleSpeed: 999,
            minSpeed: 8.5,
            minGap: 150,
            numFrames: 2,//有两个动画帧
            frameRate: 1000 / 6,//动画帧切换速率,这里为一秒6帧
            speedOffset: 0.8 //速度修正
        }
    ];
    // 绘制障碍物构造函数
    Obstacle.dimensions={
        WIDTH:600
    };
    // dimensions 屏幕尺寸  gapCoefficient 障碍物间隙  opt_xOffset 障碍物水平偏移量
    function Obstacle(canvas,type, spriteImgPos, gapCoefficient, speed, opt_xOffset) {
        // console.log(opt_xOffset);
        this.canvas = canvas;
        this.ctx = canvas.getContext('2d');
        this.spritePos = spriteImgPos;
        // 障碍物类型
        this.typeConfig = type;
        this.gapCoefficient = gapCoefficient;
        // 每个障碍物数量(1-3)
        this.size = getRandomNum(1, Obstacle.MAX_OBSTACLE_LENGTH);
        this.dimensions =Obstacle.dimensions;
        // 表示该障碍物是否可以被移除
        this.remove = false;
        // 水平坐标
        this.xPos = this.dimensions.WIDTH + (opt_xOffset || 0);
        this.yPos = 0;
        this.width = 0;
        this.gap = 0;
        this.speedOffset = 0;//速度修正
        // 障碍物的动画帧
        this.currentFrame = 0;
        // 动画帧切换的计时器
        this.timer = 0;
        this.init(speed);
    }
    Obstacle.prototype = {
        init: function (speed) {
            // console.log(this.typeConfig);
            // 如果随机障碍物是翼龙,则只出现一只
            // 翼龙的multipleSpeed是999,远大于speed
            if (this.size > 1 && this.typeConfig.multipleSpeed > speed) {
                this.size = 1;
            }
            //障碍物的总宽度等于单个障碍物的宽度乘以个数
            this.width = this.typeConfig.width * this.size;
            // 若障碍物的纵坐标是一个数组
            // 则随机选取一个
            if (Array.isArray(this.typeConfig.yPos)) {
                var yPosConfig = this.typeConfig.yPos;
                this.yPos = yPosConfig[getRandomNum(0, yPosConfig.length - 1)];
            } else {
                this.yPos = this.typeConfig.yPos;
            }
            this.draw();
            // 对翼龙的速度进行修正,让它看起来有的飞得快一些,有的飞的慢一些
            if (this.typeConfig.speedOffset) {
                this.speedOffset = Math.random() > 0.5 ? this.typeConfig.speedOffset : -this.typeConfig.speedOffset;

            }
            // 障碍物之间的间隙,与游戏速度有关
            this.gap = this.getGap(this.gapCoefficient, speed);
        },
        // 障碍物之间的间隔,gapCoefficient为间隔系数
        getGap: function (gapCoefficient, speed) {
            var minGap = Math.round(this.width * speed + this.typeConfig.minGap * gapCoefficient);
            var maxGap = Math.round(minGap * Obstacle.MAX_GAP_COEFFICIENT);
            return getRandomNum(minGap, maxGap);
        },
        // 判断障碍物是否移出屏幕外
        isVisible: function () {
            return this.xPos + this.width > 0;
        },
        draw: function () {
            // 障碍物宽高
            var sourceWidth = this.typeConfig.width;
            var sourceHeight = this.typeConfig.height;
            //根据障碍物数量计算障碍物在精灵图导航的x坐标
            // this.size的取值范围为1-3
            var sourceX = (sourceWidth * this.size) * (0.5 * (this.size - 1)) + this.spritePos.x;
            // console.log(sourceX);
            // 如果当前动画帧大于0,说明障碍物类型是翼龙
            // 更新翼龙的精灵图x坐标使其匹配第二帧动画
            if (this.currentFrame > 0) {
                sourceX += sourceWidth * this.currentFrame;
            };
            // console.log(this.xPos, this.yPos);
            this.ctx.drawImage(imgSprite,
                sourceX, this.spritePos.y,
                sourceWidth * this.size, sourceHeight,
                this.xPos, this.yPos,
                sourceWidth * this.size, sourceHeight);
        },
        // 单个障碍物的移动
        update: function (deltaTime, speed) {
            // 如果障碍物还没有移出屏幕外
            if (!this.remove) {
                // 如果有速度修正则修正速度
                if (this.typeConfig.speedOffset) {
                    speed += this.speedOffset;
                }
                // 更新x坐标
                this.xPos =this.xPos- Math.floor((speed * FPS / 1000) * deltaTime);
                // console.log(this.xPos);
                // Update frame
                if (this.typeConfig.numFrames) {
                    this.timer += deltaTime;
                    if (this.timer >= this.typeConfig.frameRate) {
                        // 在两个动画帧之间来回切换以达到动画效果
                        this.currentFrame = this.currentFrame == this.typeConfig.numFrames - 1 ? 0 : this.currentFrame + 1;
                        this.timer = 0;
                    }
                }
                this.draw();
                if (this.isVisible()) {
                    this.remove = true;
                }
            }
        },
        // 管理多个障碍物移动
        updateObstacles: function (deltaTime, currentSpeed) {
            // 障碍物列表,保存一个障碍物列表的副本
            var updateObstacles = Obstacle.obstacles.slice(0);
            for (var i = 0; i < Obstacle.obstacles.length; i++) {
                var obstacle = Obstacle.obstacles[i];
                obstacle.update(deltaTime, currentSpeed);
                // 移除被标记为删除的障碍物
                if (obstacle.remove) {
                    updateObstacles.shift();
                }
            }
            Obstacle.obstacles = updateObstacles;
            if (Obstacle.obstacles.length > 0) {
                // 获取障碍物列表中的最后一个障碍物
                var lastObstacle = Obstacle.obstacles[Obstacle.obstacles.length - 1];

                // 若满足条件则添加障碍物
                if (lastObstacle &&
                    lastObstacle.isVisible() &&
                    (lastObstacle.xPos + lastObstacle.width + lastObstacle.gap) <
                    this.dimensions.WIDTH) {
                    this.addNewObstacle(currentSpeed);
                }
            } else {
                // 若障碍物列表中没有障碍物则立即添加
                this.addNewObstacle(currentSpeed);
            }
        },
        // 随机添加障碍
        addNewObstacle: function (currentSpeed) {
            // 随机选取一种类型的障碍
            var obstacleTypeIndex = getRandomNum(0, Obstacle.types.length - 1);
            var obstacleType = Obstacle.types[obstacleTypeIndex];


            // 检查随机取到的障碍物类型是否与前两个重复
            // 或者检查其速度是否合法,这样可以保证游戏在低速时不出现翼龙
            // 如果检查不通过则重新再选一次直到通过为止
            if (this.duplicateObstracleCheck(obstacleType.type) || currentSpeed < obstacleType.minSpeed) {
                this.addNewObstacle(currentSpeed);
            } else {
                // 检查通过后,获取该值在精灵图中的坐标
                var obstacleSpritePos = this.spritePos[obstacleType.type];
                // 生成新的障碍物并存入数组
                Obstacle.obstacles.push(new Obstacle(this.canvas, obstacleType, obstacleSpritePos, this.dimensions, this.gapCoefficient, currentSpeed, obstacleType.width));
                // 同时将障碍物类型存入histo数组
                // 该方法向数组开头添加一个或者更多元素,并返回长度
                Obstacle.obstacleHistory.unshift(obstacleType.type);
            }
            // 若history数组的长度大于1,则清空最前面的两个
            if (Obstacle.obstacleHistory.length > 1) {
                Obstacle.obstacleHistory.splice(Obstacle.MAX_OBSTACLE_DUPLICATION);
            }
        },
        // 检查障碍物是否超过允许的最大重复数
        duplicateObstracleCheck: function (nextObstacleType) {
            var duplicateCount = 0;
            // 与history数组中的障碍物类型比较,最大只允许重得两次
            for (var i = 0; i < Obstacle.obstacleHistory.length; i++) {
                duplicateCount = Obstacle.obstacleHistory[i] === nextObstacleType ? duplicateCount + 1 : 0;
            }
            return duplicateCount >= Obstacle.MAX_OBSTACLE_DUPLICATION;
        }
    };



    // 昼夜更替效果
    NightMode.config = {
        FADE_SPEED: 0.035,//淡入淡出的速度
        HEIGHT: 40,//月亮的高度
        MOON_SPEED: 0.25,//月亮的速度
        NUM_STARS: 2,//星星的数量
        STAR_SIZE: 9,//星星的宽度
        STAR_SPEED: 0.3,//星星的速度
        STAR_MAX_Y: 70,//星星在画布上出现的位置
        WIDTH: 20//半个月度宽度
    };
    // 月亮在不同时期有不同的位置
    NightMode.phases = [140, 120, 100, 60, 40, 20, 0];
    // 时间记录
    NightMode.invertTimer = 0;
    // 是否可以进行昼夜交替
    NightMode.inverted = false;
    // 用于控制样式切换
    NightMode.invertTrigger = false;
    // 黑夜持续的时间
    NightMode.INVER_FADE_DURATION = 5000;
    // 黑夜构造函数
    function NightMode(canvas, spritePos, containerWidth) {
        this.canvas = canvas;
        this.ctx = canvas.getContext('2d');
        this.spritePos = spritePos;
        this.containerWidth = containerWidth;
        this.xPos = containerWidth - 50;//月亮的x坐标
        this.yPos = 30;//月亮的y坐标
        this.currentPhase = 0;
        this.opacity = 0;
        this.stars = [];//用于储存星星
        this.drawStars = false;//是否绘制星星
        this.placeStars();//放置星星
    };
    NightMode.prototype = {
        update: function (activated) {
            // 若夜晚模式处于激活状态并且opacity=0时
            // 对月亮周期进行更新
            if (activated && this.opacity == 0) {
                this.currentPhase++;
                if (this.currentPhase >= NightMode.phases.length) {
                    this.currentPhase = 0;
                }
            }
            // 淡入
            if (activated && (this.opacity < 1 || this.opacity == 0)) {
                this.opacity += NightMode.config.FADE_SPEED;
            } else if (this.opacity > 0) {
                this.opacity -= NightMode.config.FADE_SPEED;
            }
            // 当opacity大于0时移动月亮的位置
            if (this.opacity > 0) {
                this.xPos = this.updateXPos(this.xPos, NightMode.config.MOON_SPEED);

                // 移动星星
                if (this.drawStars) {
                    for (var i = 0; i < NightMode.config.NUM_STARS; i++) {
                        this.stars[i].x = this.updateXPos(this.stars[i].x, NightMode.config.STAR_SPEED);
                    };
                }
                this.draw();
            } else {
                this.opacity = 0;
                this.placeStars();
            }
            this.drawStars = true;
        },
        updateXPos: function (currentPos, speed) {
            if (currentPos < -NightMode.config.WIDTH) {
                currentPos = this.containerWidth;
            } else {
                currentPos -= speed;
            }
            return currentPos;
        },
        draw: function () {
            // 周期为3时画满月
            var moonSourceWidth = this.currentPhase == 3 ? NightMode.config.WIDTH * 2 : NightMode.config.WIDTH;
            var moonSourceHeight = NightMode.config.HEIGHT;
            // 从精灵图上获取月亮正确的形状
            var moonSourceX = this.spritePos.x + NightMode.phases[this.currentPhase];
            // console.log(moonSourceX);
            var moonOutputWidth = moonSourceWidth;
            var starSize = NightMode.config.STAR_SIZE;
            var starSourceX = spriteDefinition.STAR.x;
            this.ctx.save();
            // 画布透明度随之变化
            this.ctx.globalAlpha = this.opacity;
            if (this.drawStars) {
                for (var i = 0; i < NightMode.config.NUM_STARS; i++) {
                    this.ctx.drawImage(imgSprite,
                        starSourceX, this.stars[i].sourceY,
                        starSize, starSize,
                        Math.round(this.stars[i].x), this.stars[i].y,
                        NightMode.config.STAR_SIZE, NightMode.config.STAR_SIZE);
                }
            }
            this.ctx.drawImage(imgSprite,
                moonSourceX, this.spritePos.y,
                moonSourceWidth, moonSourceHeight,
                Math.round(this.xPos), this.yPos,
                moonOutputWidth, NightMode.config.HEIGHT);
            this.ctx.globalAlpha = 1;
            this.ctx.restore();
        },
        placeStars: function () {
            // 将画布分为若干组
            var segmentSize = Math.round(this.containerWidth / NightMode.config.NUM_STARS);
            for (var i = 0; i < NightMode.config.NUM_STARS; i++) {
                this.stars[i] = {};
                // 每组星星位置随机
                this.stars[i].x = getRandomNum(segmentSize * i, segmentSize * (i + 1));
                this.stars[i].y = getRandomNum(0, NightMode.config.STAR_MAX_Y);
                this.stars[i].sourceY = spriteDefinition.STAR.y + NightMode.config.STAR_SIZE * i;
            }
        },
        invert: function (deltaTime) {
            this.update(NightMode.inverted);
            // 黑夜持续时间为5秒
            if (NightMode.invertTimer > NightMode.INVER_FADE_DURATION) {
                NightMode.invertTimer = 0;
                NightMode.invertTrigger = false;
                // 切换类
                NightMode.inverted = div.classList.toggle('inverted', NightMode.invertTrigger)

            } else if (NightMode.invertTimer) {
                NightMode.invertTimer += deltaTime;
            } else {
                // 每700米触发黑夜,
                NightMode.invertTrigger = !(gameFrame % 500);
                if (NightMode.invertTrigger && NightMode.invertTimer === 0) {
                    NightMode.invertTimer += deltaTime;
                    NightMode.inverted = div.classList.toggle('inverted', NightMode.invertTrigger);
                }
            }
        },
        reset: function () {
            this.currentPhase = 0;
            this.opacity = 0;
            this.update(false);
        }

    };

    //  云朵构造函数
    Cloud.config = {
        HEIGHT: 14,//云朵sprite的高度
        MAX_CLOUD_GAP: 400,//两朵云之间最大的间隙
        MAX_SKY_LEVEL: 30,//云朵 的最大高度
        MIN_CLOUD_GAP: 100,//两朵云之间的最小间隙
        MIN_SKY_LEVEL: 71,//云朵的最小高度
        WIDTH: 46,//云朵sprite的宽度
        MAX_CLOUDS: 6,//最多云朵数量
        CLOUD_FREQUENCY: 0.5//云朵出现频率
    };
    // 储存云朵
    Cloud.clouds = [];
    function getRandomNum(min, max) {
        return Math.floor(Math.random() * (max - min + 1)) + min;
    }
    // canvas用于绘制的画布
    // spritePos在雪碧图中的坐标
    // containerWidth容器宽度
    function Cloud(canvas, spritePos, containerWidth) {

        this.canvas = canvas;
        this.ctx = canvas.getContext('2d');
        this.spritePos = spritePos;
        this.xPos = containerWidth;//云朵初始x坐标在屏幕外
        this.yPos = 0;//云朵初始高度
        this.remove = false;//是否移除

        // console.log(this.spritePos.x);
        // 云朵之间的间隙400-100
        this.cloudGap = getRandomNum(Cloud.config.MAX_CLOUD_GAP, Cloud.config.MIN_CLOUD_GAP);
        this.init();
    }
    // 主要逻辑代码在Cloud的原型链中
    Cloud.prototype = {
        init: function () {
            // 设置云朵的高度为随机30-71
            this.yPos = getRandomNum(Cloud.config.MAX_SKY_LEVEL, Cloud.config.MIN_SKY_LEVEL);
            this.draw();
        },
        draw: function () {
            // 不使用save和restore下一次绘制会在第一次绘制的基础上继续绘制,添加过后第二次绘制会回归初始化
            this.ctx.save();
            var sourceWidth = Cloud.config.WIDTH,
                sourceHeight = Cloud.config.HEIGHT;
            this.ctx.drawImage(imgSprite,
                this.spritePos.x, this.spritePos.y,
                sourceWidth, sourceHeight,
                this.xPos, this.yPos,
                sourceWidth, sourceHeight);
            this.ctx.restore();
        },
        // 添加云朵并控制其移动
        updateClouds: function (speed) {
            var numClouds = Cloud.clouds.length;
            if (numClouds) {
                for (var i = numClouds - 1; i >= 0; i--) {
                    Cloud.clouds[i].update(speed);
                };
                var lastCloud = Cloud.clouds[numClouds - 1];
                // 若当前存在的云朵数量小于最大云朵数量
                // 并且云朵位置大于间隙时
                // 随机添加云朵
                // 云朵数量小于设定最大值,屏幕宽度-初始坐标大于最后一个云朵
                if (numClouds < Cloud.config.MAX_CLOUDS &&
                    (DEFAULT_WIDTH - lastCloud.xPos) > lastCloud.cloudGap &&
                    Cloud.config.CLOUD_FREQUENCY > Math.random()) {
                    this.addCloud();
                };
                // 过滤掉已经移出屏幕外的云朵
                Cloud.clouds = Cloud.clouds.filter(function (obj) {
                    return !obj.remove;
                });
            } else {
                this.addCloud();
            }
        },
        update: function (speed) {
            // 仅绘制符合条件的云朵
            if (!this.remove) {
                // 向左移动
                this.xPos -= Math.ceil(speed);
                this.draw();
                if (!this.isVisible()) {
                    this.remove = true;
                }
            }
        },
        // 判断云朵是否移除屏幕外
        isVisible: function () {
            return this.xPos + Cloud.config.WIDTH > 0;
        },
        // 将云朵添加至数组
        addCloud: function () {
            var cloud = new Cloud(this.canvas, spriteDefinition.CLOUD, DEFAULT_WIDTH);
            Cloud.clouds.push(cloud);
        }
    };

    // canvas地面绘制在画布上
    // spritpos 地面在精灵图上的坐标
    function HorizonLine(canvas, spritePos) {
        this.spritePos = spritePos;
        this.canvas = canvas;
        this.ctx = canvas.getContext("2d");
        this.sourceDimensions = {};
        this.dimensions = HorizonLine.dimensions;
        // 在精灵图坐标为2处和602处分别为不同的地形
        this.sourceXPos = [this.spritePos.x, this.spritePos.x + this.dimensions.WIDTH];
        this.xPos = [];//地面在画布中的x坐标
        this.yPos = 0;//地面在画布中的y坐标
        this.bumpThreshold = 0.5;	//随机地形系数

        this.setSourceDimesions();
        this.draw();
    }

    HorizonLine.dimensions = {
        WIDTH: 600,	//宽600
        HEIGHT: 12,	//高12像素
        YPOS: 127	//在画布中的位置
    };
    // HorizonLine原型中的方法
    HorizonLine.prototype = {
        setSourceDimesions: function () {
            for (var dimension in HorizonLine.dimensions) {
                this.sourceDimensions[dimension] = HorizonLine.dimensions[dimension];
                this.dimensions[dimension] = HorizonLine.dimensions[dimension];
            }
            //地面在画布上的位置
            this.xPos = [0, HorizonLine.dimensions.WIDTH];//0,600
            this.yPos = HorizonLine.dimensions.YPOS;
        },
        //随机地形
        getRandomType: function () {
            return Math.random() > this.bumpThreshold ? this.dimensions.WIDTH : 0;
        },
        draw: function () {
            //要绘制的图像,图像将要被绘制的区域的左上角的坐标,图像所要绘制区域的大小,所要绘制的图像区域的左上角坐标,图像区域所要绘制的画布大小
            this.ctx.drawImage(imgSprite,
                this.sourceXPos[0], this.spritePos.y,
                this.sourceDimensions.WIDTH, this.sourceDimensions.HEIGHT,
                this.xPos[0], this.yPos,
                this.dimensions.WIDTH, this.dimensions.HEIGHT);

            this.ctx.drawImage(imgSprite,
                this.sourceXPos[1], this.spritePos.y,
                this.sourceDimensions.WIDTH, this.sourceDimensions.HEIGHT,
                this.xPos[1], this.yPos,
                this.dimensions.WIDTH, this.dimensions.HEIGHT);

        },
        updateXPos: function (pos, increment) {
            var line1 = pos,
                line2 = pos === 0 ? 1 : 0;

            this.xPos[line1] -= increment;
            this.xPos[line2] = this.xPos[line1] + this.dimensions.WIDTH;
            // 若第一段地面完全移出canvas外
            if (this.xPos[line1] <= -this.dimensions.WIDTH) {
                // 则将其移动至canvas外右侧
                this.xPos[line1] += this.dimensions.WIDTH * 2;
                // 同时将第二段地面移动至canvas内
                this.xPos[line2] = this.xPos[line1] - this.dimensions.WIDTH;
                // 随机选择地形
                this.sourceXPos[line1] = this.getRandomType() + this.spritePos.x;
            }
        },
        update: function (deltaTime, speed) {
            //FPS每秒传真率(刷新率)
            var increment = Math.floor(speed * (FPS / 1000) * deltaTime);
            if (this.xPos[0] <= 0) {
                // 交换地面一和地面二
                this.updateXPos(0, increment);
            } else {
                this.updateXPos(1, increment);
            }
            this.draw();
        },
        reset: function () {
            this.xPos[0] = 0;
            this.xPos[1] = HorizonLine.dimensions.WIDTH;
        }
    };


    //不写onload方法就显示不出图片
    window.onload = function () {
        var canvas = document.createElement('canvas'),
            a = document.getElementById('runner-container'),
            ctx = canvas.getContext('2d');
        canvas.id = 'c';
        canvas.width = 600;
        canvas.height = 150;
        a.appendChild(canvas);
        // console.log(spriteDefinition.CLOUD.x);
        
        var speed = 3;
        var h = new HorizonLine(canvas, spriteDefinition.HORIZON);
        var cloud = new Cloud(canvas, spriteDefinition.CLOUD, DEFAULT_WIDTH);
        var night = new NightMode(canvas, spriteDefinition.MOON, DEFAULT_WIDTH);
        var obstacle = new Obstacle(canvas,Obstacle.types[0] ,spriteDefinition,  0.6, 1);
        // console.log(obstacle);
        var startTime = 0;
        var deltaTime;
        (function draw(time) {
            gameFrame++;
            if (speed < 13.5) {
                speed += 0.01;
            }
            // 清空矩形区域内的绘图
            ctx.clearRect(0, 0, 600, 150);
            time = time || 0;
            deltaTime = time - startTime;
            h.update(deltaTime, speed);
            cloud.updateClouds(0.2);
            night.invert(deltaTime);
            obstacle.updateObstacles(deltaTime, speed);
            // console.log(speed);
            startTime = time;
            window.requestAnimationFrame(draw, canvas);
        })();
    };
</script>

参考了博客园逐影的文章,讲的非常详细。

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值