回顾
在做这个之前,我还做了
【canvas】网易云音乐鲸云特效『水晶音波』的简单实现
【canvas】网易云音乐鲸云动效『孤独星球』的简单实现
【canvas】实现多种形状的烟花
基本思想就是用三阶贝塞尔曲线拟合圆弧,用圆弧拼接成圆。
其中比较关键的是h
的长度,其最佳公式为
h = 4 ( 1 − cos θ 2 ) 3 sin θ 2 ⋅ r h = \frac{ 4 ( 1 - \cos{ \dfrac{\theta}{2} } ) }{ 3 \sin{ \dfrac{\theta}{2} } } \cdot r h=3sin2θ4(1−cos2θ)⋅r
计算出h
后,4个点的坐标就很容易得到,为
A ( r , 0 ) B ( r , h ) C ( r cos θ + h sin θ , r sin θ − h cos θ ) D ( r cos θ , r sin θ ) \begin{aligned} & \textbf A(r,\ \ 0)\\ & \textbf B(r,\ \ h)\\ & \textbf C(r\cos \theta + h\sin \theta,\ \ r\sin \theta - h\cos \theta)\\ & \textbf D(r\cos \theta,\ \ r\sin \theta) \end{aligned} A(r, 0)B(r, h)C(rcosθ+hsinθ, rsinθ−hcosθ)D(rcosθ, rsinθ)
关于公式,我从本站的一位博主『你别无选择』,所写的一篇博客『三阶贝塞尔曲线拟合圆弧的一般公式』中学到的,这里注明一下出处。
如果用 4 段三阶贝塞尔曲线模拟圆,则 θ = π 2 \theta = \dfrac{\pi}{2} θ=2π,效果如下
由于对称性,我只需要算出其中第一段圆弧的起始点 A \textbf A A,控制点 B \textbf B B,控制点 C \textbf C C,然后旋转,得到剩余点的坐标,就可以画圆了。
理解了公式,就可以动手写代码了。
class Cricle {
/** * 三阶贝塞尔曲线模拟圆 * @param {Object} context canvas.getContext("2d") * @param {Array} pole 圆心位置 * @param {Number} petal 用 petal 段三阶贝塞尔曲线模拟圆 * @param {Number} radius 半径 * @param {String} color 圆内填充颜色 * @param {Number} α 将圆旋转一个α° */
constructor(context, pole, petal, radius, color, α = 0) {
this.ctx = context;
this.pole = pole;
this.petal = petal;
this.radius = radius;
this.color = color;
this.α = α;
this.length = petal * 3;
this.buffer = [];
this.data = [];
this.__init();
this.render();
}
__init() {
const θ = 2 * Math.PI / this.petal;
const cosθ = Math.cos(θ);
const sinθ = Math.sin(θ);
const h = this.radius * (4 * (1 - Math.cos(θ / 2))) / (3 * Math.sin(θ / 2));
const A = [this.radius, 0];
const B = [this.radius, h];
const C = [this.radius * cosθ + h * sinθ, this.radius * sinθ - h * cosθ];
for (let i = 0, idx = 0; i < this.petal; ++i, idx += 3) {
const cosNθ = Math.cos(i * θ + this.α);
const sinNθ = Math.sin(i * θ + this.α);
this.data[idx] = this.__rotate(A, cosNθ, sinNθ);
this.data[idx + 1] = this.__rotate(B, cosNθ, sinNθ);
this.data[idx + 2] = this.__rotate(C, cosNθ, sinNθ);
}
this.data.forEach((v, i) => {
this.buffer[i] = [v[0] + this.pole[0], v[1] + this.pole[1]]; });
this.buffer[this.buffer.length] = this.buffer[0];
}
__rotate(p, cosα, sinα) {
return [p[0] * cosα - p[1] * sinα, p[1] * cosα + p[0] * sinα];
}
render() {
this.ctx.moveTo(...this.buffer[0]);
this.ctx.beginPath();
this.ctx.strokeStyle = 'blue';
for (let i = 0, idx = 0; i < this.petal; ++i, idx += 3) {
const A = this.buffer[idx];
const B = this.buffer[idx + 1];
const C = this.buffer[idx + 2];
const D = this.buffer[idx + 3];
this.ctx.lineTo(...A);
this.ctx.bezierCurveTo(...B, ...C, ...D);
// 标出所有点
// this.ctx.fillStyle = `hsl(${Math.floor(Math.random() * 360)}, 60%, 60%)`;
// this.ctx.fillRect(A[0] - 2, A[1] - 2, 4, 4);
// this.ctx.fillRect(B[0] - 2, B[1] - 2, 4, 4);
// this.ctx.fillRect(C[0] - 2, C[1] - 2, 4, 4);
}
this.ctx.closePath();
this.ctx.fillStyle = this.color;
this.ctx.fill();
}
}
const canvas = document.getElementById('background');
const pole = [canvas.width / 2, canvas.height / 2];
const petal = 4;
const radius = canvas.width / 2 * .5625;
const cricle = new Cricle(canvas.getContext("2d"), pole, petal, radius, 'rgba(241, 240, 237, .1)', 0);
4段贝塞尔曲线拟合圆
4段贝塞尔曲线已经可以拟合出圆了。
当然,可以用更多段贝塞尔曲线拟合。
如下,为24段贝塞尔曲线拟合圆
可以利用三阶贝塞尔曲线拟合圆,做炫酷的音频可视化。
如网易云音乐的鲸云动效『迷幻水波』
我之前已经做了『水晶音波』 『孤独星球』。这次,我尝试去实现『迷幻水波』。但是效果不是很好,就不贴源码了,也没放到codepen上,只是展示一下效果图。如果有兴趣,源码链接在这:github
事不过三,至此,模仿网易云音乐鲸云动效之路就结束了。