从 0 到 1 实现鼠标联动粒子动画

今天带大家探索一个炫酷的前端动画效果:鼠标联动粒子动画!这种效果不仅充满科技感,还能为网页增加互动性和视觉冲击力。鼠标轻轻移动,粒子会随之动态变化,线条连接点之间更是随着距离产生不同的透明度。

源代码地址:

https://codepen.io/MarcoGuglielmelli/pen/ExGYae

实现步骤

HTML

创建一个 canvas 和一个标题

html 复制代码
<div id="large-header" class="large-header">
  <canvas id="demo-canvas"></canvas>
  <h1 class="main-title">Connect <span class="thin">Three</span></h1>
</div>

引入动画库

复制代码
<script src="https://www.marcoguglie.it/Codepen/AnimatedHeaderBg/demo-1/js/TweenLite.min.js"></script>

CSS

设置页面的宽度和高度,并且给页面设置背景,将 title 水平垂直居中。

css 复制代码
.large-header {
  position: relative;
  width: 100%;
  background: #333;
  overflow: hidden;
  background-size: cover;
  background-position: center center;
  z-index: 1;
}

#large-header {
  background-image: url("https://www.marcoguglie.it/Codepen/AnimatedHeaderBg/demo-1/img/demo-1-bg.jpg");
}

.main-title {
  position: absolute;
  margin: 0;
  padding: 0;
  color: #f9f1e9;
  text-align: center;
  top: 50%;
  left: 50%;
  -webkit-transform: translate3d(-50%, -50%, 0);
  transform: translate3d(-50%, -50%, 0);
}

.main-title .thin {
  font-weight: 200;
}

Javascript

初始化 Canvas

在 JavaScript 中,我们首先要获取 Canvas 元素,并设置它的宽度和高度为当前浏览器窗口的宽度和高度。

  • 获取 Canvas 上下文:ctx = canvas.getContext('2d') 是获取 Canvas 2D 绘图上下文,我们用它来进行绘制。
  • 随机生成点:我们生成了一些点,并将它们的位置保存到 points 数组中。每个点都会有一个原始位置(originX 和 originY),用于后续的动画效果。
javascript 复制代码
var width,
  height,
  largeHeader,
  canvas,
  ctx,
  points,
  target,
  animateHeader = true;

// 初始化头部和Canvas
function initHeader() {
  width = window.innerWidth; // 获取浏览器的宽度
  height = window.innerHeight; // 获取浏览器的高度
  target = { x: width / 2, y: height / 2 }; // 设置动画的目标点为浏览器中心

  // 获取Canvas元素和设置其尺寸
  largeHeader = document.getElementById("large-header");
  largeHeader.style.height = height + "px"; // 设置容器的高度为浏览器的高度

  canvas = document.getElementById("demo-canvas");
  canvas.width = width;
  canvas.height = height;
  ctx = canvas.getContext("2d"); // 获取2d上下文,后续绘制需要用到它

  // 创建随机的点
  points = [];
  for (var x = 0; x < width; x += width / 20) {
    for (var y = 0; y < height; y += height / 20) {
      var px = x + (Math.random() * width) / 20; // x坐标带随机偏移
      var py = y + (Math.random() * height) / 20; // y坐标带随机偏移
      points.push({ x: px, originX: px, y: py, originY: py }); // 保存每个点的位置和原始位置
    }
  }
}

计算点之间的距离和连接关系

我们需要为每个点找到离它最近的 5 个点,然后通过这些点来绘制连接线。这是实现动画的关键部分。

代码实现:

javascript 复制代码
// 计算每个点的5个最近邻
for (var i = 0; i < points.length; i++) {
  var closest = [];
  var p1 = points[i];
  for (var j = 0; j < points.length; j++) {
    var p2 = points[j];
    if (p1 !== p2) {
      // 排除自己
      var placed = false;
      for (var k = 0; k < 5; k++) {
        if (!placed) {
          if (closest[k] === undefined) {
            closest[k] = p2; // 如果还没有5个邻近点,则直接添加
            placed = true;
          }
        }
      }

      for (var k = 0; k < 5; k++) {
        if (!placed) {
          if (getDistance(p1, p2) < getDistance(p1, closest[k])) {
            closest[k] = p2; // 更新最近的邻近点
            placed = true;
          }
        }
      }
    }
  }
  p1.closest = closest; // 保存每个点的5个最近邻点
}

代码解释:

  • 排除自己:if (p1 !== p2) 这段代码确保点不会与自身连接。
  • 计算距离:getDistance(p1, p2) 是我们用来计算两个点之间的距离的函数。它通过勾股定理计算点间的欧几里得距离。
  • 选择最近邻点:我们为每个点找到它的 5 个最近邻点,保存到 closest 数组中。

添加事件监听器

为了使得动画可以响应用户的输入,我们需要监听鼠标的移动和窗口的变化。

代码实现:

javascript 复制代码
function addListeners() {
  if (!("ontouchstart" in window)) {
    window.addEventListener("mousemove", mouseMove); // 监听鼠标移动
  }
  window.addEventListener("scroll", scrollCheck); // 监听滚动事件
  window.addEventListener("resize", resize); // 监听窗口大小变化
}

function mouseMove(e) {
  var posx =
    e.pageX ||
    e.clientX + document.body.scrollLeft + document.documentElement.scrollLeft;
  var posy =
    e.pageY ||
    e.clientY + document.body.scrollTop + document.documentElement.scrollTop;
  target.x = posx; // 更新鼠标位置
  target.y = posy;
}

function scrollCheck() {
  if (document.body.scrollTop > height)
    animateHeader = false; // 当页面滚动超过一定高度,停止动画
  else animateHeader = true;
}

function resize() {
  width = window.innerWidth;
  height = window.innerHeight;
  largeHeader.style.height = height + "px"; // 重新设置容器的高度
  canvas.width = width;
  canvas.height = height; // 重新设置Canvas的宽高
}

代码解释:

  • mousemove:监听鼠标移动事件,将鼠标的位置作为目标点 target.x 和 target.y 更新。
  • scrollCheck:根据页面的滚动位置来决定是否继续播放动画。
  • resize:在窗口尺寸变化时,重新设置 Canvas 的宽高,并调整动画的显示区域。

动画实现

在这个步骤中,我们使用 requestAnimationFrame 来实现不断渲染的动画效果。我们会清除画布,重新绘制所有的点,并根据鼠标位置来调整每个点的透明度。

代码实现:

javascript 复制代码
function initAnimation() {
  animate(); // 启动动画
  for (var i in points) {
    shiftPoint(points[i]); // 给每个点添加偏移动画
  }
}

function animate() {
  if (animateHeader) {
    ctx.clearRect(0, 0, width, height); // 清除画布
    for (var i in points) {
      if (Math.abs(getDistance(target, points[i])) < 4000) {
        points[i].active = 0.3;
        points[i].circle.active = 0.6;
      } else if (Math.abs(getDistance(target, points[i])) < 20000) {
        points[i].active = 0.1;
        points[i].circle.active = 0.3;
      } else if (Math.abs(getDistance(target, points[i])) < 40000) {
        points[i].active = 0.02;
        points[i].circle.active = 0.1;
      } else {
        points[i].active = 0;
        points[i].circle.active = 0;
      }

      drawLines(points[i]); // 绘制连接线
      points[i].circle.draw(); // 绘制圆圈
    }
  }
  requestAnimationFrame(animate); // 请求下一帧动画
}

function shiftPoint(p) {
  TweenLite.to(p, 1 + 1 * Math.random(), {
    x: p.originX - 50 + Math.random() * 100,
    y: p.originY - 50 + Math.random() * 100,
    ease: Circ.easeInOut,
    onComplete: function () {
      shiftPoint(p); // 每次偏移完成后递归调用,继续动画
    },
  });
}

代码解释:

  • clearRect:每一帧渲染之前,我们清除整个画布。
  • requestAnimationFrame:浏览器提供的一个函数,用于在下一帧重新调用 animate,实现流畅的动画效果。
  • shiftPoint:让每个点随机移动,产生动画效果。TweenLite 是一个动画库,它帮助我们实现平滑的动画过渡。

绘制圆圈和连接线

最终,我们需要绘制每个点的圆圈和点与点之间的连线。

代码实现:

javascript 复制代码
function drawLines(p) {
  if (!p.active) return; // 如果点不可见,则不绘制
  for (var i in p.closest) {
    ctx.beginPath();
    ctx.moveTo(p.x, p.y); // 连接线起点
    ctx.lineTo(p.closest[i].x, p.closest[i].y); // 连接线终点
    ctx.strokeStyle = "rgba(156,217,249," + p.active + ")"; // 设置连接线颜色
    ctx.stroke(); // 绘制连接线
  }
}

function Circle(pos, rad, color) {
  var _this = this;

  // 构造函数
  (function () {
    _this.pos = pos || null;
    _this.radius = rad || null;
    _this.color = color || null;
  })();

  this.draw = function () {
    if (!_this.active) return;
    ctx.beginPath();
    ctx.arc(_this.pos.x, _this.pos.y, _this.radius, 0, 2 * Math.PI, false);
    ctx.fillStyle = "rgba(156,217,249," + _this.active + ")"; // 设置圆圈颜色
    ctx.fill(); // 绘制圆圈
  };
}

代码解释:

  • drawLines:根据点与点之间的距离,绘制它们之间的连线。如果两个点距离较近,连接线的透明度较高。
  • Circle:我们为每个点创建一个圆形对象,并通过 draw 方法绘制该圆。

总结

通过以上步骤,我们实现了一个基于 Canvas 的动态点线动画。每个点的位置根据鼠标位置发生变化,并且每两个点之间会根据距离绘制不同透明度的连接线。通过调整点的随机偏移,我们为每个点增加了动画效果。

相关推荐
顾安r2 小时前
11.8 脚本网页 星际逃生
c语言·前端·javascript·flask
im_AMBER2 小时前
React 17
前端·javascript·笔记·学习·react.js·前端框架
一雨方知深秋2 小时前
2.fs模块对计算机硬盘进行读写操作(Promise进行封装)
javascript·node.js·promise·v8·cpython
顺凡5 小时前
删一个却少俩:Antd Tag 多节点同时消失的原因
前端·javascript·面试
前端大卫5 小时前
动态监听DOM元素高度变化
前端·javascript
Cxiaomu5 小时前
React Native App 图表绘制完整实现指南
javascript·react native·react.js
ken22325 小时前
在被窝里使用笔记本电脑,容易损坏键盘?
计算机外设·电脑
qq. 28040339846 小时前
vue介绍
前端·javascript·vue.js
某林2126 小时前
如何使用ROS 2与STM32进行串口通信,并实现通过键盘按键‘1’来控制LED灯开关
stm32·嵌入式硬件·计算机外设
Mr.Jessy6 小时前
Web APIs 学习第五天:日期对象与DOM节点
开发语言·前端·javascript·学习·html