除了疫情,广州最近还老下雨!给网页也来一场雨的洗礼吧

1,941 阅读4分钟

前言

最近广州疫情爆发,而且还老是下雨! 但让人感动的是,很多很多人,不分昼夜,冒着大雨奔赴一线,风雨无阻的守护我们!

想自己动手给网页来场雨的洗礼,记住这些可爱的人,同时希望疫情早些过去

image.png

分析需求

给整个页面下场雨,一场雨是由无数的雨滴构成,所以我们是不是可以用div或者canvas来实现雨滴,另外需要一个专门生成雨滴构造函数,并且每滴雨它出现的位置、大小、下落速度和方向都是不一的

下雨

使用div模拟雨滴

body .rain {
    display: block;
    width: 2px;
    height: 50px;
    background-image: radial-gradient(#fff 0%, rgba(255, 255, 255, 0) 60%);
    margin: 0 auto;
}

显示一滴雨如图

image.png

但是我们需要很多雨滴,那我们就不能这样写了

使用js动态生成N滴雨

1.首先先得有个构造函数

通过构造函数生成雨滴,但需要注意每滴雨的位置、速度和方向不一都可能不一样的

class Rain {
    constructor(opt = {}) {
        // 每个el即是一个雨滴
        this.el = null;
        // 雨点的出现的位置
        this.x = 0;
        this.y = 0;
        //雨的颗粒度大小
        this.width = opt.width;
        this.maxWidth = opt.maxWidth || 3;
        this.minWidth = opt.minWidth || 1;
        // 雨点的长度
        this.height = 0;
        this.maxHeight = opt.maxHeight || 30;
        this.minHeight = opt.minHeight || 20;
        // 雨的下落速度
        this.speed_x = 0
        this.speed_y = 0
        this.maxSpeed = opt.maxSpeed || 100;
        this.minSpeed = opt.minSpeed || 10;
        // 初始化数据
        this.init();
    }
}

如上,通过new Rain产生的对象身上会有我们提前预设的属性,比如雨滴的大小widthheightxy轴下落速率speed_xspeed_y等等 其中我们还需要一个init函数,来初始化对象

2.初始化对象基本信息

init() {
    // 初始化指定范围的随机雨点大小
    this.width = Math.floor(Math.random() * this.maxWidth + this.minWidth)
    this.height = Math.floor(Math.random() * this.maxHeight + this.minHeight)
    // 初始化指定范围的随机雨点位置
    this.x = Math.floor(Math.random() * (window.innerWidth - this.width))
    this.y = Math.floor(Math.random() * (window.innerHeight - this.height))
    // 初始化指定范围的随机雨点速度
    this.speed_y = Math.random() * this.maxSpeed + this.minSpeed
    this.speed_x = Math.random() * this.maxSpeed 
}

如上代码,初始化雨滴包括三组值widthheight值,x轴和y轴的位置,以及下雨的速度speed_yspeed_x,设置两个速度的原因是让雨的水平方向和垂直方向的速度不一样。需要注意,这里可以控制雨的大小,所以以后可以在这里调整雨

初始化雨滴的信息后,需要将雨滴的样式添加上

3.添加设置样式函数

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

通过setStyle函数设置好雨滴的基本样式后,接着需要一个创建真实元素的函数,将雨滴渲染出来

4.渲染雨滴

render() {
    this.el = document.createElement('div')
    this.setStyle()
    document.body.appendChild(this.el)
}

创建render函数,将对象的el赋值为新创建的div dom元素,然后通过setStyle设置样式,最后通过appendChild添加到页面中

5.造1000雨滴试试

let rainList = []
for (let i = 0; i <= 1000; i++) {
    let rain = new Rain();
    rain.render();
    rainList.push(rain);
}

通过for循环创建我们想要的雨点数,并渲染到页面中,通过rainList将创建的雨滴存起来。

创建雨滴结果如下

image.png

但是雨还不能动,得想办法让它动起来

6.让雨下起来

创建一个move方法,改变雨滴的位置即可实现动,但是需要注意判断边界,当超出边界时,重新初始化让它跑到其他可视区去,并重新设置随机样式,这样才有连续的雨滴下落

move() {
    // this.x += this.speed_x
    this.y += this.speed_y
    // 判断边界
    if (this.x < -this.width || this.x > window.innerWidth || this.y > window.innerHeight) {
        this.init()
        this.setStyle()
    }
    this.el.style.transform = `translate(${this.x}px, ${this.y}px)`

}

上述代码调用一次只会动一次,所以需要用到定时器,让它持续动。或者使用window.requestAnimationFrame,这个方法是用来在页面重绘之前,通知浏览器调用一个指定的函数。requestAnimationFrame比起 setTimeout、setInterval的优势主要有两点:

  1. requestAnimationFrame 会把每一帧中的所有DOM操作集中起来,在一次重绘或回流中就完成,并且重绘或回流的时间间隔紧紧跟随浏览器的刷新频率,一般来说,这个频率为每秒60帧;
  2. 在隐藏或不可见的元素中,requestAnimationFrame将不会进行重绘或回流,这当然就意味着更少的的cpu和内存使用量;
function moveRain() {
    window.requestAnimationFrame(() => {
        rainList.forEach((item) => {
            item.move()
        })
        moveRain()
    })
}

最后执行moveRain

moveRain()

7.效果展示

结果如图

动画.gif

可以看到大致的结果已经出来了,雨滴样式以及大小和快慢可以自己调,会出现不同的样式雨:如大一些的雨

let rain = new Rain({
    maxWidth:5,
    speemaxSpeed:150
});

结果如下

大一些的雨.gif

另外下午不可能每次只有向下飘,有时候刮风,上下飘时还左右斜飘。这可以调整水平偏移位置来调整 如:

    move() {
        this.x += 5;
        ...
    }

结果如图

倾斜的雨.gif

小结

通过这个案例,复习了原生api的基本使用。但是仍然存在很多问题,还有一些需要完善的地方。比如:

  1. 代码的复用性;能不能用于各个想引用的元素上?
  2. 那么多的div移动带来的性能问题
  3. 顶部出现的雨总是比较少,其实主要原因是当第一次超出可视区域时;
  4. 除了这种方式,是不是我们可以使用其他方式来实现呢。比如canvas,性能会更佳 等等...

备注:

  1. 灵感来源:juejin.cn/post/691085…
  2. 上述代码已上传github:github.com/jCodeLife/r…
  3. 新手小白,难免有跑偏。有不正确的,希望批评指出,我会及时更正,感谢!!