Open yinguangyao opened 3 years ago
这是基于 React + Canvas 画的一朵樱花。
首先需要了解一些 canvas 的概念。使用 <canvas></canvas> 会创建一块画布,我们可以在这个上面绘制内容。
<canvas></canvas>
var canvas = document.getElementById('tutorial'); //获得 2d 上下文对象 var ctx = canvas.getContext('2d');
一般来说,canvas 创建的画布以左上角作为原点(0, 0)。
我们使用 beginPath 来创建一条路径。然后用 moveTo 移动到起始点坐标,用 closePath 闭合路径。
beginPath
moveTo
closePath
可以使用 stroke 来绘制图形轮廓,用 fill 来绘制填充内容。
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...
arc
Math.PI
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。
2 * π
2πR
const rad = 180 / π
以前初中就学过这俩知识,对于一个直角三角形来说,cos 就是较长的直角边除以斜边,sin 则是较短的直角边除以斜边。
在 JavaScript 里面会接收弧度作为参数,所以需要手动转换度数为弧度。
const cos = Math.cos(2 * rad) const sin = Math.sin(2 * rad)
一般我们绘制贝塞尔曲线都是用的二次贝塞尔曲线,它有一个起始点、控制点、结束点三个坐标来决定的。
感兴趣的可以看一下这篇文章:怎么理解贝塞尔曲线?
在 canvas 里面也提供了 quadraticCurveTo(cp1x, cp1y, x, y) 方法来绘制曲线。
quadraticCurveTo(cp1x, cp1y, x, y)
了解完上面的知识后,开始绘制我们的樱花。首先要知道,樱花包含花瓣和花蕊两部分,花蕊在花瓣正中间。
我们考虑用粉红色来绘制花瓣,用白色绘制花蕊。
樱花有五瓣,所以一瓣的夹角是 75°,也就是 75 / rad 弧度。
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 就是指的是第几瓣的角度。
cx + R * Math.cos(a * part / rad)
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();
最后,我把这个项目部署到了线上,可以访问 http://sakura.gyyin.top 来访问到。
樱花
这是基于 React + Canvas 画的一朵樱花。
canvas
首先需要了解一些 canvas 的概念。使用
<canvas></canvas>
会创建一块画布,我们可以在这个上面绘制内容。绘制路径
一般来说,canvas 创建的画布以左上角作为原点(0, 0)。
我们使用
beginPath
来创建一条路径。然后用moveTo
移动到起始点坐标,用closePath
闭合路径。可以使用
stroke
来绘制图形轮廓,用fill
来绘制填充内容。绘制圆形
可以通过
arc
来绘制一个圆形,它接受四个参数,分别是圆形坐标、半径、开始弧度、结束弧度、顺逆时针。Math.PI
就是数学上的圆周率π,一般是 3.1415926...弧度
弧度(rad)是数学上面的概念,一般是指从圆心拉了两条半径,这俩半径中间的圆弧,如果它的长度和半径相等,那么这个角度就是一弧度。
一般来说,一个圆有
2 * π
个弧度,也是因为圆周长是2πR
。cos 和 sin
以前初中就学过这俩知识,对于一个直角三角形来说,cos 就是较长的直角边除以斜边,sin 则是较短的直角边除以斜边。
在 JavaScript 里面会接收弧度作为参数,所以需要手动转换度数为弧度。
贝塞尔曲线
一般我们绘制贝塞尔曲线都是用的二次贝塞尔曲线,它有一个起始点、控制点、结束点三个坐标来决定的。
感兴趣的可以看一下这篇文章:怎么理解贝塞尔曲线?
在 canvas 里面也提供了
quadraticCurveTo(cp1x, cp1y, x, y)
方法来绘制曲线。开始绘制
了解完上面的知识后,开始绘制我们的樱花。首先要知道,樱花包含花瓣和花蕊两部分,花蕊在花瓣正中间。
我们考虑用粉红色来绘制花瓣,用白色绘制花蕊。
樱花有五瓣,所以一瓣的夹角是 75°,也就是
75 / rad
弧度。首先我们需要声明一个樱花类,它有半径、圆心坐标、颜色等属性。接着开始绘制。
花瓣
绘制最麻烦的一步就是花瓣的弧度,这是个贝塞尔曲线。观察图片,我们可以以花瓣凹进去的三角形(剪刀形状)到圆心距离作为半径,以剪刀两边的作为一个贝塞尔曲线的控制点。
那么这个控制点的坐标是什么呢?如上图所示,其实我们的控制点p1在分割线上,和原点距离是半径的长度,而终点在p2上面,长度大概是半径的1.2-1.4倍。p0p1 和 p0p2 大概构成了 25 °的角。
所以这里也很容易进行计算。首先计算出控制点 p1 的位置,肯定是
cx + R * Math.cos(a * part / rad)
,这里的 a 就是循环生成的,a * part / rad
就是指的是第几瓣的角度。然后我们找到 1/3 (25°)的坐标。设置 R1 为 1.3 倍半径。
这样关键的两个点就画了出来,接着生成贝塞尔曲线。
然后我们绘制出剩下的一半花瓣。最终代码如下:
花蕊
接着绘制花蕊,其实花蕊很容易绘制,因为它们分别处于 1/3、1/2、2/3 处。
这几个坐标点都找好了,但是不要忘了在终点绘制一个小圆点,这个更像花蕊上面的蕊头。
最终的代码如下:
总结
最后,我把这个项目部署到了线上,可以访问 http://sakura.gyyin.top 来访问到。