【问题标题】:HTML5 Context/Canvas - when to call draw on the contextHTML5 Context/Canvas - 何时在上下文上调用 draw
【发布时间】:2019-05-20 16:02:39
【问题描述】:

我正在编写一个小的面向对象风格的 JavaScript 演示——只是为了画一堆在屏幕上移动的球。在这一点上没有什么花哨的,没有碰撞检测或任何东西。假设我的 Ball.js 类是好的是安全的。

我的问题是这样的:我应该在哪里调用 ball.draw(context) ?以我设置的方式将球绘制到屏幕上的唯一方法似乎是将调用放在 generateBalls() 中。但这意味着每个球只抽一次。

因此,如果有人可以在这里指出我的方式错误,我真的很感激。这不是家庭作业 - 只是想更好地处理 javascript 和 canvas。

<!DOCTYPE HTML>
<html>
<head>
<meta charset="utf-8">
<title>Untitled Document</title>
<script src="ball.js"></script>
<script src="utils.js"></script>
...
    <canvas id="canvas" width="600" height="480"></canvas>
    <script type="text/javascript">
        window.addEventListener('load', eventWindowLoaded, false);

        function eventWindowLoaded() {
            canvasApp();    
        }

        function canvasSupport() {
            return true;    
        }

        function canvasApp() {
            if(!canvasSupport()) {
                return; 
            }
        }
        console.log("app entered");
        var numBalls = 45;
        //var numBalls = demo.numberofballs.value;
        var maxSize = 8;
        var minSize = 5; 
        var maxSpeed = maxSize + 5;
        var balls = new Array();
        var tempBall;
        var tempX;
        var tempY;
        var tempSpeed;
        var tempAngle;
        var tempRadius;
        var tempRadians;
        var tempXunits;
        var tempYunits;

        canvas = document.getElementById("canvas");
        context = canvas.getContext("2d");

        generateBalls();

        setInterval(drawScreen, 33);

        function generateBalls() {
            console.log("Make some balls");
            for(var index = 0; index < numBalls; index++) {
                var tempRadius = Math.floor(Math.random()*maxSize)+minSize;
                var ball = new Ball(tempRadius, "#000000"); 
                ball.x = tempRadius * 2 + (Math.floor(Math.random()*canvas.width) -  tempRadius * 2);
                ball.y = tempRadius * 2 + (Math.floor(Math.random()*canvas.height) - tempRadius * 2);
                ball.speed = maxSpeed - tempRadius;
                ball.angle = Math.floor(Math.random()*360);
                ball.dx = Math.cos(tempRadians) * tempSpeed;
                ball.dy = Math.sin(tempRadians) * tempSpeed;
                // here outputted balls but a stupid place to put it LOL
                balls.push(ball);



            }

        }

        function drawScreen() {
            console.log("draw screen");



            // loop through all balls and adjust their position
            // a BallManager could do this more cleanly
            for(var index = 0; index < balls.length; index++) {

                context.fillStyle="#EE00EE";
                context.fillRect(0,0,canvas.width, canvas.height);

                // Box
                context.strokeStyle = "#ff0043";
                context.strokeRect(1,1,canvas.width-2, canvas.height-2);

                // place balls
                context.fillStyle = "#ff8783";
                console.log("ball mover loop in drawscreen");
                // no var ball now

                ball = balls[index];
                ball.x += ball.dx;
                ball.y += ball.dy;
                ball.draw(context);
                //checkBoundaries(balls[index]);
                if(ball.x > canvas.width || ball.x < 0) {
                ball.angle = 180 - ball.angle;
                updateBall(ball);   
                    } else if(ball.y > canvas.height || ball.y < 0) {
                        ball.angle = 360 - ball.angle;
                        updateBall(ball);   
                        //ball.draw(context);
                }

            }

        }   

        //function checkBoundaries(ball) {
            //console.log("Check Bounds: " + " " + "ball.x: " + ball.x + " " + //"ball.y: " + ball.y);

        //}

        function updateBall(ball) {
            ball.radians = ball.angle * Math.PI / 180;
            ball.dx = Math.cos(ball.radians) * ball.speed;
            ball.dy = Math.sin(ball.radians) * ball.speed;
            //ball.draw(context);
        }

    </script>
</body>
</html>

感谢您的建议, 马克

【问题讨论】:

  • 我认为,一种可能的方法是引入一个全屏更新功能,该功能清除背景并将所有球绘制在当前位置。这个函数应该通过 setInterval 调用。

标签: javascript html html5-canvas html5-animation


【解决方案1】:

您的示例包含多个错误,请检查您修改后的代码。它有效,但您必须扩展和更正它。

<!DOCTYPE HTML>
<html>
  <head>
  <meta charset="utf-8">
  <title>Untitled Document</title>
  <script type="text/javascript">
    // next lines is a Ball() implementation code
    Ball = function(radius,color) {
        this.radius=radius;
        this.color=color;
    };
    Ball.prototype.x=0;
    Ball.prototype.y=0;
    Ball.prototype.speed=0;
    Ball.prototype.angle=0;
    Ball.prototype.dx=0;
    Ball.prototype.dy=0;
    Ball.prototype.radius=10;
    Ball.prototype.color="#000";
    Ball.prototype.draw=function() {

        context.beginPath();
        context.arc(this.x,this.y,this.radius,0,Math.PI*2,true);
        context.lineWidth = 5;
        context.strokeStyle = this.color; // line color
        context.stroke();
        context.closePath();
    };

    window.addEventListener('load', eventWindowLoaded, false);

    function eventWindowLoaded() {
        canvasApp();    

        //console.log("app entered");
        window.canvas = document.getElementById("canvas");
        window.context = canvas.getContext("2d");

        generateBalls();
        // if you want to use setInterval() instead replace next line
        setTimeout(drawScreen, 33);
    }

    function canvasSupport() {
        return true;    
    }

    function canvasApp() {
        if(!canvasSupport()) {
            return;
        }
    }

    var numBalls = 45;
    //var numBalls = demo.numberofballs.value;
    var maxSize = 8;
    var minSize = 5;
    var maxSpeed = maxSize + 5;
    var balls = new Array();
    var tempBall;
    var tempX;
    var tempY;
    var tempSpeed;
    var tempAngle;
    var tempRadius;
    var tempRadians;
    var tempXunits;
    var tempYunits;

    function generateBalls() {
        //console.log("Make some balls");
        for(var index = 0; index < numBalls; index++) {
            var tempRadius = Math.floor(Math.random()*maxSize)+minSize;
            var tempRadians = Math.random()*Math.PI;
            var tempSpeed = 10;
            var ball = new Ball(tempRadius, "#000000");
            ball.x = tempRadius * 2 + (Math.floor(Math.random()*canvas.width) -  tempRadius * 2);
            ball.y = tempRadius * 2 + (Math.floor(Math.random()*canvas.height) - tempRadius * 2);
            ball.speed = maxSpeed - tempRadius;
            ball.angle = Math.floor(Math.random()*360);
            ball.dx = Math.cos(tempRadians) * tempSpeed;
            ball.dy = Math.sin(tempRadians) * tempSpeed;
            // here outputted balls but a stupid place to put it LOL
            balls.push(ball);
        }
    }

    function drawScreen() {
        console.log("draw screen");


        context.fillStyle="#EE00EE";
        context.fillRect(0,0,canvas.width, canvas.height);

        // Box
        context.strokeStyle = "#ff0043";
        context.strokeRect(1,1,canvas.width-2, canvas.height-2);

        // loop through all balls and adjust their position
        // a BallManager could do this more cleanly
        for(var index = 0; index < balls.length; index++) {
            // place balls
            context.fillStyle = "#008700";
            //console.log("ball mover loop in drawscreen");
            // no var ball now

            ball = balls[index];
            ball.x += ball.dx;
            ball.y += ball.dy;
            ball.draw(context);
            //checkBoundaries(balls[index]);
            if(ball.x > canvas.width || ball.x < 0) {
                ball.angle = 180 - ball.angle;
                updateBall(ball);   
            } else if(ball.y > canvas.height || ball.y < 0) {
                ball.angle = 360 - ball.angle;
                 updateBall(ball);   
                //ball.draw(context);
            }

        }
        // if you want to use setInterval() instead remove next line
        setTimeout(drawScreen, 33);
    }   

    //function checkBoundaries(ball) {
        //console.log("Check Bounds: " + " " + "ball.x: " + ball.x + " " + //"ball.y: " + ball.y);

    //}

    function updateBall(ball) {
        ball.radians = ball.angle * Math.PI / 180;
        ball.dx = Math.cos(ball.radians) * ball.speed;
        ball.dy = Math.sin(ball.radians) * ball.speed;
        //ball.draw(context);
    }

  </script>
  </head>
  <body>
    <canvas id="canvas" width="600" height="480" style="background:red;"></canvas>
  </body>
</html>

http://jsfiddle.net/QVgZx/2/

【讨论】:

  • 我知道 Ball 在这里的工作方式,但这是我的设置方式——我也经常看到其他人这样做。
  • @Marc H:对于您的代码:1) 将&lt;script&gt; 标记放置在&lt;head&gt; 以外的任何标记内是一种糟糕的技术。 2)Ball() 实现:Ball 构造函数中重要的两行,draw() 方法和声明所有 Ball 成员的初始值。 3) “应用程序启动代码” 移入窗口加载事件处理函数(代码中的 eventWindowLoaded)。 4) 修正 generateBalls(): 添加 tempRadiand 和 tempSpeed 变量设置。 5) 在 drawScreen() 中,背景绘图代码从 for 循环移动到顶部
【解决方案2】:

只是删除线程以为到达这里的画布新手添加一些更新:

Stan 建议在代码中创建一个函数来在一个位置绘制每个球;这有很多好处,它使动画更流畅,代码更容易调试/维护。

但是,当谈到使用 setInterval 来触发此操作时,我不同意 Stan 的观点……尽管,当 Stan 编写它时,这很可能是很常见的做法。

不过,现在我们使用 requestAnimationFrame。

这会将您的绘画与浏览器的其余部分同步。

这可以让您的代码运行得更快,因为每次浏览器绘制某些东西时,它都必须再次遍历整个页面以确定所有内容的去向,以防万一发生移动。

使用 setInterval 不能保证它什么时候触发,当然也不能保证它与浏览器的屏幕刷新时间一致。

这意味着它正在绘制一点,重新配置页面,再绘制一点,再次重新配置,再绘制一些......一次又一次。这很糟糕,而且很慢。

然而,使用 requestAnimationFrame 可以让浏览器在它即将重绘屏幕时调用您的函数...这意味着一次重绘和一次刷新。

更快,更清洁。

它的工作方式略有不同,但实际上非常简单。

requestAnimationFrame(redraw);

这会将您的函数“重绘”注册到 requestAnimationFrame,以便下次浏览器想要重绘窗口时,将调用您的函数。

这样做的一个问题是它只会调用你的函数一次......这样,它就像一个 setTimeout 而不是一个 setInterval。

这样,您不需要传递计时器变量来停止动画;它将停止,因为它停止被调用。

但是,为了确保您的动画继续运行,只需将相同的调用放在重绘函数的底部即可:

function redraw()
{
    var blah, blahblah;
    ...

    requestAnimationFrame(redraw);
}

您也可以有条件地设置它,以运行动画直到完成,然后停止:

function redraw()
{
    ...

    if (!finished) requestAnimationFrame(redraw);
}

Mozilla 参考 HERE、Paul Irish HERE 和 Chris Coyer HERE

【讨论】:

    猜你喜欢
    • 2016-10-29
    • 2017-07-18
    • 2018-11-06
    • 1970-01-01
    • 2013-06-10
    • 2014-12-21
    • 1970-01-01
    • 1970-01-01
    • 2017-09-23
    相关资源
    最近更新 更多