preface

The design website that my girlfriend often visits these two days has more snow effect on the page, so he asked me if my website can snow. As a programmer, I generally say that it can’t be realized, but as a boyfriend, I can’t say no.

snow

Snow we can use the span TAB and the CSS radial gradient simple meaning:

.snow {
  display: block;
  width: 100px;
  height: 100px;
  background-image: radial-gradient(#fff 0%.rgba(255.255.255.0) 60%);
  border-radius: 50%;
}
Copy the code

The effect is as follows:

A lot of snow

There are no two identical snowflakes in the world, so each snowflake has its own size, position, speed and other attributes. To do this, create a snowflake class:

class Snow {
  constructor (opt = {}) {
    / / element
    this.el = null
    / / diameter
    this.width = 0
    // Maximum diameter
    this.maxWidth = opt.maxWidth || 80
    // Minimum diameter
    this.minWidth = opt.minWidth || 2
    / / transparency
    this.opacity = 0
    // Horizontal position
    this.x = 0
    // Reset the position
    this.y = 0
    / / speed
    this.speed = 0
    // Maximum speed
    this.maxSpeed = opt.maxSpeed || 4
    // Minimum speed
    this.minSpeed = opt.minSpeed || 1
    // Browser window size
    this.windowWidth = window.innerWidth
    this.windowHeight = window.innerHeight
    
    this.init()
  }

  // Initialize various properties
  init () {
    this.width = Math.floor(Math.random() * this.maxWidth + this.minWidth)
    this.opacity = Math.random()
    this.x = Math.floor(Math.random() * (this.windowWidth - this.width))
    this.y = Math.floor(Math.random() * (this.windowHeight - this.width))
    this.speed = Math.random() * this.maxSpeed + this.minSpeed
  }

  // Set the style
  setStyle () {
    this.el.style.cssText = `
      position: fixed;
      left: 0;
      top: 0;
      display: block;
      width: The ${this.width}px;
      height: The ${this.width}px;
      opacity: The ${this.opacity};
      background-image: radial-gradient(#fff 0%, rgba(255, 255, 255, 0) 60%);
      border-radius: 50%;
      z-index: 9999999999999;
      pointer-events: none;
      transform: translate(The ${this.x}px, The ${this.y}px);
    `
  }

  / / rendering
  render () {
    this.el = document.createElement('div')
    this.setStyle()
    document.body.appendChild(this.el)
  }
}
Copy the code

The init method is used to generate random initial size, position, speed, etc. Try this in the browser window new100 slices:

let snowList = []
for (let i = 0; i < 100; i++) {
    let snow = new Snow()
    snow.render()
    snowList.push(snow)
}
Copy the code

The effect is as follows:

move

Snow can only be called snow when it moves. It’s easy to move, just keep changing the x and y coordinates. Add a motion method to the snow class:

class snow {
    move () {
        this.x += this.speed
        this.y += this.speed
        this.el.style.left = this.x + 'px'
        this.el.style.top = this.y + 'px'}}Copy the code

Next, refresh continuously using requestAnimationFrame:

moveSnow () {
    window.requestAnimationFrame(() = > {
        snowList.forEach((item) = > {
            item.move()
        })
        moveSnow()
    })
}
Copy the code

The effect is as follows, since the velocity is positive, the whole thing is tilted to the right:

You can see it’s moving, but it’s gone off the screen, so the snow is going to disappear, right? To keep the snow going, it’s easy to detect the position of the snow, and if it’s off screen, send it back to the top. Modify the move method:

move () {
    this.x += this.speed
    this.y += this.speed
    // If you leave the window completely, you need to change the init method. If you leave the window completely, you need to change the init method, because you want the y coordinate to be 0 or less than 0, so that it does not appear out of nowhere, but from the sky
    if (this.x < -this.width || this.x > this.windowWidth || this.y > this.windowHeight) {
      this.init(true)
      this.setStyle()
    }
    this.el.style.left = this.x + 'px'
    this.el.style.top = this.y + 'px'
  }
Copy the code
init (reset) {
    // ...
    this.width = Math.floor(Math.random() * this.maxWidth + this.minWidth)
    this.y = reset ? -this.width : Math.floor(Math.random() * this.windowHeight)
    // ...
  }
Copy the code

So the snow will keep falling:

To optimize the

1. Horizontal speed

The horizontal and vertical velocity is the same, but it looks a little too oblique, so adjust it to separate the horizontal velocity from the vertical velocity:

class Snow {
  constructor (opt = {}) {
    // ...
    // Horizontal velocity
    this.sx = 0
    // Vertical velocity
    this.sy = 0
		// ...
  }
  
  init (reset) {
    // ...
    this.sy = Math.random() * this.maxSpeed + this.minSpeed
    this.sx = this.sy * Math.random()
  }
  
  move () {
    this.x += this.sx
    this.y += this.sy
    // ...}}Copy the code

2. There is no snow in the lower left corner

Because the whole is tilted to the right, there is a high probability of no snow in the lower left corner, which can be solved by having snow randomly appear on the left:

init (reset) {
  // ...
  this.x = Math.floor(Math.random() * (this.windowWidth - this.width))
  this.y = Math.floor(Math.random() * (this.windowHeight - this.width))
  if (reset && Math.random() > 0.8) {// Let a small portion of the snow initialize on the left
    this.x = -this.width
  } else if (reset) {
    this.y = -this.width
  }
  // ...
}
Copy the code

3. Snow in front of you

Randomly select a bit of snow to give it more volume, transparency and speed, and then use the CSS3 3D perspective effect to increase its Z-axis value, so that it feels like it is passing in front of your eyes:

<body style="perspective: 500; -webkit-perspective: 500"></body>
Copy the code
class Snow {
  constructor (opt = {}) {
    // ...
    // The value of the z axis
    this.z = 0
    // The maximum speed of the fast pass
    this.quickMaxSpeed = opt.quickMaxSpeed || 10
    // The minimum speed of a fast streak
    this.quickMinSpeed = opt.quickMinSpeed || 8
    // Fast across the width
    this.quickWidth = opt.quickWidth || 80
    // Fast across the transparency
    this.quickOpacity = opt.quickOpacity || 0.2
    // ...
  }
  
  init (reset) {
    let isQuick = Math.random() > 0.8
    this.width = isQuick ? this.quickWidth : Math.floor(Math.random() * this.maxWidth + this.minWidth)
    this.z = isQuick ? Math.random() * 300 + 200 : 0
    this.opacity = isQuick ? this.quickOpacity : Math.random()
    // ...
    this.sy = isQuick ? Math.random() * this.quickMaxSpeed + this.quickMinSpeed : Math.random() * this.maxSpeed + this.minSpeed
    // ...
  }
  
  move () {
    // ...
    this.el.style.transform = `translate3d(The ${this.x}px, The ${this.y}px, The ${this.z}px)`}}Copy the code

4. Heavy snow

Snowflakes are as light as goose feathers. How does goose feathers float? Is it swinging from side to side? Then we can also choose a part of the snowflake to let it float like a goose feather. It is very simple to swing from side to side, and the speed will be increased and decreased.

class Snow {
  constructor (opt = {}) {
    // ...
    // Whether to swing from side to side
    this.isSwing = false
    // The step size of the left and right swing
    this.stepSx = 0.03
    // ...
  }

  // Randomly initialize the properties
  init (reset) {
    // ...
    this.isSwing = Math.random() > 0.8
    // ...
  }

  move () {
    if (this.isSwing) {
      if (this.sx >= 1 || this.sx <= -1) {
        this.stepSx = -this.stepSx
      }
      this.sx += this.stepSx
    }
    // ...}}Copy the code

In addition to this method, there is another way to sway left and right, which is to use the sine or cosine functions, since they are rotated 90 degrees to sway left and right:

We use the sine function, the formula is: y=sin(x), the value of x is represented by radians, as long as it keeps increasing, the value of y is used to modify the speed change step of the snowflake in the horizontal direction:

class Snow {
  constructor (opt = {}) {
    // ...
    // Whether to swing from side to side
    this.isSwing = false
    // The oscillating sine function x variable
    this.swingRadian = 0
    // Sinusoidal x step of swinging from side to side
    this.swingStep = 0.01
    // ...
  }

  init (reset) {
    // ...
    this.swingStep = 0.01 * Math.random()
  }

  move () {
    if (this.isSwing) {
      this.swingRadian += this.swingStep
      this.x += this.sx * Math.sin(this.swingRadian * Math.PI) * 0.2
    } else {
      this.x += this.sx
    }
    // ...}}Copy the code

Because the sine function y changes from 1 to -1, so the amplitude is too big, so it’s multiplied by a decimal 0.2 to reduce the amplitude. Another way to reduce the amplitude is not to use the whole sine curve, but to cut a suitable interval from it. For example, let x change from 0.9 PI to 1.1 PI:

class Snow {
  constructor (opt = {}) {
    // ...
    // Whether to swing from side to side
    this.isSwing = false
    // The oscillating sine function x variable
    this.swingRadian = 1// Need to change to an intermediate value
    // Sinusoidal x step of swinging from side to side
    this.swingStep = 0.01
    // ...
  }

  init (reset) {
    // ...
    this.swingStep = 0.01 * Math.random()
    this.swingRadian = Math.random() * (1.1 - 0.9) + 0.9// Let it be random
  }

  move () {
    if (this.isSwing) {
      if (this.swingRadian > 1.1 || this.swingRadian < 0.9) {
        this.swingStep = -this.swingStep
      }
      this.swingRadian += this.swingStep
      this.x += this.sx * Math.sin(this.swingRadian * Math.PI)
    } else {
      this.x += this.sx
    }
    // ...}}Copy the code

5. Go down slowly

Now that we have a curve in the horizontal direction, can we also change it to an non-uniform velocity in the vertical direction? Can, of course, is the difference between speed has been positive, otherwise will appear the natural phenomenon, change speed curve also can use the cosine, we use the above the sine curve between PI PI 0.9 to 1.1, according to the above, you can find the corresponding cosine curve is negative, the trend is first slow, quick, so you can use this section to change the direction of the vertical speed:

move () {
  if (this.isSwing) {
    if (this.swingRadian > 1.1 || this.swingRadian < 0.9) {
      this.swingStep = -this.swingStep
    }
    this.swingRadian += this.swingStep
    this.x += this.sx * Math.sin(this.swingRadian * Math.PI)
    this.y -= this.sy * Math.cos(this.swingRadian * Math.PI)// Since the velocities are all negative, let's change it to minus
  } else {
    this.x += this.sx
    this.y += this.sy
  }
  // ...
}
Copy the code

6. At the top

To prevent shading from higher-level elements on the page, add a large layer to the snowflake style:

render () {
    this.el = document.createElement('div')
    this.el.style.cssText = ` / /... z-index: 9999999999999; `
    document.body.appendChild(this.el)
}
Copy the code

7. Can’t see me

Changed the hierarchy so that the snowflake would be at the top of the page, which would block other elements’ mouse events. We need to disable it from responding to mouse events:

render () {
    this.el = document.createElement('div')
    this.el.style.cssText = ` / /... pointer-events: none; `
    document.body.appendChild(this.el)
  }
Copy the code

8. Be better

Use the better performing Transform property to animate:

render () {
    this.el = document.createElement('div')
    this.el.style.cssText = `
        left: 0;
        top: 0;
        transform: translate(The ${this.x}px, The ${this.y}px);
    `
    document.body.appendChild(this.el)
}
Copy the code
move () {
    // ...
    // this.el.style.left = this.x + 'px'
    // this.el.style.top = this.y + 'px'
    this.el.style.transform = `translate(The ${this.x}px, The ${this.y}px)`
}
Copy the code

Of course, the best way is to use canvas.

Final result:

Rain & sleet

Rain is similar to snow, both fall from the sky, but the speed of the rain is faster, and usually does not swing from side to side or anything, and the direction is basically the same. First, let’s modify the pattern:

setStyle () {
  this.el.style.cssText = ` / /... width: 1px; / /... `
}
Copy the code

Easy, just write the width to 1:

Next, remove the sway:

move () {
  this.x += this.sx
  this.y += this.sy
  // ...
}
Copy the code

The effect is as follows:

You can see that the rain is moving vertically and horizontally, which is obviously not possible. You need to tilt it at a certain Angle so that it is moving in the same direction. This is also easy.

move () {
  // ...
  this.el.style.transform = `translate(The ${this.x}px, The ${this.y}px) The ${this.getRotate(this.sy, this.sx)}`
}
getRotate(sy, sx) {
  return `rotate(${sx === 0 ? 0 : (90 + Math.atan(sy / sx) * (180 / Math.PI))}deg)`
}
Copy the code

Because tan(θ)=sy/sx, θ= math.atan (sy /sx), because the rain line segment is vertical from top to bottom by default, θ represents the Angle with the horizontal direction, so you need to rotate 90 degrees first, then rotate the degree of the Angle, the final radian Angle formula is: Angle = radian *(180/π).

Rain and snow both come true, let them out together, it is sleet:

According to the weather snow

Put the above code on the website to have the effect of snow, in addition, you can also use the API of the weather manufacturer, according to the real-time weather to snow or rain, and then realize the effect of the sun, clouds and so on, an immersive weather is completed, interested in the practice of their own.

The full code is at github.com/wanglin2/sn… .