1.简介
由于最近打算给自己的网站写个花里胡哨的背景动画,以及学习一下canvas的用法,于是就诞生了这篇文章。
效果预览
项目地址
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()方法看看图片什么样
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() // 恢复画布状态,便于下个图片绘制
}
复制代码
结果如下 图中可看出生成图片的范围为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的一个实践,有很多不足的地方还请见谅。
以上~