Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

Tags:

html

css

canvas

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

like image 859
zmanc Avatar asked Dec 07 '22 22:12

zmanc


2 Answers

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>
like image 172
markE Avatar answered Dec 10 '22 11:12

markE


Great answer by MarkE (and he deserves the bounty) but when I saw the De Casteljau's algorithm and had a close look it struck me as a mathematician writing software, not a programmer doing math.

Using the passed arguments as intermediates in the calculation there are a few operations that can be dropped thus improving the algorithm. It is the same math function diverging by no more than +/- 1e-14 (which in Javascript floating point is as close as it gets)

For want of a better name cubicQ

function cubicQ(t, a, b, c, d) {
    a += (b - a) * t;
    b += (c - b) * t;
    c += (d - c) * t;
    a += (b - a) * t;
    b += (c - b) * t;
    return a + (b - a) * t; 
}

And incase there is a need for the second order polynomials required by the ctx.quadraticCurveTo

function quadQ(t, a, b, c){
    a += (b - a) * t;
    b += (c - b) * t;
    return a + (b - a) * t;
}

Where a,b,c are the x or y points on the curve with b the control point. t is the position 0 <= t <= 1

And just for the interest the linear version

function linearQ(t, a, b){
    return a + (b - a) * t;
}

As you can see it is just a line. The quadratic comprised of linear interpolations 3 (lines), and the cubic is 6.

For this question the 15% increase in performance is trivial and inconsequential, but for more intensive need 15% is well worth the few extra lines of code, not to mention that it just looks better.. LOL

like image 24
Blindman67 Avatar answered Dec 10 '22 10:12

Blindman67