前端 Canvas 训练营第一期:鼠标交互粒子背景效果

Posted 没头发的米糊

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了前端 Canvas 训练营第一期:鼠标交互粒子背景效果相关的知识,希望对你有一定的参考价值。

一、引言

这是一个全新的系列,在这个系列中,我将通过一系列实际案例,和你一起学习、研究Canvas,从而掌握编写 Canvas 特效的能力。

html5 canvas 元素用于图形的绘制,通过脚本 (通常是javascript)来完成。
canvas 标签只是图形容器,您必须使用脚本来绘制图形。
你可以通过多种方法使用 canvas 绘制路径,盒、圆、字符以及添加图像。

这是一个注重实际案例的系列教程,我希望能够通过具体的案例,而非抽象的文档和解释带你学习 Canvas 这项技术。那么废话不多说,我们直接看这次的案例吧。

二、案例介绍

这是在很多博客网站上都非常常见的背景特效,在空旷的画布上,有非常多的随机运动的点,这些点在运动时如果距离足够近,则会产生连线。当鼠标移入时,也会为周围的点创建连线,同时与鼠标相连的点会受到鼠标的牵引。
我们可以将这个案例轻松地划分为三个由易到难的实现阶段:

  1. 简单效果:即完成点的自由运动、以及连线的自动产生。
  2. 复杂效果:鼠标移入时,完成鼠标与周围点的连线。
  3. 最终效果:鼠标移动时,与鼠标相连的点受到牵引。

在学习完这篇文章后,我建议你至少掌握到第二阶段,最终效果是否能够掌握取决于你是否有兴趣。

三、逐步实现

1. 文件创建

首先,创建一个html文件:

<!DOCTYPE html>
<html lang="cn">
<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">
    <link rel="stylesheet" href="style.css">
    <script src="script.js" defer></script>
    <title>Document</title>
</head>
<body>
    <canvas id="canvas"></canvas>
</body>
</html>

同时,创建对应的CSS文件:

*{
    margin: 0;
    padding: 0;
}

body{
    background-color: #333;
    overflow: hidden;
}

#canvas{
    position:fixed;
    left:0;
    top:0;
}

最后,创建一个js文件,接下来,我们将在js文件中进行编辑。

2. 简单效果的实现

因为这是本系列的第一期,因此我会比较啰嗦地讲一些 Canvas 的方法调用。
首先,我们需要创建一个 canvas 对象。

var canvas = document.getElementById("canvas");
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
var ctx = canvas.getContext("2d");

首先,我们需要找到 Canvas 对象,然后为 Canvas 对象设置宽高。
接下来,我们需要获取到 Canvascontext 对象。getContext("2d") 对象是内建的 HTML5 对象,拥有多种绘制路径、矩形、圆形、字符以及添加图像的方法。我们在进行绘制时,需要调用它上面的属性和方法。

var particles = []
var count = parseInt(canvas.height/150*canvas.width/150);
var maxDis = 150;

接下来,我们创建一个particles数组,用来存储所有的粒子对象,然后规定粒子的数目在每150*150像素一颗,最后定义连线的最大距离是150像素。
在完成了上面一系列准备动作以后,我们需要设计一个 Particle 类。

class Particle{
    constructor(x,y) {
        this.x = x;
        this.y = y;
        this.directionY = 0.5 - Math.random();
        this.directionX = 0.5 - Math.random();
    }
}

我们首先定义了Particle类的构造方法,它定义了位置xy,速度变量directionXdirectionY
接下来,我们需要设计一个更新方法,用于更新粒子下一步的状态:

    update() {
        this.x += this.directionX;
        this.y += this.directionY;
    }

最后,我们需要为粒子对象设计一个draw方法,draw函数里直接调用了canvas的方法,用于在视图上绘制粒子:

    draw() {
        ctx.beginPath();
        ctx.arc(this.x,this.y,2,0,Math.PI*2);
        ctx.fillStyle = "white";
        ctx.fill();
    }

这个方法中,我们使用了一开始拿到的context对象,利用这个对象上的属性和方法绘制粒子。在绘制粒子时,我们首先调用ctx.beginPath();表示开始绘制,接下来调用ctx.arc(this.x,this.y,2,0,Math.PI*2);在粒子所在的位置绘制一个半径为2的圆,最后使用ctx.stroke();表明绘制结束。

在canvas中绘制圆形, 我们将使用以下方法:
arc(x,y,r,start,stop)

通过以上几个方法,我们就完成了Particle类的定义,完整的代码如下:

class Particle{
    constructor(x,y) {
        this.x = x;
        this.y = y;
        this.directionY = 0.5 - Math.random();
        this.directionX = 0.5 - Math.random();
    }
    update() {
        this.x += this.directionX;
        this.y += this.directionY;
    }
    draw() {
        ctx.beginPath();
        ctx.arc(this.x,this.y,2,0,Math.PI*2);
        ctx.fillStyle = "white";
        ctx.fill();
    }
}

接下来,我们需要实现一个创建粒子的方法:

function createParticle(){
    let x = Math.random() * canvas.width;
    let y = Math.random() * canvas.height;
    particles.push(new Particle(x, y));
}

我们每创建一个粒子,就将粒子放进我们的particles数组里。
下面,我们实现一个处理粒子的方法:

function handleParticle(){
    particles.forEach((element,index) => {
        element.update();
        element.draw();
        if(element.x < 0 || element.x > canvas.width){
            element.directionX = - element.directionX;
        }
        if(element.y < 0 || element.y > canvas.height) {
            element.directionY = - element.directionY;
        }
        particles.forEach((aElement,index) => {
    		distance = Math.sqrt( Math.pow(element.x - aElement.x,2) + Math.pow(element.y - aElement.y,2) );
    		if(distance < maxDis) {
        		ctx.beginPath();
        		ctx.strokeStyle = "rgba(255,255,255," + (1 - distance / maxDis) + ")";
        		ctx.moveTo(element.x,element.y);
        		ctx.lineTo(aElement.x,aElement.y);
        		ctx.lineWidth = 1;
        		ctx.stroke();
    		}
		})
    }); 
}

在粒子的处理函数中,我们通过forEach函数遍历每个粒子,然后分别调用粒子的update方法和draw方法,更新粒子的状态,并且绘制粒子。之后再做检查,如果粒子超出了canvas元素的边界,则通过改变它的速度方向使其反弹回去。

之后,我们还需要再进行一次内部遍历,检查当前粒子与其他粒子的距离,如果距离小于maxDis,则绘制粒子之间的线段。绘制的颜色可以将透明度设置为与距离有关,绘制的方法与上面的draw函数有异曲同工之妙,这里就不再啰嗦。

最后,我们通过调用一个定时器来完成粒子的动态效果:

function draw(){
    ctx.clearRect(0,0,canvas.width,canvas.height);
    //清空画布内容
    if(particles.length < count) {
        createParticle();
    }
    handleParticle();
}

setInterval(draw,10);

至此,我们已经完成了简单效果,即完成点的自由运动、以及连线的自动产生。这是实现了简单效果的完整代码:

var canvas = document.getElementById("canvas");
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
var ctx = canvas.getContext("2d");
var particles = []
var count = parseInt(canvas.height/150*canvas.width/150);
var maxDis = 150;

class Particle{
    constructor(x,y) {
        this.x = x;
        this.y = y;
        this.directionY = 0.5 - Math.random();
        this.directionX = 0.5 - Math.random();
    }
    update() {
        this.x += this.directionX;
        this.y += this.directionY;
    }
    draw() {
        ctx.beginPath();
        ctx.arc(this.x,this.y,2,0,Math.PI*2);
        ctx.fillStyle = "white";
        ctx.fill();
    }
}

function createParticle(){
    let x = Math.random() * canvas.width;
    let y = Math.random() * canvas.height;
    particles.push(new Particle(x, y));
}

function handleParticle(){
    particles.forEach((element,index) => {
        element.update();
        element.draw();
        if(element.x < 0 || element.x > canvas.width){
            element.directionX = - element.directionX;
        }
        if(element.y < 0 || element.y > canvas.height) {
            element.directionY = - element.directionY;
        }
        particles.forEach((aElement,index) => {
            distance = Math.sqrt( Math.pow(element.x - aElement.x,2) + Math.pow(element.y - aElement.y,2) );
            if(distance < maxDis) {
                ctx.beginPath();
                ctx.strokeStyle = "rgba(255,255,255," + (1 - distance / maxDis) + ")";
                ctx.moveTo(element.x,element.y);
                ctx.lineTo(aElement.x,aElement.y);
                ctx.lineWidth = 1;
                ctx.stroke();
            }
        })
    }); 
}

function draw(){
    ctx.clearRect(0,0,canvas.width,canvas.height);
    if(particles.length < count) {
        createParticle();
    }
    handleParticle();
}

setInterval(draw,10);

3. 复杂效果的实现

实现简单效果以后,接下来,我们将演示如何实现复杂一些的效果,即鼠标移入时,完成鼠标与周围点的连线。
这个功能其实不难,在实现时最重要的在于获取当前的鼠标位置
这里我们选择使用全局变量mouseXmouseY来保存鼠标信息。同时设定一个鼠标捕获最大距离maxMouseDis

var mouseX = -1,mouseY = -1;
var maxMouseDis = 250;

同时,通过为canvas对象添加如下的鼠标事件,

canvas.addEventListener("mousemove",function(e) {
    mouseX = e.clientX;
    mouseY = e.clientY;
})
canvas.addEventListener("mouseout",function(){
    mouseX = -1;
    mouseY = -1;
})

当鼠标在canvas元素上移动时,会实时更新鼠标位置,一旦鼠标移出canvas元素,我们就将mouseXmouseY置为-1作为标记。
添加完鼠标事件后,我们还需要设计一个鼠标处理函数:

function handleMouse(){
    if(mouseX == -1 || mouseY == -1) return;
    particles.forEach((element,index) => {
        let distance = Math.sqrt( Math.pow(element.x - mouseX,2) + Math.pow(element.y - mouseY,2) );
        if(distance < maxMouseDis) {
            ctx.beginPath();
            ctx.strokeStyle = "rgba(255,255,255," + (1 - distance / maxMouseDis) + ")";
            ctx.moveTo(element.x,element.y);
            ctx.lineTo(mouseX,mouseY);
            ctx.lineWidth = 1;
            ctx.stroke();
        }
    })
}

handleMouse函数中,我们需要遍历所有的粒子,一旦鼠标与粒子之间的距离小于设定的maxMouseDis,则开始连线,连线方式与上面handleParticle中一致。
最后,不要忘记在定时器函数draw中添加handleMouse

function draw(){
    ctx.clearRect(0,0,canvas.width,canvas.height);
    if(particles.length < count) {
        createParticle();
    }
    handleParticle();
    handleMouse();
}

通过上面一系列操作,我们已经完成了复杂效果,即鼠标移入时,完成鼠标与周围点的连线。这是完整的代码:

var canvas = document.getElementById("canvas");
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
var ctx = canvas.getContext("2d");
var particles = []
var count = parseInt(canvas.height/150*canvas.width/150);
var maxDis = 150;
var maxMouseDis = 250;
var mouseX = -1,mouseY = -1;

class Particle{
    constructor(x,y) {
        this.x = x;
        this.y = y;
        this.directionY = 0.5 - Math.random();
        this.directionX = 0.5 - Math.random();
    }
    update() {
        this.x += this.directionX;
        this.y += this.directionY;
    }
    draw() {
        ctx.beginPath();
        ctx.arc(this.x,this.y,2,0,Math.PI*2);
        ctx.fillStyle = "white";
        ctx.fill();
    }
}

function createParticle(){
    let x = Math.random

以上是关于前端 Canvas 训练营第一期:鼠标交互粒子背景效果的主要内容,如果未能解决你的问题,请参考以下文章

前端 Canvas 训练营第一期:鼠标交互粒子背景效果

前端 Canvas 训练营第一期:鼠标交互粒子背景效果

教程HTML5 Canvas旋涡粒子动画特效

Javascript - 交互式粒子徽标不起作用

Unity每日灵感第一期:IPointer_?_Handler接口实现有趣的鼠标交互

OpenHarmony - 基于ArkUI(JS)实现移动粒子效果背景