Canvas绘制炫酷背景

该文章也发布在我的博客上了哦:博客地址

效果预览

我们在很多网站上也许会见到如下背景:
预览
这个背景其实很简单就能复刻,使用Canvas绘制,之后使用requestAnimationFrame来做帧动画平滑过渡就可以了。

代码解析

画布相关

<canvas id="canvas"></canvas>

这一段就是布置一张画布,id为canvas

const dpr = window.devicePixelRatio;  

这是获取当前设备的缩放,来保证图像清晰的

class Graphic {  
  ...
}  

这个就是我封装的一个类,包含了初始化,绘制,清空以及更新位置。

构造方法:

constructor() {  
    //点的数量  
    this.graphicNum = 40;  
    this._canvas = document.querySelector("#canvas");  
    this._ctx = this._canvas.getContext("2d");  
    this._canvas.width = window.innerWidth * dpr;  
    this._canvas.height = window.innerHeight * dpr;  
    this._ctx.scale(dpr, dpr);  
  
    this._points = new Array(this.graphicNum).fill(0).map(() => ({  
      ...randomInnerPoint(),//起点坐标  
      target: randomInnerPoint(),//终点坐标  
      speed: 2.5, // 控制速度  
    }));  
  }  

这里我默认端点数量为40,使用getContext方法获取到上下文对象对画布进行绘制。points中存放了起点坐标、终点坐标以及动画的速度。

这里需要注意:宽高的设置不能使用style.XXXX,因为我们需要设置的是画布的宽高,而不是外围背景的宽高

绘制、清除以及更新

// 绘制,属性为相距最远的路线  
  draw(maxDis = 200) {  
    //绘制线条  
    this._points.forEach((point, index) => {  
      const point1 = point;  
      for (let i = index; i < this.graphicNum; i++) {  
        const point2 = this._points[i % this.graphicNum];  
  
        this._ctx.beginPath();  
        this._ctx.moveTo(point1.x, point1.y);  
        this._ctx.lineTo(point2.x, point2.y);  
        const dis = Math.sqrt(  
            Math.pow(point1.x - point2.x, 2) + Math.pow(point1.y - point2.y, 2)  
        );  
        //控制透明度  
        this._ctx.strokeStyle = `rgba(200,200,200,${1 - dis / maxDis})`;  
        this._ctx.lineWidth = 1;  
        this._ctx.stroke();  
      }  
    });  
  
    // 绘制端点  
    this._points.forEach((point) => {  
      this._ctx.beginPath();  
      this._ctx.arc(point.x, point.y, 4, 0, 2 * Math.PI);  
  
      this._ctx.fillStyle = `rgba(200, 200, 200)`;  
      this._ctx.fill();  
    });  
  }  
  
  clear() {  
    //清空画布  
    this._ctx.clearRect(0, 0, this._canvas.width, this._canvas.height);  
  }  
  
  update() {  
    this._points.forEach(point => {  
      // 计算距离  
      const dx = point.target.x - point.x;  
      const dy = point.target.y - point.y;  
      const distance = Math.sqrt(dx * dx + dy * dy);  
  
      // 如果点到达目标位置,生成新的目标位置  
      if (distance < point.speed) {  
        point.target = randomInnerPoint();  
      } else {  
        // 按照速度移动点的位置  
        point.x += (dx / distance) * point.speed;  
        point.y += (dy / distance) * point.speed;  
      }  
    });  
  }  

这里绘制的时候,我将所有的点都与其后方的点之间连接了一条线,这条线的透明度是由距离控制的,maxDis就是最远距离
清空就是将整个画布全部清空
更新函数就是对每个点进行位置计算,然后移动位置,位置的动画交由requestAnimationFrame控制。如果这个点到达了目标位置,那么重新给一个目标。这里计算是否到达终点是计算的当前距离对速度的大小,不然会出现一些抖动现象。

完整代码

<script setup>  
import {onMounted} from "vue";  
  
const dpr = window.devicePixelRatio;  
  
function randomInnerPoint() {  
  return {  
    x: Math.random() * window.innerWidth,  
    y: Math.random() * window.innerHeight,  
  };  
}  
  
class Graphic {  
  constructor() {  
    //点的数量  
    this.graphicNum = 40;  
    this._canvas = document.querySelector("#canvas");  
    this._ctx = this._canvas.getContext("2d");  
    this._canvas.width = window.innerWidth * dpr;  
    this._canvas.height = window.innerHeight * dpr;  
    this._ctx.scale(dpr, dpr);  
  
    this._points = new Array(this.graphicNum).fill(0).map(() => ({  
      ...randomInnerPoint(),//起点坐标  
      target: randomInnerPoint(),//终点坐标  
      speed: 2.5, // 控制速度  
    }));  
  }  
  
  // 绘制,属性为相距最远的路线  
  draw(maxDis = 200) {  
    //绘制线条  
    this._points.forEach((point, index) => {  
      const point1 = point;  
      for (let i = index; i < this.graphicNum; i++) {  
        const point2 = this._points[i % this.graphicNum];  
  
        this._ctx.beginPath();  
        this._ctx.moveTo(point1.x, point1.y);  
        this._ctx.lineTo(point2.x, point2.y);  
        const dis = Math.sqrt(  
            Math.pow(point1.x - point2.x, 2) + Math.pow(point1.y - point2.y, 2)  
        );  
        //控制透明度  
        this._ctx.strokeStyle = `rgba(200,200,200,${1 - dis / maxDis})`;  
        this._ctx.lineWidth = 1;  
        this._ctx.stroke();  
      }  
    });  
  
    // 绘制端点  
    this._points.forEach((point) => {  
      this._ctx.beginPath();  
      this._ctx.arc(point.x, point.y, 4, 0, 2 * Math.PI);  
  
      this._ctx.fillStyle = `rgba(200, 200, 200)`;  
      this._ctx.fill();  
    });  
  }  
  
  clear() {  
    //清空画布  
    this._ctx.clearRect(0, 0, this._canvas.width, this._canvas.height);  
  }  
  
  update() {  
    this._points.forEach(point => {  
      // 计算距离  
      const dx = point.target.x - point.x;  
      const dy = point.target.y - point.y;  
      const distance = Math.sqrt(dx * dx + dy * dy);  
  
      // 如果点到达目标位置,生成新的目标位置  
      if (distance < point.speed) {  
        point.target = randomInnerPoint();  
      } else {  
        // 按照速度移动点的位置  
        point.x += (dx / distance) * point.speed;  
        point.y += (dy / distance) * point.speed;  
      }  
    });  
  }  
}  
  
function initBackGraphic() {  
  const graphics = new Graphic();  
  let animationFrameId;  
  
  function animate() {  
    graphics.clear();  
    graphics.update();  
    graphics.draw();  
    // 递归调用下一帧  
    animationFrameId = requestAnimationFrame(animate);  
  }  
  
  animate(); // 开始动画  
}  
  
onMounted(() => {  
  initBackGraphic()  
});  
  
</script>  
  
<template>  
  <canvas id="canvas"></canvas>  
</template>  
  
<style scoped>  
#canvas {  
  position: fixed;  
  box-sizing: border-box;  
  background-color: #363636;  
  width: 100%;  
  height: 100%;  
}  
</style>

参考:使用canvas完成基本绘图【渡一教育】

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值