[Canvas] netease Cloud Music Whale cloud special effects “crystal sound wave” simple implementation
Today, I’m going to implement the Lonely Planet effect.
This is a screenshot of netease cloud music, the end put my own effect
We have already started the “game” last time, so we don’t need to repeat it here. We will directly talk about how to use canvas to draw the background ripple effect.
canvas
Now I’m going to go into detail. Let’s define a hollow circle with a small ball on it. So to have these key variables,
variable | |
---|---|
The hollow circle | Center position, increasing speed, radius, gradient range (i.e., how big the circle is at most) |
A ball | Position, speed of movement, radius. |
The hollow circle is going to get bigger, the little ball is going to move, so it’s easy to do it, so I’m going to do it the way I defined the triangle last time, and I’m going to do it this way
class Circle {
constructor(context, speed, pole, radius, range) {
this.ctx = context;
this.speed = speed;
this.pole = pole;
this.radius = radius;
this.range = range;
this.__restart();
}
__restart() {
this.r = this.r ? this.radius[0] : this.radius[1];
this.ballRadius = Math.floor(4 + Math.random() * 3);
this.ballAngle = Math.random() * PI2; // The position of the ball is expressed by Angle, randomly generated
this.ballPoint || (this.ballPoint = [0.0]);
this.ballPoint[0] = Math.cos(this.ballAngle) * this.r; // Calculate the coordinates of the ball according to the radius of the hollow circle
this.ballPoint[1] = Math.sin(this.ballAngle) * this.r;
}
__lerp(src, dst, coeff) {
return src + (dst - src) * coeff; // Linear function, elementary mathematics
}
__update() {
if (this.r - this.range > 0.0001) // Hollow circles start again after a certain range
this.__restart();
else {
this.r += this.speed;
this.ballAngle += .01;
this.ballPoint[0] = Math.cos(this.ballAngle) * this.r;
this.ballPoint[1] = Math.sin(this.ballAngle) * this.r;
this.opacity = this.__lerp(1.0.this.r / this.range);
}
}
render() {
this.__update();
// Draw a hollow circle
this.ctx.lineWidth = 2;
this.ctx.strokeStyle = `rgba(179, 179, 179, The ${this.opacity}) `;
this.ctx.beginPath();
this.ctx.arc(this.pole[0].this.pole[1].this.r, 0, PI2);
this.ctx.stroke();
// Draw the ball
this.ctx.strokeStyle = `rgba(179, 179, 179, 0)`;
this.ctx.fillStyle = `rgba(179, 179, 179, The ${this.opacity}) `;
this.ctx.beginPath();
this.ctx.arc(this.pole[0] + this.ballPoint[0].this.pole[1] + this.ballPoint[1].this.ballRadius, 0, PI2);
this.ctx.stroke();
this.ctx.fill(); }}Copy the code
In the above code, there are a few things worth perusing:
- Why do you count balls like that
[x, y]
I could write it like this, right?
this.ballPoint || (this.ballPoint = [0.0]);
this.ballPoint[0] = Math.cos(this.ballAngle) * this.r;
this.ballPoint[1] = Math.sin(this.ballAngle) * this.r;
Copy the code
Without such
this.ballPoint = [Math.cos(this.ballAngle) * this.r, Math.sin(this.ballAngle) * this.r];
Copy the code
There’s not much difference between the two methods, but I’m a compulsive compulsive, and the latter creates new arrays repeatedly to assign to this.ballPoint, which I think is bad, because arrays only need to be created once, changing the values in the array each time.
- Why construct this circle when I pass in the radius
radius
It’s an array, right?
In fact, this is to achieve well-proportioned. This question is addressed further in the “scenario” definition.
This is followed by defining the “scenario.”
class Scene {
constructor(canvas) {
this.cvs = canvas;
this.ctx = canvas.getContext('2d');
const slit = 50;
const pole = this.cvs.width / 2;
this.circleSet = [];
this.circleNum = Math.floor(pole / slit);
const range = this.circleNum * slit;
for (let i = 1; i < this.circleNum; ++i)
this.circleSet.push(new Circle(this.ctx, 1, [pole, pole], [slit, slit * i], range));
}
render() {
this.ctx.clearRect(0.0.this.cvs.width, this.cvs.height);
this.circleSet.forEach(circle= > circle.render());
}
run() {
if (!this.timer) {
this.timer = setInterval(this.render.bind(this), 25);
}
}
stop() {
if (this.timer) {
clearInterval(this.timer);
this.timer = 0; }}}/ / run
const canvas = document.getElementById('background');
canvas.width = canvas.height = Math.ceil(canvas.parentNode.lastElementChild.offsetWidth * 1.68421);
const scene = new Scene(canvas);
scene.run();
Copy the code
Among them
const slit = 50;
const pole = this.cvs.width / 2;
this.circleSet = [];
this.circleNum = Math.floor(pole / slit);
const range = this.circleNum * slit;
for (let i = 1; i < this.circleNum; ++i)
this.circleSet.push(new Circle(this.ctx, 1, [pole, pole], [slit, slit * i], range));
Copy the code
This part is the principle, and I’ve drawn a simple diagram to show it
When the radius of a hollow Circle exceeds range, it goes back to the start position in the diagram due to the circle.__update () method. That’s how the cycle works.
The final result
Github online demo: Codepen