Search code examples
javascripthtmlcanvasclip

How do I create a canvas html5 clipping region with a hole in the middle?


I am trying to dynamically code this image using HTML5 Canvas/Javascript:

enter image description here

The hole in the middle must be transparent, because I am superimposing this image onto a background. My code draws two oval paths, one inside the other. I am assuming that the best way to do this is by creating a clipping region that allows a fill inside the larger oval but outside the smaller one. How do I do this? Please help!­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­

My code (does not resolve the problem because it uses a white-out fill for the smaller oval):

var canvas = document.getElementById("myCanvas");
var context = canvas.getContext("2d");
//parameters of the ovals;

var height = 20;
var curve1 = 0.56;
var curve2 = 0.44;
var width_ratio = 0.38;
var x_pos = 0;
var y_pos = 0;var x1 = x_pos;
var y1 = y_pos - height/2;
var x2 = x_pos + height * width_ratio;
var y2 = y_pos;
var x3 = x_pos;
var y3 = y_pos + height/2;
var x4 = x_pos - height * width_ratio;
var y4 = y_pos;

var xv1 = x1 + ((x2 - x1) * curve1);
var yv1 = y1;
var xv2 = x2;
var yv2 = y1 + ((y2 - y1) * curve2);
var xv3 = x2;
var yv3 = y2 + (height/2 - (height/2 * curve2));
var xv4 = xv1;
var yv4 = y3;
var xv5 = x3 - ((x2 - x1) * curve1);
var yv5 = y3;
var xv6 = x4;
var yv6 = yv3;
var xv7 = x4;
var yv7 = y4 - (height/2 - (height/2 * curve2));
var xv8 = x1 - ((x2 - x1) * curve1);
var yv8 = y1;

var x5 = x1;
var y5 = y1 + height / 10;
var x6 = x2 - height / 5;//x2
var y6 = y2;//y2
var x7 = x5;//x3
var y7 = y3 - height / 10;//y3
var x8 = x4 + height / 5;
var y8 = y4;//y4


var xv9 = x5 + ((x6 - x5) * curve1);//xv1
var yv9 = y5;//yv1
var xv10 = x6;//xv2
var yv10 = y5 + ((y6 - y5) * curve2);//yv2 
var xv11 =  x6;//xv3
var yv11 = y7 - ((y7 - y6) * curve2);//yv3
var xv12 = xv9;//xv4
var yv12 = y7;//yv4
var xv13 = x5 - ((x6 - x5) * curve1);//xv5
var yv13 = y7;//yv5
var xv14 = x7 - height / 5;//xv6
var yv14 = yv11;//yv6
var xv15 = x8;//xv7
var yv15 = yv10;
var xv16 = x5 - ((x6 - x5) * curve1);
var yv16 = yv9;

context.save();
context.scale(5,5);
context.translate(50, 50);
context.rotate(60*Math.PI/180); 

context.beginPath();
context.fillStyle = "black";
context.moveTo(x1,y1); //0,0
context.bezierCurveTo(xv1,yv1,xv2,yv2,x2,y2);
context.bezierCurveTo(xv3,yv3,xv4,yv4,x3,y3);
context.bezierCurveTo(xv5,yv5,xv6,yv6,x4,y4);
context.bezierCurveTo(xv7,yv7,xv8,yv8,x1,y1);
context.closePath();
context.fill();
//context.clip()??? ---- HELP HERE!

context.beginPath();
context.fillStyle = "white";
context.moveTo(x5,y5); //0,0
context.bezierCurveTo(xv9,yv9,xv10,yv10,x6,y6);
context.bezierCurveTo(xv11,yv11,xv12,yv12,x7,y7);
context.bezierCurveTo(xv13,yv13,xv14,yv14,x8,y8);
context.bezierCurveTo(xv15,yv15,xv16,yv16,x5,y5);
context.closePath();
context.fill();//doesn't really work for my purposes

Solution

  • You just need to reverse the second path. Also don't create a new path with beginPath between the two paths.

    Example uses your code with modifications to create a clip area. I then draw a large rectangle over the top of it to show what the clip shape is.

    Also you should use arrays when you have large sets of numbers. Will save you a lot of typing.

    var context = canvas.getContext("2d");
    //parameters of the ovals;
    
    var height = 20;
    var curve1 = 0.56;
    var curve2 = 0.44;
    var width_ratio = 0.38;
    var x_pos = 0;
    var y_pos = 0;var x1 = x_pos;
    var y1 = y_pos - height/2;
    var x2 = x_pos + height * width_ratio;
    var y2 = y_pos;
    var x3 = x_pos;
    var y3 = y_pos + height/2;
    var x4 = x_pos - height * width_ratio;
    var y4 = y_pos;
    
    var xv1 = x1 + ((x2 - x1) * curve1);
    var yv1 = y1;
    var xv2 = x2;
    var yv2 = y1 + ((y2 - y1) * curve2);
    var xv3 = x2;
    var yv3 = y2 + (height/2 - (height/2 * curve2));
    var xv4 = xv1;
    var yv4 = y3;
    var xv5 = x3 - ((x2 - x1) * curve1);
    var yv5 = y3;
    var xv6 = x4;
    var yv6 = yv3;
    var xv7 = x4;
    var yv7 = y4 - (height/2 - (height/2 * curve2));
    var xv8 = x1 - ((x2 - x1) * curve1);
    var yv8 = y1;
    
    var x5 = x1;
    var y5 = y1 + height / 10;
    var x6 = x2 - height / 5;//x2
    var y6 = y2;//y2
    var x7 = x5;//x3
    var y7 = y3 - height / 10;//y3
    var x8 = x4 + height / 5;
    var y8 = y4;//y4
    
    
    var xv9 = x5 + ((x6 - x5) * curve1);//xv1
    var yv9 = y5;//yv1
    var xv10 = x6;//xv2
    var yv10 = y5 + ((y6 - y5) * curve2);//yv2 
    var xv11 =  x6;//xv3
    var yv11 = y7 - ((y7 - y6) * curve2);//yv3
    var xv12 = xv9;//xv4
    var yv12 = y7;//yv4
    var xv13 = x5 - ((x6 - x5) * curve1);//xv5
    var yv13 = y7;//yv5
    var xv14 = x7 - height / 5;//xv6
    var yv14 = yv11;//yv6
    var xv15 = x8;//xv7
    var yv15 = yv10;
    var xv16 = x5 - ((x6 - x5) * curve1);
    var yv16 = yv9;
    
    context.save();
    context.scale(5,5);
    context.translate(50, 50);
    context.rotate(60*Math.PI/180); 
    
    context.beginPath();
    context.fillStyle = "black";
    context.moveTo(x1,y1); //0,0
    context.bezierCurveTo(xv1,yv1,xv2,yv2,x2,y2);
    context.bezierCurveTo(xv3,yv3,xv4,yv4,x3,y3);
    context.bezierCurveTo(xv5,yv5,xv6,yv6,x4,y4);
    context.bezierCurveTo(xv7,yv7,xv8,yv8,x1,y1);
    
    
    context.moveTo(x5,y5); //0,0
    
    context.bezierCurveTo(xv16,yv16,xv15,yv15,x8,y8);
    context.bezierCurveTo(xv14,yv14,xv13,yv13,x7,y7);
    context.bezierCurveTo(xv12,yv12,xv11,yv11,x6,y6);
    context.bezierCurveTo(xv10,yv10,xv9,yv9,x5,y5);
    
    context.clip();
    //context.setTransform(1,0,0,1,0,0)
    context.fillRect(-500,-500,1024,1024);
    <canvas id = "canvas" width = 1024 height = 1024></canvas>