How can we animate a closed parabolic bezier curve based on the progress of a certain event?
More Context ::
PS: There are other SO questions which show how to build a circular progress bar but what I'm looking at is a parabolic progress bar.
You can fake filling any SVG curve in canvas with a bit of a mix of SVG and Canvas to calculate some things:
const path = document.querySelector( '#computing-path' );
const canvas = document.querySelector( 'canvas' );
const progress = document.querySelector( '#progress' );
const bezierPoints = [
[50, 50],
[0, 30],
[20, 180],
[180, 150]
];
const ctx = canvas.getContext( '2d' );
/* We need to set the SVG path to compute its length */
path.setAttribute( 'd', `M${bezierPoints[0].join()} C ${bezierPoints[1].join()} ${bezierPoints[2].join()} ${bezierPoints[3].join()}` );
const pathLength = path.getTotalLength();
/* `p` will be a normalised value between 0 and 1 */
function draw( p = 0 ){
ctx.clearRect( 0, 0, canvas.width, canvas.height );
if( p <= 0 ) return;
ctx.save();
ctx.lineWidth = 10;
ctx.strokeStyle = 'white';
/** This is the crucial bit - the second value is very large because we want the empty space of the dash to stretch to at least the end. */
ctx.setLineDash([ p * pathLength, pathLength * 10 ]);
ctx.beginPath();
ctx.moveTo( ...bezierPoints[0] );
ctx.bezierCurveTo(
...bezierPoints[1],
...bezierPoints[2],
...bezierPoints[3]
);
ctx.stroke();
ctx.restore();
}
draw( progress.value );
progress.addEventListener( 'input', event => draw( progress.value ));
html, body { height: 100%; }
body { background: gray; display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 20px; }
canvas { border: 1px solid white; }
<svg xmlns="http://www.w3.org/2000/svg" style="visibility:hidden;position:fixed;top:0;left:0;transform:translate(-100%,-100%)"><path id="computing-path" /></svg>
<canvas width="200" height="200"></canvas>
<input id="progress" type="range" min="0" max="1" value=".5" step=".01" />