clearing circular regions from HTML5 Canvas

后端 未结 6 1047
耶瑟儿~
耶瑟儿~ 2020-11-30 01:37

It appears the only way to clear a region from a canvas is to use the clearRect() command - I need to clear a circle (I am masking out areas from a filled canvas, point ligh

相关标签:
6条回答
  • 2020-11-30 02:20

    Use canvas.getContext("2d").arc(...) to draw a circle over the area with the background colour?

    var canvas = document.getElementById("myCanvas");
    var context = canvas.getContext("2d");
    context.arc(x, y, r, 0, 2*Math.PI, false);
    context.fillStyle = "#FFFFFF";
    context.fill();
    
    0 讨论(0)
  • 2020-11-30 02:23

    If you're making a game or something where squeezing every bit of performance matters, have a look at how I made this answer: Canvas - Fill a rectangle in all areas that are fully transparent

    Specifically, the edit of the answer that leads to this: http://jsfiddle.net/a2Age/2/

    The huge plusses here:

    • No use of paths (slow)
    • No use of clips (slow)
    • No need for save/restore (since there's no way to reset a clipping region without clearing all state(1), it means you must use save/restore also)

    (1) I actually complained about this and resetClip() has been put in the offical spec because of it, but it will be a while before browsers implement it.

    Code

    var ctx          = document.getElementById('canvas1').getContext('2d'),
        ambientLight = 0.1,
        intensity    = 1,
        radius       = 100,
        amb          = 'rgba(0,0,0,' + (1 - ambientLight) + ')';
    
    addLight(ctx, intensity, amb, 200, 200, 0, 200, 200, radius); // First circle
    addLight(ctx, intensity, amb, 250, 270, 0, 250, 270, radius); // Second circle
    addLight(ctx, intensity, amb, 50, 370, 0, 50, 370, radius, 50); // Third!
    
    ctx.fillStyle = amb;
    ctx.globalCompositeOperation = 'xor';
    ctx.fillRect(0, 0, 500, 500);
    
    function addLight(ctx, intsy, amb, xStart, yStart, rStart, xEnd, yEnd, rEnd, xOff, yOff) {
      xOff = xOff || 0;
      yOff = yOff || 0;
    
      var g = ctx.createRadialGradient(xStart, yStart, rStart, xEnd, yEnd, rEnd);
      g.addColorStop(1, 'rgba(0,0,0,' + (1 - intsy) + ')');
      g.addColorStop(0, amb);
      ctx.fillStyle = g;
      ctx.fillRect(xStart - rEnd + xOff, yStart - rEnd + yOff, xEnd + rEnd, yEnd + rEnd);
    }
    canvas {
      border: 1px solid black;
      background-image: url('http://placekitten.com/500/500');
    }
    <canvas id="canvas1" width="500" height="500"></canvas>

    0 讨论(0)
  • 2020-11-30 02:32

    Given the requirements, these answers are fine. But lets say you're like me and you have additional requirements:

    1. You want to "clear" a part of a shape that may be partially outside the bounds of the shape you're clearing.
    2. You want to see the background underneath the shape instead of clearing the background.

    For the first requirement, the solution is to use context.globalCompositeOperation = 'destination-out' The blue is the first shape and the red is the second shape. As you can see, destination-out removes the section from the first shape.

    Here's some example code:

      explosionCanvasCtx.fillStyle = "red"
      drawCircle(explosionCanvasCtx, projectile.radius, projectile.radius, projectile.radius)
      explosionCanvasCtx.fill()
    
      explosionCanvasCtx.globalCompositeOperation = 'destination-out' #see https://developer.mozilla.org/samples/canvas-tutorial/6_1_canvas_composite.html
      drawCircle(explosionCanvasCtx, projectile.radius + 20, projectile.radius, projectile.radius)
      explosionCanvasCtx.fill()
    

    Here's the potential problem with this: The second fill() will clear everything underneath it, including the background. Sometimes you'll want to only clear the first shape but you still want to see the layers that are underneath it.

    The solution to that is to draw this on a temporary canvas and then drawImage to draw the temporary canvas onto your main canvas. The code will look like this:

      diameter = projectile.radius * 2
      console.log "<canvas width='" + diameter + "' height='" + diameter + "'></canvas>"
      explosionCanvas = $("<canvas width='" + diameter + "' height='" + diameter + "'></canvas>")
      explosionCanvasCtx = explosionCanvas[0].getContext("2d")
    
      explosionCanvasCtx.fillStyle = "red"
      drawCircle(explosionCanvasCtx, projectile.radius, projectile.radius, projectile.radius)
      explosionCanvasCtx.fill()
    
      explosionCanvasCtx.globalCompositeOperation = 'destination-out' #see https://developer.mozilla.org/samples/canvas-tutorial/6_1_canvas_composite.html
      durationPercent = (projectile.startDuration - projectile.duration) / projectile.startDuration
      drawCircle(explosionCanvasCtx, projectile.radius + 20, projectile.radius, projectile.radius)
      explosionCanvasCtx.fill()
      explosionCanvasCtx.globalCompositeOperation = 'source-over' #see https://developer.mozilla.org/samples/canvas-tutorial/6_1_canvas_composite.html
    
      ctx.drawImage(explosionCanvas[0], projectile.pos.x - projectile.radius, projectile.pos.y - projectile.radius) #center
    
    0 讨论(0)
  • 2020-11-30 02:33

    Where x = left position, y = right position, r = radius, and ctx = your canvas:

    function clearCircle( x , y , r ){
        for( var i = 0 ; i < Math.round( Math.PI * r ) ; i++ ){
            var angle = ( i / Math.round( Math.PI * r )) * 360;
            ctx.clearRect( x , y , Math.sin( angle * ( Math.PI / 180 )) * r , Math.cos( angle * ( Math.PI / 180 )) * r );
        }
    }
    
    0 讨论(0)
  • 2020-11-30 02:35

    Use .arc to create a circular stroke and then use .clip() to make that the current clipping region.

    Then you can use .clearRect() to erase the whole canvas, but only the clipped area will change.

    0 讨论(0)
  • 2020-11-30 02:38

    You have a few options.

    Firstly, here's a function we'll use to fill a circle.

    var fillCircle = function(x, y, radius)
    {
        context.beginPath();
        context.arc(x, y, radius, 0, 2 * Math.PI, false);
        context.fill();
    };
    

    clip()

    var clearCircle = function(x, y, radius)
    {
        context.beginPath();
        context.arc(x, y, radius, 0, 2 * Math.PI, false);
        context.clip();
        context.clearRect(x - radius - 1, y - radius - 1,
                          radius * 2 + 2, radius * 2 + 2);
    };
    

    See this on jsFiddle.


    globalCompositeOperation

    var clearCircle = function(x, y, radius)
    {
        context.save();
        context.globalCompositeOperation = 'destination-out';
        context.beginPath();
        context.arc(x, y, radius, 0, 2 * Math.PI, false);
        context.fill();
        context.restore();
    };
    

    See this on jsFiddle.


    Both gave the desired result on screen, however the performance wasn't sufficient in my case as I was drawing and clearing a lot of circles each frame for an effect. In the end I found a different way to get a similar effect to what I wanted by just drawing thicker lines on an arc, but the above may still be useful to someone having different performance requirements.

    0 讨论(0)
提交回复
热议问题