This question has sort of been answered here: Combined area of overlapping circles
My problem is more specific though. I have an arbitrary number of arbitrarily sized ci
It is possible to do natively without using pixel manipulation or any library.
Provided the transparency is the same for all the circles it is pretty straight forward.
Circles on top of some random background
What you need to do is to:
Your circle function can look something like this:
function drawCircle(x, y, r, step) {
ctx.beginPath();
switch (step) {
case 0: // step 0, outer circle red
ctx.fillStyle = '#f00';
break;
case 1: // step 1, middle circle blue
ctx.fillStyle = '#00f';
r *= 0.67;
break;
case 2: // step 2, inner circle green
ctx.fillStyle = '#0f0';
r *= 0.33;
break;
}
ctx.arc(x, y, r, 0, 2 * Math.PI);
ctx.fill();
}
The function takes the x
and y
center point as well as radius
. But in addition it takes a step
value between 0 and 2 which determines which surface is being drawn. This will be important in the next steps.
First we can define an array holding all the circles we want to draw:
var circs = [
//x y r dx dy (the last two for animation only)
[100, 100, 50, 2, 1],
[200, 200, 50, -2, -3],
[150, 50, 50, 3, -1]
];
From here you would drag them, offset x and y and then redraw them, but for demo's sake I'll animate them.
Before we draw we set the global alpha on the main canvas (the off-screen is kept solid):
mctx.globalAlpha = 0.7; // main canvas
And the animation loop:
function start() {
// clear off-screen canvas
ctx.clearRect(0,0, w, h);
// clear main canvas
mctx.clearRect(0,0, w, h);
var t = 0, i, c;
// outer step loop
for(; t < 3; t++) {
// draw all circles at current step
for(i = 0; c = circs[i]; i++) {
drawCircle(c[0], c[1], c[2], t);
}
}
// re-position circles for animation
for(i = 0;c = circs[i]; i++) {
c[0] += c[3]; /// add delta to x
c[1] += c[4]; /// add delta to y
// reverse deltas if at boundaries
if (c[0] < 0 || c[0] > w) c[3] = -c[3];
if (c[1] < 0 || c[1] > h) c[4] = -c[4];
}
// draw off-screen to main canvas
mctx.drawImage(ocanvas, 0, 0);
// loop animation
requestAnimationFrame(start);
}
The global alpha can be reset for each operation in case you want to draw other elements to the canvas - or use a second on-screen canvas to hold static content.
var demo = document.getElementById("demo");
var w = demo.width, h = demo.height;
var ocanvas = document.createElement('canvas');
ocanvas.width = w;
ocanvas.height = h;
var ctx = ocanvas.getContext('2d');
var mctx = demo.getContext('2d');
var img = document.createElement('img')
img.onload = start;
img.src = 'http://i.imgur.com/CHPdL2y.png';
/// key to it all
mctx.globalAlpha = 0.7;
var circs = [
//x y r dx dy
[100, 100, 50, 2 , 1.5],
[200, 200, 70, -2 , -3],
[150, 50, 50, 3 , -1],
[150, 50, 30, 4 , 4],
[150, 50, 20, -3 , -2],
[100, 100, 55, 2.5, 2.5],
[200, 200, 75, -1 , -2.5],
[150, 50, 45, 3.5, -2],
[150, 50, 35, 5 , 2],
[150, 50, 25, -1.2, -5]
];
function drawCircle(x, y, r, step) {
ctx.beginPath();
switch (step) {
case 0:
ctx.fillStyle = '#f00';
break;
case 1:
ctx.fillStyle = '#00f';
r *= 0.67;
break;
case 2:
ctx.fillStyle = '#0f0';
r *= 0.33;
break;
}
ctx.arc(x, y, r, 0, 2 * Math.PI);
ctx.fill();
}
function start() {
ctx.clearRect(0, 0, w, h);
mctx.clearRect(0, 0, w, h);
var i = 0, t, c;
for(t = 0; t < 3; t++) {
for(i = 0; c = circs[i]; i++) {
drawCircle(c[0], c[1], c[2], t);
}
}
for(i = 0;c = circs[i]; i++) {
c[0] += c[3];
c[1] += c[4];
if (c[0] < 0 || c[0] > w) c[3] = -c[3];
if (c[1] < 0 || c[1] > h) c[4] = -c[4];
}
mctx.drawImage(ocanvas, 0, 0);
requestAnimationFrame(start);
}
body {
margin:0;
background:url(//i.stack.imgur.com/b8eCZ.jpg) no-repeat;
}
<canvas id="demo" width="500" height="333"></canvas>
You must use a library like RaphaelJS. It is capable of more than what you ask for here.
Edit: You can apply alpha transparency through js by programmatically determining the distance between centres (D) and comparing it with the sum of the radii (S). If D < S, then they overlap so take care of red circles.
(http://raphaeljs.com/)