The question as it stands is a bit unclear about requirements. Here is in any case an approach that does not require much calculations, but takes advantage of draw operations to visualize about the same as shown in the codepen.
The main steps are:
- At an off-screen canvas:
- Define a line thickness with radius set to the green area
- Define round caps for the line
- Draw the Bezier line with solid color
- Draw the result into main canvas with various offsets relative to the thickness of the blue line.
- Clear the center and you will have the blue outline
- Implement a manual Bezier so you can draw the green arc/ellipse at any point within that shape
Radius/diameter can be expanded. If you need variable radius you can just use the Bezier formula to plot a series of blue arcs on top of each other instead.
Proof-of-concept
This will show the process step-by-step.
Step 1
On an off-screen canvas (shown on-screen here for demo, we'll switch in the next step):

var c = document.querySelector("canvas"),
ctx = c.getContext("2d"),
dia = 90; // diameter of graphics
ctx.strokeStyle = "blue"; // color
ctx.lineWidth = dia; // line-width = dia
ctx.lineCap = "round"; // round caps
// draw bezier (quadratic, one control point)
ctx.moveTo(dia, dia);
ctx.quadraticCurveTo(300, 230, c.width - dia, dia);
ctx.stroke();
<canvas width=600 height=300></canvas>
Done. We now have the main shape. Adjust points as needed.
Step 2
As we now have the main shape we will create the outline using this shape:
- Draw this to main canvas offset it circle (f.ex. 8 position around main area)
- Knock out the center using comp. mode "destination-out" to leave only the outline

var c = document.querySelector("canvas"),
co = document.createElement("canvas"),
ctx = c.getContext("2d"),
ctxo = co.getContext("2d"),
dia = 90;
co.width = c.width;
co.height = c.height;
ctxo.strokeStyle = "blue";
ctxo.lineWidth = dia;
ctxo.lineCap = "round";
// draw bezier (quadratic here, one control point)
ctxo.moveTo(dia, dia);
ctxo.quadraticCurveTo(300, 230, c.width - dia, dia);
ctxo.stroke();
// draw multiple times to main canvas
var thickness = 1, angle = 0, step = Math.PI * 0.25;
for(; angle < Math.PI * 2; angle += step) {
var x = thickness * Math.cos(angle),
y = thickness * Math.sin(angle);
ctx.drawImage(co, x, y);
}
// punch out center
ctx.globalCompositeOperation = "destination-out";
ctx.drawImage(co, 0, 0);
<canvas width=600 height=300></canvas>
Step 3
Plot the green circle using a custom implementation of the canvas. First we back up a copy of the resulting blue outline so we can redraw it on top of the free circle. We can reuse out off-line canvas for that, just clear it and draw back the result (reset transforms):
The only calculation we need from here is for the quadratic Bezier where we supply t
in the range [0, 1] to get a point:
function getQuadraticPoint(z0x, z0y, cx, cy, z1x, z1y, t) {
var t1 = (1 - t), // (1 - t)
t12 = t1 * t1, // (1 - t) ^ 2
t2 = t * t, // t ^ 2
t21tt = 2 * t1 * t; // 2(1-t)t
return {
x: t12 * z0x + t21tt * cx + t2 * z1x,
y: t12 * z0y + t21tt * cy + t2 * z1y
}
}
The result will be (using values closer to original codepen):

var c = document.querySelector("canvas"),
co = document.createElement("canvas"),
ctx = c.getContext("2d"),
ctxo = co.getContext("2d"),
radius = 150,
dia = radius * 2;
co.width = c.width;
co.height = c.height;
ctxo.translate(2,2); // to avoid clipping of edges in this demo
ctxo.strokeStyle = "blue";
ctxo.lineWidth = dia;
ctxo.lineCap = "round";
// draw bezier (quadratic here, one control point)
ctxo.moveTo(radius, radius);
ctxo.quadraticCurveTo(300, 230, c.width - radius - 6, radius);
ctxo.stroke();
// draw multiple times to main canvas
var thickness = 1, angle = 0, step = Math.PI * 0.25;
for(; angle < Math.PI * 2; angle += step) {
var x = thickness * Math.cos(angle),
y = thickness * Math.sin(angle);
ctx.drawImage(co, x, y);
}
// punch out center
ctx.globalCompositeOperation = "destination-out";
ctx.drawImage(co, 0, 0);
// back-up result by reusing off-screen canvas
ctxo.clearRect(0, 0, co.width, co.height);
ctxo.drawImage(c, 0, 0);
// Step 3: draw the green circle at any point
ctx.globalCompositeOperation = "source-over"; // normal comp. mode
ctx.fillStyle = "#9f9";
ctx.strokeStyle = "#090";
var t = 0, dlt = 0.01;
(function loop(){
ctx.clearRect(0, 0, c.width, c.height);
t += dlt;
// calc position based on t [0, 1] and the same points as for the blue
var pos = getQuadraticPoint(radius, radius, 300, 230, c.width - radius, radius, t);
// draw the arc
ctx.beginPath();
ctx.arc(pos.x + 2, pos.y + 2, radius, 0, 2*Math.PI);
ctx.fill();
// draw center line
ctx.beginPath();
ctx.moveTo(radius, radius);
ctx.quadraticCurveTo(300, 230, c.width - radius - 6, radius);
ctx.stroke();
// draw blue outline on top
ctx.drawImage(co, 0, 0);
if (t <0 || t >= 1) dlt = -dlt; // ping-pong for demo
requestAnimationFrame(loop);
})();
// formula for quadr. curve is: B(t) = (1-t)^2 * Z0 + 2(1-t)t * C + t^2 * Z1
function getQuadraticPoint(z0x, z0y, cx, cy, z1x, z1y, t) {
var t1 = (1 - t), // (1 - t)
t12 = t1 * t1, // (1 - t) ^ 2
t2 = t * t, // t ^ 2
t21tt = 2 * t1 * t; // 2(1-t)t
return {
x: t12 * z0x + t21tt * cx + t2 * z1x,
y: t12 * z0y + t21tt * cy + t2 * z1y
}
}
<canvas width=600 height=600></canvas>
Example using non 1:1 axis by scale:
var c = document.querySelector("canvas"),
co = document.createElement("canvas"),
ctx = c.getContext("2d"),
ctxo = co.getContext("2d"),
radius = 150,
dia = radius * 2;
co.width = c.width;
co.height = c.height;
ctxo.translate(2,2); // to avoid clipping of edges in this demo
ctxo.strokeStyle = "blue";
ctxo.lineWidth = dia;
ctxo.lineCap = "round";
// draw bezier (quadratic here, one control point)
ctxo.moveTo(radius, radius);
ctxo.quadraticCurveTo(300, 230, c.width - radius - 6, radius);
ctxo.stroke();
// draw multiple times to main canvas
var thickness = 2, angle = 0, step = Math.PI * 0.25;
for(; angle < Math.PI * 2; angle += step) {
var x = thickness * Math.cos(angle),
y = thickness * Math.sin(angle);
ctx.drawImage(co, x, y);
}
// punch out center
ctx.globalCompositeOperation = "destination-out";
ctx.drawImage(co, 0, 0);
// back-up result by reusing off-screen canvas
ctxo.setTransform(1,0,0,1,0,0); // remove scale
ctxo.clearRect(0, 0, co.width, co.height);
ctxo.drawImage(c, 0, 0);
// Step 3: draw the green circle at any point
ctx.globalCompositeOperation = "source-over"; // normal comp. mode
ctx.fillStyle = "#9f9";
ctx.strokeStyle = "#090";
ctx.scale(1, 0.4); // create ellipse
var t = 0, dlt = 0.01;
(function loop(){
ctx.clearRect(0, 0, c.width, c.height * 1 / 0.4);
t += dlt;
// calc position based on t [0, 1] and the same points as for the blue
var pos = getQuadraticPoint(radius, radius, 300, 230, c.width - radius, radius, t);
// draw the arc
ctx.beginPath();
ctx.arc(pos.x + 2, pos.y + 2, radius, 0, 2*Math.PI);
ctx.fill();
// draw center line
ctx.beginPath();
ctx.moveTo(radius, radius);
ctx.quadraticCurveTo(300, 230, c.width - radius - 6, radius);
ctx.stroke();
// draw blue outline on top
ctx.drawImage(co, 0, 0);
if (t <0 || t >= 1) dlt = -dlt; // ping-pong for demo
requestAnimationFrame(loop);
})();
// formula for quadr. curve is: B(t) = (1-t)^2 * Z0 + 2(1-t)t * C + t^2 * Z1
function getQuadraticPoint(z0x, z0y, cx, cy, z1x, z1y, t) {
var t1 = (1 - t), // (1 - t)
t12 = t1 * t1, // (1 - t) ^ 2
t2 = t * t, // t ^ 2
t21tt = 2 * t1 * t; // 2(1-t)t
return {
x: t12 * z0x + t21tt * cx + t2 * z1x,
y: t12 * z0y + t21tt * cy + t2 * z1y
}
}
<canvas width=600 height=600></canvas>