Canvas 实现樱花特效
樱花
搬运一下老文。这是基于 React + Canvas 画的一朵樱花。
canvas
首先需要了解一些 canvas 的概念。使用 <canvas></canvas>
会创建一块画布,我们可以在这个上面绘制内容。
var canvas = document.getElementById('tutorial');
//获得 2d 上下文对象
var ctx = canvas.getContext('2d');
绘制路径
一般来说,canvas 创建的画布以左上角作为原点(0, 0)。
我们使用 beginPath
来创建一条路径。然后用 moveTo
移动到起始点坐标,用 closePath
闭合路径。
可以使用 stroke
来绘制图形轮廓,用 fill
来绘制填充内容。
function draw(){
var canvas = document.getElementById('tutorial');
if (!canvas.getContext) return;
var ctx = canvas.getContext("2d");
ctx.beginPath(); //新建一条path
ctx.moveTo(50, 50); //把画笔移动到指定的坐标
ctx.lineTo(200, 50); //绘制一条从当前位置到指定坐标(200, 50)的直线.
//闭合路径。会拉一条从当前点到path起始点的直线。如果当前点与起始点重合,则什么都不做
ctx.closePath();
ctx.stroke(); //绘制路径。
}
draw();
绘制圆形
可以通过 arc
来绘制一个圆形,它接受四个参数,分别是圆形坐标、半径、开始弧度、结束弧度、顺逆时针。
Math.PI
就是数学上的圆周率π,一般是 3.1415926...
function draw(){
var canvas = document.getElementById('tutorial');
if (!canvas.getContext) return;
var ctx = canvas.getContext("2d");
ctx.beginPath();
ctx.arc(50, 50, 40, 0, Math.PI * 2, false);
ctx.stroke();
}
draw();
弧度
弧度(rad)是数学上面的概念,一般是指从圆心拉了两条半径,这俩半径中间的圆弧,如果它的长度和半径相等,那么这个角度就是一弧度。
一般来说,一个圆有 2 * π
个弧度,也是因为圆周长是 2πR
。
const rad = 180 / π
cos 和 sin
以前初中就学过这俩知识,对于一个直角三角形来说,cos 就是较长的直角边除以斜边,sin 则是较短的直角边除以斜边。
在 JavaScript 里面会接收弧度作为参数,所以需要手动转换度数为弧度。
const cos = Math.cos(2 * rad)
const sin = Math.sin(2 * rad)
贝塞尔曲线
一般我们绘制贝塞尔曲线都是用的二次贝塞尔曲线,它有一个起始点、控制点、结束点三个坐标来决定的。
感兴趣的可以看一下这篇文章:怎么理解贝塞尔曲线?
在 canvas 里面也提供了 quadraticCurveTo(cp1x, cp1y, x, y)
方法来绘制曲线。
开始绘制
了解完上面的知识后,开始绘制我们的樱花。首先要知道,樱花包含花瓣和花蕊两部分,花蕊在花瓣正中间。
我们考虑用粉红色来绘制花瓣,用白色绘制花蕊。
樱花有五瓣,所以一瓣的夹角是 75°,也就是 75 / rad
弧度。
首先我们需要声明一个樱花类,它有半径、圆心坐标、颜色等属性。接着开始绘制。
class Flower {
r = r;
color = color;
cx = 800;
cy = 500;
}
花瓣
绘制最麻烦的一步就是花瓣的弧度,这是个贝塞尔曲线。观察图片,我们可以以花瓣凹进去的三角形(剪刀形状)到圆心距离作为半径,以剪刀两边的作为一个贝塞尔曲线的控制点。
那么这个控制点的坐标是什么呢?如上图所示,其实我们的控制点p1在分割线上,和原点距离是半径的长度,而终点在p2上面,长度大概是半径的1.2-1.4倍。p0p1 和 p0p2 大概构成了 25 °的角。
所以这里也很容易进行计算。首先计算出控制点 p1 的位置,肯定是 cx + R * Math.cos(a * part / rad)
,这里的 a 就是循环生成的,a * part / rad
就是指的是第几瓣的角度。
const x0 = cx + R * Math.cos((a * part) / rad);
const y0 = cy + R * Math.sin((a * part) / rad);
然后我们找到 1/3 (25°)的坐标。设置 R1 为 1.3 倍半径。
const x1 = cx + R1 * Math.cos((a * part + 2 * part / 6) / rad);
const y1 = cy + R1 * Math.sin((a * part + 2 * part / 6) / rad);
这样关键的两个点就画了出来,接着生成贝塞尔曲线。
ctx.moveTo(cx, cy);
ctx.quadraticCurveTo(x0, y0, x1, y1);
然后我们绘制出剩下的一半花瓣。最终代码如下:
const x0 = cx + R * Math.cos((a * part) / rad);
const y0 = cy + R * Math.sin((a * part) / rad);
const x1 = cx + R1 * Math.cos((a * part + 2 * part / 6) / rad);
const y1 = cy + R1 * Math.sin((a * part + 2 * part / 6) / rad);
// 这个点其实在中点,也就是 37.5°的地方
const x2 = cx + R * Math.cos((a * part + 3 * part / 6) / rad);
const y2 = cy + R * Math.sin((a * part + 3 * part / 6) / rad);
const x3 = cx + R1 * Math.cos((a * part + 4 * part / 6) / rad);
const y3 = cy + R1 * Math.sin((a * part + 4 * part / 6) / rad);
const x4 = cx + R * Math.cos((a * part + part) / rad);
const y4 = cy + R * Math.sin((a * part + part) / rad);
// petal
ctx.beginPath();
ctx.moveTo(cx, cy);
ctx.quadraticCurveTo(x0, y0, x1, y1);
ctx.lineTo(x2, y2);
ctx.lineTo(x3, y3);
ctx.quadraticCurveTo(x4, y4, cx, cy);
ctx.fill();
ctx.stroke();
花蕊
接着绘制花蕊,其实花蕊很容易绘制,因为它们分别处于 1/3、1/2、2/3 处。
const ax0 = cx + R / 3 * Math.cos((a * part + 2 * part / 6) / rad);
const ay0 = cy + R / 3 * Math.sin((a * part + 2 * part / 6) / rad);
const ax1 = cx + R / 2 * Math.cos((a * part + 3 * part / 6) / rad);
const ay1 = cy + R / 2 * Math.sin((a * part + 3 * part / 6) / rad);
const ax2 = cx + R / 3 * Math.cos((a * part + 4 * part / 6) / rad);
const ay2 = cy + R / 3 * Math.sin((a * part + 4 * part / 6) / rad);
这几个坐标点都找好了,但是不要忘了在终点绘制一个小圆点,这个更像花蕊上面的蕊头。
ctx.arc(ax0, ay0, 2, 0, 2 * Math.PI)
最终的代码如下:
const { ctx, cx, cy, r: R } = this
ctx.save();
ctx.strokeStyle = "#fff";
const ax0 = cx + R / 3 * Math.cos((a * part + 2 * part / 6) / rad);
const ay0 = cy + R / 3 * Math.sin((a * part + 2 * part / 6) / rad);
const ax1 = cx + R / 2 * Math.cos((a * part + 3 * part / 6) / rad);
const ay1 = cy + R / 2 * Math.sin((a * part + 3 * part / 6) / rad);
const ax2 = cx + R / 3 * Math.cos((a * part + 4 * part / 6) / rad);
const ay2 = cy + R / 3 * Math.sin((a * part + 4 * part / 6) / rad);
let ary = []
// 如果半径大于40
if (R > 40) {
ary = [{
x: ax0,
y: ay0
}, {
x: ax1,
y: ay1
}, {
x: ax2,
y: ay2
}];
} else {
ary = [{
x: ax1,
y: ay1
}];
}
ctx.beginPath();
for (let i = 0; i < ary.length; i++) {
ctx.moveTo(cx, cy);
ctx.lineTo(ary[i].x, ary[i].y);
ctx.arc(ary[i].x, ary[i].y, 2, 0, 2 * Math.PI)
}
ctx.stroke();
ctx.restore();
总结
最后,我把这个项目部署到了线上,可以访问 sakura.gyyin.top 来访问到。
欢迎关注我的 Github:github.com/yinguangyao…
转载自:https://juejin.cn/post/7039379088892493837