From front-end early reading class

In the project, I used Canvas to realize the water wave diagram. Here I share its implementation principle. At the beginning, you may not know where to start. Let's take a look at the characteristics of waves.

Yes, some people will think that it is sine and cosine curve ! For the wave with small wave steepness, the sine or cosine curve is generally selected to represent the waveform, which is the simplest and closest to the actual waveform. Here I choose sinusoidal curve to achieve.

Before talking about the implementation idea, let's recall the basis of sine curve.

#### Sine curve

Sine curve formula: y = A sin(Bx + C) + D

The amplitude is A, and the greater the value of A, the steeper the curve:

The period is 2 π / b. when the value of B is greater than 1, the greater the value of B, the shorter the period. When the value of B is less than 1 and greater than 0, the period becomes longer:

The phase shift is − C/B. when B remains unchanged, when C is positive, the curve moves to the left, and when C is negative, the curve moves to the right:

The vertical displacement is D, and the control curve moves up and down:

#### Realization idea

After understanding some properties of sinusoidal curve, we can use these properties to control waves,

- Amplitude: controls the height of the wave
- Period: controls the width of waves
- Phase shift: controls the horizontal movement of waves
- Vertical displacement: controls the height of the water level

The realization of animation effect mainly uses phase shift to produce the feeling of wave movement by continuously moving the curve horizontally, and then multiple curves can be drawn. The visual difference between the curves is generated by controlling the attributes (height, width and moving speed), and there will be the feeling of wave fluctuation. With these ideas, let's realize them slowly.

#### Curve drawing

Initialize and define the width and height of the Canvas:

componentDidMount() { const self = this; const canvas = this.refs.canvas; canvas.height = 500; canvas.width = 500; this.canvas = canvas; this.canvasWidth = canvas.width; this.canvasHeight = canvas.height; const ctx = canvas.getContext('2d'); this.drawSin(ctx); } render() { return ( <div className="content page"> <canvas ref="canvas"></canvas> </div> ); }

According to the parameter configuration defining the wave, draw the sinusoidal curve through the formula: y = wave height * sin(x * wave width + horizontal displacement):

drawSin(ctx) { const points = []; const canvasWidth = this.canvasWidth; const canvasHeight = this.canvasHeight; const startX = 0; const waveWidth = 0.05; // Wave width, the smaller the number, the wider const waveHeight = 20; // Wave height, the greater the number, the higher const xOffset = 0; // Horizontal displacement ctx.beginPath(); for (let x = startX; x < startX + canvasWidth; x += 20 / canvasWidth) { const y = waveHeight * Math.sin((startX + x) * waveWidth + xOffset); points.push([x, (canvasHeight / 2) + y]); ctx.lineTo(x, (canvasHeight / 2) + y); } ctx.lineTo(canvasWidth, canvasHeight); ctx.lineTo(startX, canvasHeight); ctx.lineTo(points[0][0], points[0][1]); ctx.stroke(); }

When the curve is drawn, the curve is static. How to make it move? As mentioned in the previous idea, you can continuously change the horizontal offset (xOffset) to make the curve move horizontally, which can produce a dynamic effect.

componentDidMount() { ... this.xOffset = 0; // Initial offset this.speed = 0.1; // migration velocity requestAnimationFrame(this.draw.bind(this, canvas)); } draw() { const canvas = this.canvas; const ctx = canvas.getContext('2d'); ctx.clearRect(0, 0, canvas.width, canvas.height); // Curve drawing this.drawSin(ctx, this.xOffset); this.xOffset += this.speed; requestAnimationFrame(this.draw.bind(this)); } drawSin(ctx, xOffset = 0) { ... }

#### Spherical drawing

Now our rudiment has come out, and the curve and dynamic effect have been realized. It can be regarded as that water is installed on a cuboid. If water is installed on a sphere?

Here, we use the clip() method of Canvas to define the cutting area. After defining the cutting area, the browser will restrict all drawing operations to this area. Therefore, we can draw a circle first, and define that the area to be drawn later can only be within the area of this circle, and the excess part will not be displayed, so as to form the effect of waves in a sphere.

draw() { ... if (!this.isDrawCircle) { this.drawCircle(ctx); } this.drawSin(ctx, this.xOffset); this.xOffset += this.speed; requestAnimationFrame(this.draw.bind(this)); } drawCircle(ctx) { const r = this.canvasWidth / 2; const lineWidth = 5; const cR = r - (lineWidth); ctx.lineWidth = lineWidth; ctx.beginPath(); ctx.arc(r, r, cR, 0, 2 * Math.PI); ctx.stroke(); ctx.clip(); this.isDrawCircle = true; }

#### Water level control

Do you feel a little? At present, it is still a little short of controlling the water level, that is, the percentage mapped to the data. If you pay attention to the above, you will find that the calculation of y coordinate of sinusoidal curve will add canvas height / 2. In fact, this is the setting of water level.

Let's see: y = A sin(Bx + C) + D, the height of the curve is determined by A and D, A controls the height of the wave, and the actual water level is still controlled by D.

Visually, the height of water level means the percentage of data. Assuming that the current percentage is 80%, the height of water level is canvasHeight * 0.8, and the coordinate mapped to coordinate system y is canvasHeight * (1 - 0.8). (the coordinate origin is in the upper left corner). In terms of animation effect, in addition to the horizontal movement of sinusoidal curve, we add the dynamic effect of water level rise:

componentDidMount() { ... this.xOffset = 0; this.speed = 0.1; // Water level value this.rangeValue = 0.6; // Initial water level this.nowRange = 0; requestAnimationFrame(this.draw.bind(this, canvas)); } draw() { ... this.drawSin(ctx, this.xOffset, this.nowRange); this.xOffset += this.speed; if (this.nowRange < this.rangeValue) { this.nowRange += 0.01; } requestAnimationFrame(this.draw.bind(this)); } drawCircle(ctx) { ... } drawSin(ctx, xOffset = 0, nowRange = 0) { ... for (let x = startX; x < startX + canvasWidth; x += 20 / canvasWidth) { const y = waveHeight * Math.sin((startX + x) * waveWidth + xOffset); points.push([x, (1 - nowRange) * canvasHeight + y]); ctx.lineTo(x, (1 - nowRange) * canvasHeight + y); } ... }

#### Final effect

Finally, we add color and a sine curve, and there will be a feeling of wave fluctuation.

In the above spherical drawing, we use the method of cutting area. Some people will think that at this time, I don't need to cut the circle, but use other shapes to create the effect of water in various containers.

Source code: https://github.com/beyondxgb/wave-demo

About the author of this article: @ original text of war cessation: https://juejin.im/post/6844903640537235470