从 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 的动态点线动画。每个点的位置根据鼠标位置发生变化,并且每两个点之间会根据距离绘制不同透明度的连接线。通过调整点的随机偏移,我们为每个点增加了动画效果。

相关推荐
程序员_三木6 分钟前
使用 Three.js 创建圣诞树场景
开发语言·前端·javascript·ecmascript·three
赵大仁35 分钟前
深入理解 Vue 3 中的具名插槽
前端·javascript·vue.js·react.js·前端框架·ecmascript·html5
秋雨凉人心8 小时前
简单发布一个npm包
前端·javascript·webpack·npm·node.js
哥谭居民000110 小时前
将一个组件的propName属性与父组件中的variable变量进行双向绑定的vue3(组件传值)
javascript·vue.js·typescript·npm·node.js·css3
踢足球的,程序猿10 小时前
Android native+html5的混合开发
javascript
前端没钱10 小时前
探索 ES6 基础:开启 JavaScript 新篇章
前端·javascript·es6
一条不想当淡水鱼的咸鱼11 小时前
taro中实现带有途径点的路径规划
javascript·react.js·taro
土豆炒马铃薯。11 小时前
【Vue】前端使用node.js对数据库直接进行CRUD操作
前端·javascript·vue.js·node.js·html5
温轻舟12 小时前
前端开发 -- 自动回复机器人【附完整源码】
前端·javascript·css·机器人·html·交互·温轻舟
赵大仁12 小时前
深入解析 Vue 3 的核心原理
前端·javascript·vue.js·react.js·ecmascript