HTML5 Context/Canvas - 何时在上下文上调用绘制

发布于 2025-01-03 21:01:54 字数 4210 浏览 1 评论 0原文

我正在编写一个面向对象风格的 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>

谢谢你的建议, 马克

I'm writing a little object oriented style javasscript demo -- just to draw a bunch of balls moving around the screen. nothing fancy, no collision detection or anything at this point. Consider it safe to assume my Ball.js class is good.

My question amounts to this: Where should I call ball.draw(context) ? The only way to get balls drawn to the screen the way I set it up seems to be by placing the call in generateBalls(). But that means each ball is just drawn once.

So I'd really appreaciate it if someone could point out the error of my ways here. This isn't homework - just trying to get a better handle on javascript and 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>

Thank you for your advice,
Marc

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

热血少△年 2025-01-10 21:01:54

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

<!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/

Your example contains more than one error, please check your modified code. It works, but you must extend and correct it.

<!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/

厌味 2025-01-10 21:01:54

只是破坏线程,为刚接触画布的人添加一些更新:

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

然而,当谈到使用 setInterval 来触发这个问题时,我不同意 Stan 的观点……尽管,这很可能是 Stan 编写它时的常见做法。

然而现在我们使用requestAnimationFrame。

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

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

使用 setInterval 不能保证它何时触发,并且当然不能保证它与浏览器屏幕刷新的时间一致。

这意味着它正在绘制一点,重新配置页面,再绘制一点,再次重新配置,绘制更多......如此周而复始。这是非常糟糕的,而且非常慢。

然而,使用 requestAnimationFrame 允许浏览器在要重绘屏幕时调用您的函数……这意味着一次重绘和一次刷新。

更快、更干净。

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

requestAnimationFrame(redraw);

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

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

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

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

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

    requestAnimationFrame(redraw);
}

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

function redraw()
{
    ...

    if (!finished) requestAnimationFrame(redraw);
}

Mozilla 参考 此处,Paul Irish 此处,以及 Chris Coyer 此处

Just necroing the thread to add a bit of an update for anyone new to canvas who arrives here:

Stan recommended creating a function to draw each of the balls at one spot in code; this is good for a number of reasons, it makes the animation smoother, and the code far easier to debug/maintain.

I would disagree with Stan, however, when it comes to using setInterval to trigger this... although, that might very well have been common practice when Stan wrote it.

Now, however, we use requestAnimationFrame.

This will sync your painting with the rest of the browser.

This allows your code to run far faster since every time the browser draws something, it has to go through the entire page again to determine where everything will go, just in case something moved.

Using setInterval leaves no guarantee as to when it will fire, and certainly doesn't time it to coincide with the browser's screen refresh.

This means it is drawing a little, reconfiguring the page, drawing a little more, reconfiguring again, drawing some more... and again and again. This is very bad, and very slow.

Using requestAnimationFrame, however, allows the browser to call your function whenever it is about to redraw the screen anyway... which means a single redraw, and a single refresh.

Far faster, far cleaner.

It works slightly differently, but is actually very simple.

requestAnimationFrame(redraw);

This registers your function 'redraw' with requestAnimationFrame so that the next time the browser wants to redraw the window, your function will be called.

The one gotcha with this is that it will only call your function ONCE... in that way, it is like a setTimeout rather than a setInterval.

This way, you don't need to pass around the timer variable to stop the animation; it will stop because it stopped being called.

However, to ensure that your animation continues to run, just put that very same call at the bottom of your redraw function:

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

    requestAnimationFrame(redraw);
}

You can also set this conditionally, to run your animation until it is finished, then stop:

function redraw()
{
    ...

    if (!finished) requestAnimationFrame(redraw);
}

Mozilla reference HERE, Paul Irish HERE, and Chris Coyer HERE.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文