Look at the effect

Pure animation

With music effects, Google Browser only

The code in this

Simple? We may have no shortage of simple eyes, but a shortage of perfectly simple hands

This animation effect is seen on netease Cloud Music APP, with the mentality of reviewing canvas to achieve.

Now start analyzing and implementing the required functionality

List the requirements

  1. Refer to the link above that has been implemented.
  2. Customizable parameters (size, color, etc.) for different usage scenarios
  3. You need to animate according to the tempo of the music

Begin to implement

1. Set parameters

According to the effect, we can initially think of the parameters that need to be set as follows, and the specific values of the parameters can be further modified in the subsequent implementation

 const originParams = {
  cover: ' '.// The cover of the center
  size: 500.// Canvas size
  radius: 100.// Cover diagram, the radius of the center circle, less than zero is the percentage of the container
  interval: [500.1500].// The minimum frequency of ripples (milliseconds)
  centerColor: '#ddd'.// The color of the cover image position (displayed when there is no cover image)
  borderWidth: 5.// The width of the cover image border
  borderColor: '#aaa'.// The color of the border of the cover image
  rippleWidth: 4.// The width of the circle
  rippleColor: '#fff'.// Ripple color
  pointRadius: 8.// The radius of the ripple dot
  rotateAngle: 3..// The rotation Angle of each frame of the cover image
}
Copy the code

2. Write constructors

As we know, the principle of animation is to secretly replace a similar picture when the brain does not pay attention to it and cannot respond to it. In this way, step by step, the animation is formed, and this step by step can be called frame.

So, when rendering circles and dots, we need an array to store their positions for each rendering.

In addition, we need to make some necessary initialization judgments and declare some common parameters

To sum up, we can basically write the following constructor

class Ripple {
  constructor(container, params = {}) {
    const originParams = {
      cover: ' '.size: 500.radius: 100.interval: [500.1500].centerColor: '#ddd'.borderWidth: 5.borderColor: '#aaa'.rippleWidth: 4.rippleColor: '#fff'.pointRadius: 8.rotateAngle: 3.,}this.container = typeof container === "string" ? document.querySelector(container) : container

    this.params = Object.assign(originParams, params)

    this.cover = this.params.cover

    this.radius = this.params.radius < 1 ? this.params.size * this.params.radius : this.params.radius

    this.center = this.params.size / 2  / / center

    this.rate = 0  // Record the number of frames played
    this.frame = null  // Frame animation, used to cancel
    this.rippleLines = []  // Stores the radius of the ripple circle
    this.ripplePoints = []  // Store the distance between the ripple point and the center point}}Copy the code

3. Initialize the container

Canvas images are not easy to render and rotate, so render using the IMG tag when the cover parameter is passed.

In addition, we need to add some other necessary CSS to the elements

  class Ripple{
    initCanvas() {
      this.container.innerHTML = `<canvas width="The ${this.params.size}" height="The ${this.params.size}"></canvas>The ${this.cover ? `<img src="The ${this.cover}" alt="">` : ' '}`
  
      this.cover = this.container.querySelector('img')
      this.canvas = this.container.querySelector('canvas')
      this.ctx = this.canvas.getContext('2d')
  
      this.rotate = 0
  
      const containerStyle = { ... }
      const canvasStyle = { ... }
      const coverStyle = { ... }
  
      utils.addStyles(this.container, containerStyle)
      utils.addStyles(this.canvas, canvasStyle)
      utils.addStyles(this.cover, coverStyle)
  
      this.strokeBorder()
    }
  }
Copy the code

4. Draw a circle

The basic usage of canvas will not be repeated

class Ripple{
  strokeCenterCircle() {
    const ctx = this.ctx
    ctx.beginPath()
    ctx.arc(this.center, this.center, this.radius, 0.2 * Math.PI)
    ctx.closePath()
    ctx.fillStyle = this.params.centerColor
    ctx.fill()
   }
  strokeBorder() {
    const ctx = this.ctx
    ctx.beginPath()
    ctx.arc(this.center, this.center, this.radius + this.params.borderWidth / 2.0.2 * Math.PI)
    ctx.closePath()
    ctx.strokeStyle = this.params.borderColor
    ctx.lineWidth = 5
    ctx.stroke()
  }
}
Copy the code

5. Draw a circle

Isn’t that just a circle and a circle

    class Ripple{
      drawRipple() {
        const ctx = this.ctx
  
        / / outer ring
        ctx.beginPath()
        ctx.arc(this.center, this.center, 200.0.Math.PI * 2)
        ctx.strokeStyle = 'rgba (255255255,0.4)'
        ctx.lineWidth = this.params.rippleWidth
        ctx.stroke()
  
        / / draw point
        ctx.beginPath()
        ctx.arc(this.center - 200/Math.sqrt(2), this.center - 200/Math.sqrt(2), this.params.pointRadius, 0.2 * Math.PI)
        ctx.closePath()
        ctx.fillStyle = 'rgba (255255255,0.4)'
        ctx.fill()
      }
    }
Copy the code

The following question arises

The reason for this is also very simple. The transparency of the overlapping part of the two translucent figures will definitely increase, so it can only be solved by drawing an incomplete circle (just across the part of the dot), as shown in the following figure:

The lines are moving in a little bit to make it easier to see, so the problem we’re going to have is we’re going to have r and r, and we’re going to find the Angle theta, and we’re not going to go into the details, but it’s kind of high school math. We can get the Angle math.asin (R/R / 2) * 4.

6. Keep drawing circles

The overlap between circles and dots has been resolved. Now you need to update their positions with each refresh, add a new circle and point if the condition is met, and delete the corresponding data if the radius of the circle exceeds the canvas

  class Ripple{
    strokeRipple() {
      // Delete the ring data when the ring size exceeds the canvas
      if (this.rippleLines[0] > this.params.size) {
        this.rippleLines.shift()
        this.ripplePoints.shift()
      }
  
      // Add data when the condition is reached
      if (this.rate - this.lastripple >= this.minInterval) {
        this.rippleLines.push({
          r: this.radius + this.params.borderWidth + this.params.rippleWidth / 2.color: utils.getRgbColor(this.params.rippleColor)
        })
  
        this.ripplePoints.push({
          angle: utils.randomAngle()
        })
        // Update the add time
        this.lastripple = this.rate
      }
  
      // Calculates the position data for the next rendering
      this.rippleLines = this.rippleLines.map((line, index) = >...).this.ripplePoints = this.rippleLines.map((line, index) = >...).// Render according to the new data
      this.strokeRippleLine()
      this.strokeRipplePoint()
    }
  }
Copy the code

6. Start the animation

RequestAnimationFrame is updated every render and stored in this.frame for easy cancellation.

class Ripple{
    animate() {
      this.ctx.clearRect(0.0.this.params.size, this.params.size)
  
      this.strokeRipple()
  
      this.strokeBorder()
  
      ...
  
      var that = this
      this.frame = requestAnimationFrame(function () {
        that.animate()
      })
    }
}
Copy the code

7. Add a musical beat

The principle is as follows:

  1. To create a<audio>, do relevant Settings (auto play, control display, etc.)
  2. Create an AudioContext and set the audio source to this
  3. Create an AnalyserNode and link the AnalyserNode to the audio source of the AudioContext
  4. To achieve the effect of playing the AudioContext once, you need to connect the Analyser out of the AudioContext again, otherwise the data will only go in and out without sound
  5. Create a place to draw (normal HTML elements, canvas, SVG, WebGL, etc.)
  6. Get audio data from AnalyserNode (time domain or frequency domain data, timing can be requestAnimationFrame or setTimeout)
  7. Using the obtained data, visual design, drawing images

However, there may be a compatibility problem with the AudioContext. In Safari, the audio information is not available in real time. If you know, please feel free to comment.

So this part of the implementation and nothing good to say, interested can directly view the source code and implementation

Write in the last

The implementation of the above animation is not complicated, but in the process of implementation, may consider more is how to organize the code, how to design the interface (how easy to use, increase the system, reduce the degree of operation). These things in light cat when writing a tutorial on the weak writing, one has brought or not mentioned at all, but only when writing your own to realize real needs, so, if you can see here, might as well put just saw code implementation, see the effect only, oneself also to write a (with music) such animation, after all, we don’t lack of found simple eyes, What may be missing are perfectly simple hands.

Some other projects

I think it’s great, but no one seems to know…

css tricks

js tricks

animate_resume

reference

Developer.mozilla.org/en-US/docs/…

Developer.mozilla.org/zh-CN/docs/…