前端:用canvas绘制一个烟花动画
关注并将「趣谈前端」设为星标
每日定时推送技术干货/优秀开源/技术思维
前言
在我们日常开发中贝塞尔曲线无处不在:
svg 中的曲线(支持 2阶、 3阶) canvas 中绘制贝塞尔曲线 几乎所有前端2D或3D图形图表库(echarts,d3,three.js)都会使用到贝塞尔曲线
所以掌握贝塞尔曲线势在必得。这篇文章主要是实战篇,不会介绍和贝塞尔相关的知识, 如果有同学对贝塞尔曲线不是很清楚的话:可以查看我往期的文章.
绘制贝塞尔曲线
第一步我们先创建ctx, 用ctx 画一个二阶贝塞尔曲线看下。二阶贝塞尔曲线有1个控制点,一个起点,一个终点。
const canvas = document.getElementById( 'canvas' );
const ctx = canvas.getContext( '2d' );
ctx.beginPath();
ctx.lineWidth = 2;
ctx.strokeStyle = '#000';
ctx.moveTo(100,100)
ctx.quadraticCurveTo(180,50, 200,200)
ctx.stroke();
这样我们就画好了一个贝塞尔曲线了。
绘制贝塞尔曲线动画
画一条线谁不会哇?接下来文章的主体内容。首先试想一下动画我们肯定一步步画出曲线?但是这个ctx给我们全部画出来了是不是有点问题。我们重新看下二阶贝塞尔曲线的实现过程动画,看看是否有思路。
从图中可以分析得出贝塞尔上的曲线是和t有关系的, t的区间是在0-1之间,我们是不是可以通过二阶贝塞尔的曲线方程去算出每一个点呢,这个专业术语叫「离散化」,但是这样的得出来的点的信息是不太准的,我们先这样实现。
先看下方程:
我们模拟写出代码如如下:
//这个就是二阶贝塞尔曲线方程
function twoBezizer(p0, p1, p2, t) {
const k = 1 - t
return k * k * p0 + 2 * (1 - t) * t * p1 + t * t * p2
}
//离散
function drawWithDiscrete(ctx, start, control, end,percent) {
for ( let t = 0; t <= percent / 100; t += 0.01 ) {
const x = twoBezizer(start[0], control[0], end[0], t)
const y = twoBezizer(start[1], control[1], end[1], t)
ctx.lineTo(x, y)
}
}
我们看下效果:
和我们画的几乎是一模一样,接下来就用requestAnimationFrame 开始我们的动画给出以下代码:
let percent = 0
function animate() {
ctx.clearRect( 0, 0, 800, 800 );
ctx.beginPath();
ctx.moveTo(100,100)
drawWithDiscrete(ctx,[100,100],[180,50],[200,200],percent)
ctx.stroke();
percent = ( percent + 1 ) % 100;
id = requestAnimationFrame(animate)
}
animate()
这里有两个要注意的是, 我是是percent 不断加1 和100 求余,所以呢 percent 会不断地从1-100 这样往复,OK所以我们必须要动画之前做一次区域清理, ctx.clearRect( 0, 0, 800, 800 ); 这样就可以不断的从开始到结束循环往复,我们看下效果:
看着样子是不是还不错哈哈哈😸。
绘制贝塞尔曲线动画方法2
你以为这样就结束了?当然不是难道我们真的没有办法画出某一个t的贝塞尔曲线了?当前不是,这里放一下二阶贝塞尔方程的推导过程:
二阶贝塞尔曲线上的任意一点,都是可以通过同样比例获得。在两点之间的任意一点,其实满足的一阶贝塞尔曲线, 一阶贝塞尔曲线满足的其实是线性变化。我给出以下方程
function oneBezizer(p0,p1,t) {
return p0 + (p1-p0) * t
}
从我画的图可以看出,我们只要 不断求A点 和C点就可以画出在某一时间段的贝塞尔了。
我给出以下代码和效果图:
function drawWithDiscrete2(ctx, start, control, end,percent) {
const t = percent/ 100;
// 求出A点
const A = [];
const C = [];
A[0] = oneBezizer(start[0],control[0],t);
A[1] = oneBezizer(start[1],control[1],t);
C[0] = twoBezizer(start[0], control[0], end[0], t)
C[1] = twoBezizer(start[1], control[1], end[1], t)
ctx.quadraticCurveTo(
A[ 0 ], A [ 1 ],
C[ 0 ], C[ 1 ]
);
}
礼花🎉动画
上文我们实现了一条贝塞尔线,我们将这条贝塞尔的曲线的开始点作为一个圆的圆心,然后按照某个次数求出不同的结束点。再写一个随机颜色,礼花效果就成了, 直接上代码,
for(let i=0; i<count; i++) {
const angle = Math.PI * 2 / count * i;
const x = center[ 0 ] + radius * Math.sin( angle );
const y = center[ 1 ] + radius * Math.cos( angle );
ctx.strokeStyle = colors[ i ];
ctx.beginPath();
drawWithDiscrete(ctx, center,[180,50],[x,y],percent)
ctx.stroke();
}
function getRandomColor(colors, count) {
// 生成随机颜色
for ( let i = 0; i < count; i++ ) {
colors.push(
'rgb( ' +
( Math.random() * 255 >> 0 ) + ',' +
( Math.random() * 255 >> 0 ) + ',' +
( Math.random() * 255 >> 0 ) +
' )'
);
}
}
我们看下动画吧:
结尾
本篇文章到这里就结束了,如果看了对你有帮助的, 欢迎点个赞👍和关注。你的支持是我持续更新的最大动力。
看视频, 涨芝士啦⬇️
❤️ 看完三件事
如果你觉得这篇内容对你挺有启发,我想邀请你帮我三个小忙:
点个【在看】,或者分享转发,让更多的人也能看到这篇内容 关注公众号【趣谈前端】,定期分享 工程化 / 可视化 / 低代码 / 优秀开源。
基于Koa + React + TS从零开发全栈文档编辑器(进阶实战)
点个在看你最好看