如何基于事件进度实现自定义闭合抛物线贝塞尔曲线动画
基于HTML5 Canvas的抛物线形态进度条实现方案
核心实现逻辑
你可以通过二次贝塞尔曲线拟合抛物线,再按进度截断路径的方式完成需求,整体步骤如下:
- 预定义100%进度下的曲线参数
单条抛物线可以用二次贝塞尔曲线完美拟合,只需要2个端点+1个控制点即可:- 两条抛物线共享左右两个端点
P0(x1,y1)和P2(x2,y2) - 上抛物线的控制点设为
controlTop(cx, cyTop),下抛物线的控制点设为controlBottom(cx, cyBottom),cx取两个端点的中点即可得到对称的抛物线,也可根据视觉需求自定义位置
100%进度下的闭合路径就是从P0出发用上控制点画曲线到P2,再从P2出发用下控制点画曲线回到P0后填充。
- 两条抛物线共享左右两个端点
- 按进度截断贝塞尔曲线
使用德卡斯特里奥算法(De Casteljau's Algorithm)计算指定进度t(0≤t≤1,10%进度对应t=0.1)下的截断点和截断后曲线的控制点:
对任意二次贝塞尔曲线,给定端点P0、P2,控制点P1,进度t对应的截断后前半段曲线参数为:新控制点P01 = {x: P0.x + t*(P1.x - P0.x), y: P0.y + t*(P1.y - P0.y)} 截断点Pt = {x: P01.x + t*( (P1.x + t*(P2.x - P1.x)) - P01.x ), y: P01.y + t*( (P1.y + t*(P2.y - P1.y)) - P01.y )} - 拼接对应进度的闭合路径
用进度t分别计算上、下两条抛物线的截断参数,按如下规则拼接路径即可:- 从P0出发,用截断后的上抛物线参数画到截断点Pt
- 从Pt出发,用截断后的下抛物线参数反向画回P0
- 填充/描边路径即可得到对应进度的闭合抛物线效果
可运行核心代码示例
const canvas = document.getElementById('canvas'); const ctx = canvas.getContext('2d'); // 自定义完整抛物线的坐标参数 const leftPoint = {x: 100, y: 200}; // 左共享端点 const rightPoint = {x: 500, y: 200}; // 右共享端点 const topControl = {x: 300, y: 60}; // 上抛物线控制点 const bottomControl = {x: 300, y: 340}; // 下抛物线控制点 // 计算指定进度下截断后的二次贝塞尔曲线参数 function getTruncatedQuadratic(p0, p1, p2, t) { const p01 = {x: p0.x + t * (p1.x - p0.x), y: p0.y + t * (p1.y - p0.y)}; const p12 = {x: p1.x + t * (p2.x - p1.x), y: p1.y + t * (p2.y - p1.y)}; const cutPoint = {x: p01.x + t * (p12.x - p01.x), y: p01.y + t * (p12.y - p01.y)}; return {start: p0, control: p01, end: cutPoint}; } // 绘制指定进度的闭合抛物线进度条 function drawParabolaProgress(progress) { const t = Math.max(0, Math.min(1, progress / 100)); // 把0-100的进度转为0-1的参数 ctx.clearRect(0, 0, canvas.width, canvas.height); // 分别获取上下两条抛物线的截断参数 const topTruncated = getTruncatedQuadratic(leftPoint, topControl, rightPoint, t); const bottomTruncated = getTruncatedQuadratic(leftPoint, bottomControl, rightPoint, t); ctx.beginPath(); // 画上半段曲线 ctx.moveTo(topTruncated.start.x, topTruncated.start.y); ctx.quadraticCurveTo(topTruncated.control.x, topTruncated.control.y, topTruncated.end.x, topTruncated.end.y); // 画下半段曲线闭合 ctx.quadraticCurveTo(bottomTruncated.control.x, bottomTruncated.control.y, bottomTruncated.start.x, bottomTruncated.start.y); // 自定义样式 ctx.fillStyle = '#409eff'; ctx.fill(); ctx.strokeStyle = '#1d64c7'; ctx.lineWidth = 2; ctx.stroke(); } // 测试绘制10%进度 drawParabolaProgress(10); // 动画效果直接用requestAnimationFrame动态修改传入的progress参数即可
内容的提问来源于stack exchange,提问作者Rishabh.IO
相关产品推荐
相关产品推荐

