Effect:

Break down Threejs rendering principles

To render objects in Threejs, you need to have three basic elements:

  • Scene Scene
  • The Camera Camera
  • The Renderer the Renderer

The scene can be understood as the world stage, the camera is your eye, and the renderer is understood as the brain, used to represent what your eye sees from the world stage. Before we start coding, there’s one more problem we need to solve. How is the boundary determined? Since we are going to animate, we need to determine the width and height of the boundary and reset the heart beyond the boundary to achieve good performance and reuse the heart.

Calculate the boundary

PerspectiveCamera(FOV, aspect, near, far) from Threejs.PerspectiveCamera(FOV, aspect, near, far)

Parameter Description:

  • Fov: Angle of view, which can be interpreted as the Angle at which the eyes are opened, generally 45 to 60 is best
  • Aspect: Aspect ratio
  • Near: indicates the near end point
  • Far: indicates the far endpoint

The near and far end can be understood as when you take a camera to shoot, the distance between you and the camera lens is the near end, and the distance between you and the shooting object is the far end. According to the perspective, two isosceles triangles are nested with each other. Height is the height of our page and the base of the smaller triangle is the rendered height.

That is, in Threejs Scene, the distance is calculated by the base of the small triangle, so we need to find the base of the small triangle.

Derivation process: Let the base of a small triangle be x according to the Pythagorean theorem:

tan(fov/2) = x/2/near

tan(fov/2) = height/2/far

That is:

x/near = height/far => x = height/far*near

So you can figure out the Scene boundary in Threejs

And because the default camera in Threejs is in the center position, that is, the coordinates will have positive and negative numbers, in order to facilitate the batch generation of love, we move the camera to the upper left corner and redefine the coordinate axis.

initScene(){
  		// Page width and height
      let w = this.$q.screen.width
      let h = this.$q.screen.height
      // Define near and far endpoints
      let near = 40
      let far = 1000
      // Calculate the width and height after rendering
      let mw = w*near/far
      let mh = h*near/far
      this.mw = mw
      this.mh = mh
  		// Define Threejs
      this.scene = new THREE.Scene()
      this.camera = new THREE.PerspectiveCamera(45,w/h,near,far)
  		// Set the camera position and redefine the axis
      this.camera.position.set(mw/2,-mh/2,near)
      this.renderer = new THREE.WebGL1Renderer()
      this.renderer.setSize(w,h)
      this.$refs.bgCanvas.appendChild(this.renderer.domElement)
      this.drawScene()
    }
Copy the code

Batch generation of love

Before writing the code, let’s take a look at Threejs’s Sprite system: A Sprite is a flat surface that always faces the camera, usually with a translucent texture. Sprites don’t cast any shadows

The Sprite can be generated by setting the image and by using the above operation, we know the render width MW, so we can iterate to generate the heart:

// Generate thirty hearts
for (let i = 0; i < 30; i++) {
  			// Read images to generate sprites
        new THREE.TextureLoader().load( "love.png".(image) = >{
          const material = new THREE.SpriteMaterial( { map: image } )
          const sprite = new THREE.Sprite( material )
          // Set the Sprite zoom to achieve the size of the heart
          let zoom = randomNum(0.1.0.5)
          sprite.scale.set(zoom,zoom,zoom)
          // Randomly generate hearts according to render width MW
          let x = randomNum(0.this.mw)
          sprite.position.set(x,0.5.0)
          this.scene.add( sprite )
        })
      }
Copy the code

Heart Drop animation

In Threejs, we have a requestAnimationFrame function that provides us with an animation, which is typically refreshed 60 times a second, or 60 frames. We could certainly use setInterval, but requestAnimationFrame has many advantages. Perhaps most importantly, it pauses when the user switches to another TAB, so it doesn’t waste valuable processor resources or drain battery life. So, I encapsulated it as a function to make each heart an animation on its own:

drawScene(){
      for (let i = 0; i < 30; i++) {
        new THREE.TextureLoader().load( "love.png".(image) = >{
          const material = new THREE.SpriteMaterial( { map: image } )
          const sprite = new THREE.Sprite( material )
          let zoom = randomNum(0.1.0.5)
          sprite.scale.set(zoom,zoom,zoom)
          let x = randomNum(0.this.mw)
          sprite.position.set(x,0.5.0)
          this.scene.add( sprite )
          const animeScene = () = >{
            // Animation system
            requestAnimationFrame(animeScene)
            // Reset the render height if the render height is exceeded
            if (sprite.position.y<-this.mh){
              sprite.position.y = 0.5
            }
            // Depending on the zoom size, there are different falling speeds
            sprite.position.y-=0.2-sprite.scale.x*0.1
            this.renderer.render(this.scene,this.camera)
          }
          animeScene()
        })
      }
    }
Copy the code

Demo site

MyLove