用canvas制作星星下落动画

1.简介

由于最近打算给自己的网站写个花里胡哨的背景动画,以及学习一下canvas的用法,于是就诞生了这篇文章。

效果预览

preview.gif

项目地址

github

2.编写代码

2.1.初始化

此处先初始化canvas以及导入星星、月亮图片(此处偷懒了,有兴趣也可以直接用canvas画出来)

// 获得canvas元素
let canvas = document.querySelector('#star')
const ctx = canvas.getContext('2d')
// 将canvas设为全屏
canvas.width = window.innerWidth
canvas.height = window.innerHeight

// 星星的图片
const star_img = document.createElement('img')
star_img.src = './images/star.png'
// 月亮的图片
const moon_img = document.createElement('img')
moon_img.src = './images/moon.png'

let pockets = [] // 用于保存星星和月亮的数组
复制代码

可以先用ctx.drawImage()方法看看图片什么样

dsBuffer.bmp.png

2.2.设置星星属性

考虑到仅仅让星星下落太单调,于是增加了一些要素(月亮同理)

const config = {
    "star": {
        "min_size": 10, // 星星最小尺寸
        "max_size": 40, // 星星最大尺寸
        "rot": 0, // 随机初始旋转角度 0则不旋转
        "min_step": 0.5, // 每次间隔最小下落长度
        "max_step": 2, // 每次间隔最大下落长度
        "num": 20 // 画布内的个数
    },
    "moon": {
        "min_size": 10, 
        "max_size": 40, 
        "rot": 360, // 随机初始旋转角度(0-360)
        "min_step": 0.5,
        "max_step": 2,
        "num": 20
    }
}
复制代码

2.3.初始化星星月亮

由于星星和月亮的属性一致,所以初始化可以仅用一个initPocket函数实现。

// temp_type为star和moon二选一
const initPocket = (temp_type) => {
    // config[temp_type]即为2.2.中的星星或月亮对象
    for (let i = 0; i < config[temp_type].num; i++) {
        // xy为星星随机生成的坐标 以下位置表示以canvas左上角为原点,长宽为canvas.width和300的矩形区域
        const x = Math.random() * canvas.width
        const y = Math.random() * 300
        
        // 以下为生成星星的大小、旋转和位移的值
        // 大小和位置计算公式:最小值+随机值*(最大值-最小值) 即满足2.2.的大小区间
        // 旋转的角度满足[0, star.rot] 避免图片全是相似的
        let size, rot, step
        
        size = config[temp_type].min_size + Math.random() * (config[temp_type].max_size - config[temp_type].min_size)
        rot = config[temp_type].rot * Math.random()
        step = config[temp_type].min_step + Math.random() * (config[temp_type].max_step - config[temp_type].min_step)
        
        // 添加到pockets
        pockets.push({
            type: temp_type,
            x: x,
            y: y,
            size: size,
            rot: rot,
            rot_step: rot_step,
            step: step
        })
    }
}
复制代码

通过initPocket('star')、initPocket('moon')初始化pockets数组后便可以用ctx.drawImage()看看效果,注意此处用的2.2.的config

for (const pocket of pockets) {
    ctx.beginPath()
    
    // 由于下面会更改中心点坐标和旋转,所以此处先记录下画布状态
    ctx.save()
    
    // 由于画布的中心点默认为原点,所以要先把中心点移到指定的星星或月亮的中心上
    // 星星的xy坐标为星星的左上角,所以中心点坐标应为:(星星的x坐标+星星的半径, 星星的y坐标+星星的半径)
    ctx.translate(pocket.x + pocket.size / 2, pocket.y + pocket.size / 2)
    
    // 以星星为中心旋转pocket.rot度
    ctx.rotate(pocket.rot * Math.PI / 180)

    // 虽然现在以星星为中心,但绘制星星的时候仍是从星星的左上角绘制的,所以此处要返回原点
    ctx.translate(-(pocket.x + pocket.size / 2), -(pocket.y + pocket.size / 2))
    
    // 选择星星或月亮图片
    const img = pocket.type == 'star' ? star_img : moon_img
    
    // 绘制图片
    // img为图片内容,0 0 img.width img.height表示绘制整个图片,pocket.x pocket.y pocket.size pocket.size表示绘制的坐标(x,y)和大小pocket.size
    // 详细drawImage方法可以去看文档
    ctx.drawImage(img, 0, 0, img.width, img.height,
        pocket.x, pocket.y, pocket.size, pocket.size)
        
    ctx.restore() // 恢复画布状态,便于下个图片绘制
}
复制代码

结果如下 捕获.PNG 图中可看出生成图片的范围为canvas.width*300的矩形,星星大小随机[10,40]但不会旋转,月亮大小随机[10,40]并且随机转0-360度 满足2.2.的config配置

2.4.让星星实现下落

其实此处代码与2.3.的绘制类似,但是用上了一直没使用的pocket.step属性。

const starAnimation = () => {
    // 由于每颗星星都会变化,所以此处要先清除整个画布
    ctx.clearRect(0, 0, canvas.width, canvas.height)

    // 遍历每颗星星或月亮类似2.3.的遍历pockets数组
    for (const pocket of pockets) {
        ctx.beginPath()
        ctx.save()
        ctx.translate(pocket.x + pocket.size / 2, pocket.y + pocket.size / 2)
        ctx.rotate(pocket.rot * Math.PI / 180)
        
        // 此处不同2.3.因为需要实现下落。更新星星的y坐标
        pocket.y += pocket.step
        
        // 此处不同2.3. 当星星下落出界后需要重新定位它的坐标
        if ( pocket.y > canvas.height + pocket.size) {
            pocket.x = Math.random() * canvas.width
            pocket.y = Math.random() * 300
        }

        ctx.translate(-(pocket.x + pocket.size / 2), -(pocket.y + pocket.size / 2))
        const img = pocket.type == 'star' ? star_img : moon_img
        ctx.drawImage(img, 0, 0, img.width, img.height,
            pocket.x, pocket.y, pocket.size, pocket.size)
        ctx.restore()
    }
}
复制代码

下落动画写完了,之后只需要隔一段时间执行starAnimation()方法就行,比如用setInterval。但既然是学习canvas,requestAnimationFrame的使用必不可少。
最后的动画函数如下:

// interval为执行动画的间隔 单位ms
const runAnimation = (interval) => {
    let begin_time = new Date().getTime()
    const update = () => {
        requestAnimationFrame(update)

        let cur_time = new Date().getTime()
        if (cur_time - begin_time >= interval) {
            starAnimation(ctx, pockets)
            begin_time = cur_time
        }
    }
    requestAnimationFrame(update)
}
复制代码

最终runAnimation(60)就可以下落了。

3.总结

github上有我的完整代码。顺便还增添了上下左右四个方法的移动,以及支持边移动边旋转等等。
本项目是自己学习canvas的一个实践,有很多不足的地方还请见谅。
以上~

Supongo que te gusta

Origin juejin.im/post/7070074499445555237
Recomendado
Clasificación