Question

I'm trying to make a clock with HTML 5's canvas element.

What i'm trying to do is make a line for every second, and then erase the previous line.

I want to erase the previous line with drawing another line using the globalCompositeOperation='xor'; but it doesn't work!

Here is the code:

    <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
    <html xmlns="http://www.w3.org/1999/xhtml">
    <head>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
    <title>Clock</title>
    </head>

    <body onload="spin()">
    <canvas id="myCanvas" width="578" height="400"></canvas>
    <script>
    var firstTime = 0;
    var prevX=null;
    var prevY=null;

    function spin() {
        //get the canvas element
        var canvas = document.getElementById('myCanvas');
        var context = canvas.getContext('2d');

        //get the right angle for the clock hand
        var date = new Date;
        var seconds = date.getSeconds();
        var a = seconds*6;
        var angleRadian = a*Math.PI/180;
        var angle = 1/2*Math.PI - angleRadian;

        if(a > 360)
            a = 0; 
        //Erase the previous line, if it has been drawn.
        if(prevX!=null)
            erasePrevLine(angle, canvas, context);
        //draw line
        drawLine(angle, 100, canvas, context);
        //repeat for the next second
        setTimeout(spin, 500);
    }
    function drawLine(angle, radius, canvas, context)   {
        var centerX = canvas.width/2;
        var centerY = canvas.height/2;

        var xTarget = centerX + Math.cos(angle)*radius;
        var yTarget = centerY - Math.sin(angle)*radius;

        //save this state to be erased
        prevX = xTarget;
        prevY = yTarget;

        //draw
        context.beginPath();
        context.moveTo(centerX,centerY);
        context.lineTo(xTarget, yTarget);
        context.stroke();
    }
    function erasePrevLine(angle, canvas, context)  {
        context.globalCompositeOperation = 'xor';
        var centerX = canvas.width/2;
        var centerY = canvas.height/2;
        prevAngle = angle + (Math.PI/180*6);
        var xTarget = prevX;
        var yTarget = prevY;

        //draw on the previous line
        context.beginPath();
        context.moveTo(centerX,centerY);
        context.lineTo(xTarget, yTarget );
        context.stroke();
    }
    </script>
    </body>
    </html>

And here is the live example: http://jsfiddle.net/pyerT/1/ Anybody knows the answer? it works fine with shapes and texts..

Was it helpful?

Solution

globalCompositeOperation (xor) will not work on a rotating line like a “clock hand”….here’s why:

Assume you draw a vertical line. Then draw a second vertical line to the right of the first. Assume the second vertical line overlaps the first line by half.

Canvas.globalCompositeOperation=”xor” causes overlapping areas to be removed, so the second line removes half the first line and also half of itself.

Here is code and a Fiddle: http://jsfiddle.net/m1erickson/e24KU/

    function drawLine(){
        ctx.globalCompositeOperation="xor";
        ctx.strokeStyle="red";
        ctx.lineWidth=10;
        ctx.beginPath();
        ctx.moveTo(posX,10);
        ctx.lineTo(posX,100);
        ctx.stroke();
        posX+=5;
    }

This is a Fiddle of a “clock hand” sweeping around a centerpoint: http://jsfiddle.net/m1erickson/hW2EY/

However, if we try to use “xor” on a rotating line, the lines overlay at an angle and therefore the xor is incomplete.

Here is a Fiddle showing the line “xor” being ineffective when rotated: http://jsfiddle.net/m1erickson/f7hHx/

[Edited: new code was supplied by OP allowing for an expanded answer]

I looked at your new code and am suggesting some changes and optimizations.

As I said in my original answer, you cannot effectively erase a line that was drawn at an angle. This is due to anti-aliasing that the browser does automatically—antiAliasing for canvas cannot be turned off.

Here is a Fiddle of the results after the changes: http://jsfiddle.net/m1erickson/9QD65/

Changes:

Believe it or not: It is usual to completely erase and completely redraw the canvas during each animation loop! Canvas really is quick enough to handle these redraws—especially now that canvas is GPU accelerated. If you absolutely need to optimize your performance, you can define “dirty” areas of the canvas that must be erased/redrawn and leave the other areas as previously drawn. In practice, once you need this type of performance, your canvas is so complicated that it’s more efficient to completely clear/redraw than to try to define the dirty areas.

Optimizations:

Moved canvas,context,centerX,centerY out of the animation loop since these values can be computed once and reused.

Here is my suggested code for you to look at:

 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
    <html xmlns="http://www.w3.org/1999/xhtml">
    <head>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
    <title>Clock</title>
    </head>

    <body onload="spin()">
    <canvas id="myCanvas" width="578" height="400"></canvas>
    <script>
    var firstTime = 0;
    var canvas = document.getElementById('myCanvas');
    var context = canvas.getContext('2d');
    var centerX=canvas.width/2;
    var centerY=canvas.height/2;
    var canvasWidth=canvas.width;
    var canvasHeight=canvas.height;

    function spin() {
        //get the right angle for the clock hand
        var date = new Date;
        var seconds = date.getSeconds();
        var a = seconds*6;
        var angleRadian = a*Math.PI/180;
        var angle = 1/2*Math.PI - angleRadian;

        if(a > 360)
            a = 0; 
        //draw line
        drawLine(angle, 100, canvas, context, "black",1);
        //repeat for the next second
        setTimeout(spin, 500);
    }

    function drawLine(angle, radius, canvas, context)   {
        var xTarget = centerX + Math.cos(angle)*radius;
        var yTarget = centerY - Math.sin(angle)*radius;

        //clear the canvas
        context.clearRect(0,0,canvasWidth,canvasHeight);

        //draw
        context.save();
        context.beginPath();
        context.moveTo(centerX,centerY);
        context.lineTo(xTarget, yTarget);
        context.stroke();
        context.restore()
    }

    </script>
    </body>
    </html>
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top