如何用JavaScript实现一个轻量级雪花飘落动画?(附完整代码)

121 阅读1分钟

分享一个简单但功能完整的雪花动画的实现,这个类可以轻松地在网页中添加飘雪效果,支持替换图片以实现其他飘落效果。

功能概述

这个雪花动画具有以下特性:

  • 可控制雪花数量(最多30个)
  • 可调节雪花飘落速度
  • 随机雪花大小、透明度和初始位置
  • 提供暂停和继续动画的方法

效果

image.png

代码解析

构造函数与初始化

constructor ({dom , speed, imageUrl, createrTime = 1000, number}) {
    this.boxDom = dom
    this.speed = speed > 0 && speed <= 100 ? speed / 10 : 5
    this.imageUrl = imageUrl
    this.number = number > 30 ? 30 : number
    this.createrTime = createrTime
    this.docWidth = document.documentElement.clientWidth || document.body.clientWidth
    this.docHeight = document.documentElement.clientHeight || document.body.clientHeight
    this.xueHuaList = []
    this.moveTop = null
    this.minOpacity=0.8
    this.maxOpacity=1
    this.minScale=0.3
    this.maxScale=0.6
    this.init()
}

构造函数接收配置参数并进行合理化处理,确保动画性能与视觉效果平衡。

雪花DOM创建与样式设置

createrXueHuaDom () {
    let xueHuaImg = new Image()
    xueHuaImg.src = this.imageUrl
    xueHuaImg.style.position = 'absolute'
    xueHuaImg.style.zIndex = '100000'
    xueHuaImg.onload = () => {
        this.xueHuaSize(xueHuaImg)
        this.xueHuaPosition(xueHuaImg)
        this.xueHuaOpacity(xueHuaImg)
        this.boxDom.appendChild(xueHuaImg)
    }
    return xueHuaImg
}

创建雪花图片元素并设置基本样式,确保图片加载完成后进行尺寸、位置和透明度的随机设置。

动画核心逻辑

moveXueHua () {
    this.moveXueHuaFn = setInterval(() => {
        this.xueHuaList.forEach(item => {
            item.img.style.top = `${item.img.offsetTop + item.moveTop}px`
            
            if (this.docHeight + 100 < item.img.offsetTop) {
                this.xueHuaSize(item.img)
                this.xueHuaPosition(item.img)
                this.xueHuaOpacity(item.img)
                this.moveTop = +(Math.random() * (this.speed - 1) + 1).toFixed(2)
                item.moveTop = this.moveTop
            }
        })
    }, 50)
}

通过定时器实现雪花下落动画,当雪花飘出视窗后重置其位置和属性,实现循环效果。

使用示例

// 创建雪花动画实例
const snow = new XueHuaAnimation({
    dom: document.getElementById('snow-container'),
    speed: 30, // 速度范围1-100
    imageUrl: require('snowflake.png'),
    number: 20, // 雪花数量,最大30
    createrTime: 1000 // 创建间隔
})

// 暂停动画
// snow.stopXueHua()

// 继续动画
// snow.continueXueHua()

完整代码

class XueHuaAnimation {
    constructor ({dom , speed, imageUrl, createrTime = 1000, number}) {
        this.boxDom = dom
        this.speed = speed > 0 && speed <= 100 ? speed / 10 : 5
        this.imageUrl = imageUrl
        this.number = number > 30 ? 30 : number
        this.createrTime = createrTime
        this.docWidth = document.documentElement.clientWidth || document.body.clientWidth
        this.docHeight = document.documentElement.clientHeight || document.body.clientHeight
        this.xueHuaList = []
        this.moveTop = null
        this.minOpacity=0.8
        this.maxOpacity=1
        this.minScale=0.3
        this.maxScale=0.6
        this.init()
    }

    init () {
        this.boxDom.style.position = 'fixed'
        this.boxDom.style.zIndex = 10000
        
        for(let i = 0; i < this.number; i++) {
            this.moveTop = +(Math.random() * (this.speed - 1) + 1).toFixed(2)
            this.xueHuaList.push({
                img: this.createrXueHuaDom(),
                moveTop: this.moveTop
            })
        }
        
        this.moveXueHua()
    }

    createrXueHuaDom () {
        let xueHuaImg = new Image()
        xueHuaImg.src = this.imageUrl
        xueHuaImg.style.position = 'absolute'
        xueHuaImg.style.zIndex = '100000'
        xueHuaImg.onload = () => {
            this.xueHuaSize(xueHuaImg)
            this.xueHuaPosition(xueHuaImg)
            this.xueHuaOpacity(xueHuaImg)
            this.boxDom.appendChild(xueHuaImg)
        }
        return xueHuaImg
    }

    xueHuaSize (img) {
        let lenght = +(Math.random() * (this.maxScale - this.minScale) + this.minScale).toFixed(2)
        img.style.width = `${lenght}rem`
        img.style.height = `${lenght}rem`
    }

    xueHuaPosition (img) {
        let lenght = Math.floor(Math.random() * 10)
        let topLenght = +(Math.random() * (10 - 2) + 2).toFixed(2)
        img.style.top = `${-topLenght}rem`
        img.style.left = `${lenght > 15 ? lenght - 20 : lenght}rem`
    }

    xueHuaOpacity (img) {
        let opacity = +(Math.random() * (this.maxOpacity - this.minOpacity) + this.minOpacity).toFixed(2)
        img.style.opacity = opacity.toFixed(2)
    }

    moveXueHua () {
        this.moveXueHuaFn = setInterval(() => {
            this.xueHuaList.forEach(item => {
                item.img.style.top = `${item.img.offsetTop + item.moveTop}px`
                
                if (this.docHeight + 100 < item.img.offsetTop) {
                    this.xueHuaSize(item.img)
                    this.xueHuaPosition(item.img)
                    this.xueHuaOpacity(item.img)
                    this.moveTop = +(Math.random() * (this.speed - 1) + 1).toFixed(2)
                    item.moveTop = this.moveTop
                }
            })
        }, 50)
    }

    stopXueHua () {
        window.clearInterval(this.moveXueHuaFn);
    }

    continueXueHua () {
        this.moveXueHua()
    }
}

export default XueHuaAnimation;