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.
Solution
![snapshot]()
Circles on top of some random background
What you need to do is to:
- Allocate an off-screen canvas where circles are drawn as solids (no transparency)
- Draw the circles in three steps.
- All the circles' red surface first, then all the circles' blue surface and so on.
- Set global alpha for transparency for main (visible) canvas
- Clear both canvases
- Draw off-screen canvas to main canvas
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.
Demo
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>