先看实现的效果吧:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>粒子效果</title>
<link rel="stylesheet" href="./index.css">
<style>
body{
padding: 0;
margin: 0;
height: 100vh;
box-sizing: border-box;
}
#bg{
height: 100%;
background-color: rgb(50,64,87);
background-size: cover;
position: fixed;
top: 0;
left: 0;
bottom: 0;
right: 0;
z-index: 0;
}
</style>
</head>
<body>
<div id="bg">
<canvas id="canvas"></canvas>
</div>
<script type="text/javascript">
let canvas = document.getElementById('canvas')
let width = window.innerWidth //画布宽度初始值
let height = window.innerHeight // 画布高度初始值
let animationFrame = null
const PIX_NUM = 140 // 粒子个数
const LINE_WIDTH = 1 // 粒子连线线宽
const RADIUS = 1 // 定义粒子圆的半径
let ctx = canvas.getContext('2d') //获取canvas画布上下文
let fillStyle = `rgba(255,255,255,.7)` // 像素点填充色
const CONNECTION = 80 //连线的最大距离
const FOLLOW_LENGTH = 80 // 鼠标跟随距离
let dots = [] //所有粒子集合
let mouseX = null // 鼠标x坐标
let mouseY = null // 鼠标y坐标
// 初始化画布的尺寸,并且当窗口大小变化时,也需要重新定义画布尺寸
function initCanvasSize(){
width = window.innerWidth
height = window.innerHeight
canvas.width = window.innerWidth
canvas.height = window.innerHeight
ctx.clearRect(0,0,width,height)
console.log('initCancasSize.......')
dots = []
// 每次初始化画布大小时,都需要清除上一次的动画,否则多个动画并存,会导致动画越来越快,这点和多个定时器道理一样
if(animationFrame)window.cancelAnimationFrame(animationFrame)
// // 测试10秒钟后终止动画
// setTimeout(()=>{
// window.cancelAnimationFrame(animationFrame)
// },10000)
// 调用初始化粒子的方法
initDots()
// 移动粒子
moveDots()
}
function mouseMove(e){
mouseX = e.clientX // 根据鼠标移动实时记录x坐标
mouseY = e.clientY // 根据鼠标移动实时记录y坐标
}
// 定义dot粒子类,用于生成80个像素点
class Dot{
// 构造函数,通过传入x和y的坐标来确定每一个粒子点的位置
constructor(x,y){
this.x = x
this.y = y
//随机生成 -1~1之间的数,意思就是每次重绘像素点时所移动的距离
// requestAnimationFrame()会根据每次移动的距离自动判定动画的速度
this.speedX = Math.random()*2-1
this.speedY = Math.random()*2-1
}
// 绘制每一个粒子的方法
draw(){
// console.log('开始绘制......')
ctx.beginPath() //开始绘制
ctx.arc(this.x,this.y,RADIUS,0,2*Math.PI) //根据x,y坐标绘制圆
ctx.fillStyle = 'rgba(255,255,255,1)' //设置圆填充色,即每一个粒子
ctx.fill() //填充圆
ctx.closePath() //结束绘制
}
move(){
// 判断边界,防止粒子点超出屏幕外
if(this.x >= width || this.x <= 0)this.speedX = -this.speedX
if(this.y >= height || this.y <= 0)this.speedY = -this.speedY
// 每次实例调用此方法都会使x坐标移动 speedX的距离
this.x += this.speedX
this.y += this.speedY
// 触发重绘元素点
this.draw()
}
}
// 根据上面定义的粒子数,即 PIX_NUM 常量
function initDots(){
for(let i = 0;i < PIX_NUM;i++){
// 为每个粒子点初始化x坐标,坐标范围在 0~画布宽度之间
let x = Math.floor(Math.random() * (width-0)+1)
// 同上, 0~画布高度之间
let y = Math.floor(Math.random()*(height-0)+1)
let dot = new Dot(x,y)
dot.draw()
dots.push(dot)//将每一个dot粒子实例存储到dots数组中
}
}
// 让粒子移动起来
function moveDots(){
// 清空画布:
// 即每次移动粒子时,都需要将上一次绘制的粒子从画布上清空掉之后在绘制粒子,
// 从而达到移动的视觉效果
ctx.clearRect(0,0,width,height)
for(const dot of dots){
dot.move()
}
// 启用帧动画,也是最重要的一步,否则每次重置并重绘粒子都是瞬间完成的
animationFrame = window.requestAnimationFrame(moveDots)
}
initCanvasSize()
// 监听窗口尺寸变化事件,一旦窗口大小变化,触发initCanvasSize事件
window.onresize = initCanvasSize
</script>
</body>
</html>