zmanc zmanc - 7 months ago 8
HTML Question

Drawing a curved line in CSS or canvas, and moving circle along it

I was given a design today that is a circle moving along a curved line. I created a JSBin with the progress I have made so far with pure css but I feel I'm on the wrong direction. I think maybe this would be better done with canvas but I'm not sure where to begin. This is not just drawing along a line its also filling the bars.

Fiddle

Here is the design:

enter image description here

Here is how close I have got so far with CSS:

enter image description here

Answer

Here's how to animate your circle along your curved line (which is a Cubic Bezier Curve).

  • Draw your curve using canvas's context.bezierCurveTo method.

  • Close your rainbow path using a series of canvas's context.lineTo method.

  • To fill only the curved path with your rainbow colors, you can use context.clip to restrict drawings to display only inside the path. Then you can use context.fillRect to fill with your multi-colored bands.

  • Use requestAnimationFrame to create an animation loop that draws your ball at increasing waypoints along your curve.

  • Calculate waypoints along your curve using De Casteljau's Algorithm

Here's example code and a Demo:

var canvas=document.getElementById("canvas");
var ctx=canvas.getContext("2d");
var cw=canvas.width;
var ch=canvas.height;
   
var colors=[[229,133,50],[251,183,50],[133,206,63],[22,155,116],[26,160,219]];
var points=[35,120,317,511,709,792];
var p0={x:37,y:144};
var p1={x:267,y:143};
var p2={x:651,y:129};
var p3={x:794,y:96};
var waypoints=cubicBezierPoints(p0,p1,p2,p3);
var currentIndex=0;
var radius=10;
//
requestAnimationFrame(animate);

// draw the rainbow curve thing
function drawCurve(){
    ctx.save();
    ctx.moveTo(37,144);
    ctx.bezierCurveTo(267,143,651,129,794,96);
    ctx.lineTo(794,158);
    ctx.lineTo(37,158);
    ctx.closePath();
    ctx.fill(); 
    ctx.globalCompositeOperation='source-atop';
    for(var i=0;i<points.length-1;i++){
        var c=colors[i];
        ctx.fillStyle='rgb('+c[0]+','+c[1]+','+c[2]+')';
        ctx.fillRect(points[i],0,points[i+1],ch);
    }
    ctx.restore();    
}
//
function drawBall(){
    var pt=waypoints[currentIndex];
    ctx.beginPath();
    ctx.arc(pt.x,pt.y,radius,0,Math.PI*2);
    ctx.fillStyle='white';
    ctx.fill();
    ctx.strokeStyle='black'
    ctx.lineWidth=3;
    ctx.stroke();
}

// the animation loop
function animate(){
    ctx.clearRect(0,0,cw,ch);
    drawCurve();
    drawBall();
    ctx.beginPath();
    currentIndex++;
    if(currentIndex<waypoints.length){
        requestAnimationFrame(animate);
    }
}

// calculate the waypoints
function cubicBezierPoints(p0,p1,p2,p3){
    var ticksPerSecond=60;
    var seconds=4;
    var totalTicks=ticksPerSecond*seconds;
    var pts=[];
    for(var t=0;t<totalTicks;t++){
        pts.push(getCubicBezierXYatT(p0,p1,p2,p3,t/totalTicks));
    }
    return(pts);
}

// De Casteljau's algorithm which calculates points along a cubic Bezier curve
// plot a point at interval T along a bezier curve
// T==0.00 at beginning of curve. T==1.00 at ending of curve
// Calculating 100 T's between 0-1 will usually define the curve sufficiently
function getCubicBezierXYatT(startPt,controlPt1,controlPt2,endPt,T){
    var x=CubicN(T,startPt.x,controlPt1.x,controlPt2.x,endPt.x);
    var y=CubicN(T,startPt.y,controlPt1.y,controlPt2.y,endPt.y);
    return({x:x,y:y});
}
// cubic helper formula at T distance
function CubicN(T, a,b,c,d) {
    var t2 = T * T;
    var t3 = t2 * T;
    return a + (-a * 3 + T * (3 * a - a * T)) * T
    + (3 * b + T * (-6 * b + b * 3 * T)) * T
    + (c * 3 - c * 3 * T) * t2
    + d * t3;
}
body{ background-color: ivory; }
#canvas{border:1px solid red; margin:0 auto; }
<canvas id="canvas" width=820 height=200></canvas>