news 2026/9/14 11:23:18

Canvas粒子系统实战:用p5.js实现可交互飘沙特效

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Canvas粒子系统实战:用p5.js实现可交互飘沙特效

简介:这是一份面向前端开发初学者与进阶者的交互式视觉特效实战资源,聚焦于动态飘沙特效与粒子烟雾动画的实现,适用于网页背景增强、活动页开场动效或创意交互模块开发。资源以轻量级HTML+JS方案构建,无需复杂框架依赖,兼容主流浏览器,特别适合希望快速掌握p5.js基础绘图与jQuery DOM操作结合实践的开发者。压缩包共5个文件(4个JS脚本+1个HTML主入口),总大小仅244KB,其中p5.js提供粒子系统核心能力,jquery.min.js与jquery.qrcode.min.js支撑DOM控制与扩展可能性,protect.js封装了飘沙逻辑,index.html为即开即用的演示页。目前已有391人学习下载,资源结构简洁、代码注释清晰、效果开箱即用,可直接嵌入项目调试,也可作为理解Canvas粒子运动、时间轴控制与事件响应机制的优质教学案例。

1. 飘沙特效不是“动效插件”,而是粒子系统在 Canvas 上的实时演算

你拖进页面一个index.html,打开就看到沙粒如雾般升腾、旋转、受鼠标牵引——这不是 CSS 动画帧堆出来的假动作,也不是 Three.js 渲染的静态粒子云。它用纯 JavaScript 在<canvas>上每秒重绘 60 帧,每个沙粒都是独立生命周期的物理对象:有质量、速度、加速度、衰减率、碰撞反弹系数。p5.js 提供了向量运算和渲染循环骨架,jQuery 负责 DOM 绑定与事件代理(比如鼠标移动触发力场扰动),而protect.js并非加密脚本,实为粒子边界检测与回收逻辑——当沙粒移出视口或透明度低于阈值,立即从数组中剔除并复用内存。这套组合不依赖 WebGPU 或 WebGL,兼容 IE11+,但性能瓶颈明确:粒子数超 3000 时,Chrome 会明显掉帧。适合做 Landing Page 的首屏视觉锚点、活动页倒计时背景、或作为 Vue/React 组件内嵌的轻量级装饰层,而非需要交互反馈的 UI 控件。


2. p5.js 粒子系统核心:从向量建模到生命周期控制

2.1 沙粒实体类设计:位置、速度、生命周期三要素不可拆分

飘沙特效的本质是「带衰减的运动学模拟」。每个沙粒不是 DOM 元素,而是Particle类实例,其属性必须包含:

  • pos:p5.Vector 实例,记录当前坐标(x, y)
  • vel:p5.Vector 实例,记录瞬时速度(dx, dy)
  • life:浮点数,初始为 255,每帧减decayRate,归零即销毁
  • size:随life线性缩放的半径,实现“渐隐”效果
class Particle { constructor(x, y) { this.pos = createVector(x, y); this.vel = p5.Vector.random2D().mult(random(0.5, 2)); // 初始随机方向与速度 this.life = 255; this.decayRate = random(0.8, 1.5); // 每帧衰减量,决定粒子存活时长 } update() { this.pos.add(this.vel); // 位置 = 位置 + 速度 this.vel.mult(0.99); // 速度衰减,模拟空气阻力 this.life -= this.decayRate; // 生命周期递减 } isDead() { return this.life < 0; } display() { noStroke(); fill(255, 255, 255, this.life); // alpha 通道随 life 变化 ellipse(this.pos.x, this.pos.y, map(this.life, 0, 255, 0.5, 3)); // 尺寸随生命值缩小 } }

注意p5.Vector.random2D()返回单位向量,mult()扩展为实际速度;map()life(0–255)映射到像素尺寸(0.5–3px),避免粒子缩成不可见点后仍占用计算资源。

2.2 粒子发射器:按需生成 + 边界回收机制

Emitter类管理粒子池,关键逻辑不在“发射”,而在“回收”。protect.js的核心就是这段:

// protect.js 片段:粒子边界检测与回收 function checkBounds(p) { if (p.pos.x < 0 || p.pos.x > width || p.pos.y < 0 || p.pos.y > height) { p.life = 0; // 强制标记死亡,避免越界计算 } } // 主循环中调用 for (let i = particles.length - 1; i >= 0; i--) { particles[i].update(); checkBounds(particles[i]); if (particles[i].isDead()) { particles.splice(i, 1); // 从尾部向前删除,避免索引错位 } }

splice(i, 1)从数组末尾开始删除,是因为粒子数组在draw()循环中持续push()新粒子,若从前向后遍历删除,会导致后续元素索引偏移,漏删或重复删。这是前端粒子系统最常踩的坑——用filter()重建数组虽安全但 GC 压力大,splice原地删除是平衡性能与稳定性的常见做法。

2.3 力场扰动:鼠标作为动态引力源的数学实现

飘沙的“被吸引感”来自mouseX/mouseY构建的二维力场。不是简单设置vel,而是用牛顿万有引力公式简化版:

// 在 update() 中加入力场计算 applyForce(x, y, strength = 0.1) { const force = p5.Vector.sub(createVector(x, y), this.pos); // 力向量 = 目标 - 当前 const distance = force.mag(); // 距离 if (distance < 50 && distance > 0) { // 有效作用半径 50px force.normalize(); // 单位化方向 force.mult(strength * (1 - distance / 50)); // 力大小随距离线性衰减 this.vel.add(force); } }

strength * (1 - distance / 50)是关键:距离越近力越大,但不会无限趋近于无穷大(避免粒子瞬间飞向鼠标)。normalize()mult()保证力严格沿连线方向施加,这是真实物理感的来源。若直接this.vel.x = x - this.pos.x,粒子会生硬“跳转”,失去飘散质感。


3. jQuery 与原生 JS 协同:DOM 事件绑定与性能隔离策略

3.1 为什么用 jQuery 而非addEventListener?兼容性与事件委托的实际价值

项目中jquery.min.js并非为了写$('#id').click()这种基础操作,而是解决两个具体问题:

  1. IE11 下mouseenter/mouseleave事件冒泡异常:原生addEventListener在 IE11 中对<canvas>mouseleave响应延迟或丢失,jQuery 的.on('mouseleave', ...)内部做了事件代理兜底;
  2. 移动端 touch 事件统一处理jquery.qrcode.min.js是二维码生成库,但它的touchstart/touchmove绑定逻辑被复用到飘沙上——通过$.event.special.touchstart扩展,将touchstart映射为mousedowntouchmove映射为mousemove,避免为 iOS/Android 单独写两套事件监听。
// index.html 中实际使用的事件绑定 $(document).on('mousemove touchmove', function(e) { const x = e.originalEvent.touches ? e.originalEvent.touches[0].clientX : e.clientX; const y = e.originalEvent.touches ? e.originalEvent.touches[0].clientY : e.clientY; // 传入 p5.js 的 mouseMoved 函数 if (typeof mouseMoved === 'function') mouseMoved(x, y); });

提示e.originalEvent.touches判断是否为触摸事件,clientX/Y获取坐标。jQuery 自动处理了pageX/pageYclientX/clientY在不同浏览器中的差异,这是它在 2024 年仍被轻量项目选用的核心原因——不是语法糖,而是跨端事件一致性保障。

3.2 防抖与节流:避免高频事件拖垮粒子系统

鼠标移动每秒触发 60+ 次,若每次都调用applyForce(),粒子会因力叠加过猛而炸开。protect.js中的防抖逻辑如下:

// protect.js 防抖节流 let lastForceTime = 0; const FORCE_INTERVAL = 16; // 60fps 对应 16ms function safeApplyForce(x, y) { const now = Date.now(); if (now - lastForceTime > FORCE_INTERVAL) { particles.forEach(p => p.applyForce(x, y, 0.05)); lastForceTime = now; } }

FORCE_INTERVAL = 16requestAnimationFrame的刷新周期对齐,确保力场更新频率不超过渲染帧率。若设为100(10fps),粒子响应迟滞;若设为1(无节流),CPU 占用飙升。这个参数必须根据目标设备性能实测调整,低端安卓机建议设为32

3.3 jQuery 与 p5.js 的生命周期桥接:setup()draw()如何响应 DOM 尺寸变化

index.html<canvas>默认宽高为window.innerWidth/Height,但用户缩放浏览器或切换横竖屏时需重置。jQuery 提供了可靠的resize事件监听:

$(window).resize(function() { // 通知 p5.js 重新设置 canvas 尺寸 if (typeof resizeCanvas === 'function') { resizeCanvas(); } }); // p5.js 中的 resizeCanvas 函数 function resizeCanvas() { const w = window.innerWidth; const h = window.innerHeight; resizeCanvas(w, h); // p5.js 原生方法 // 重置粒子发射器,避免旧粒子在新画布外继续计算 particles = []; for (let i = 0; i < 200; i++) { particles.push(new Particle(random(w), random(h))); } }

resizeCanvas()是 p5.js 内置函数,但必须配合手动清空粒子数组——否则旧粒子仍在pos.x > 新宽度的坐标上运算,造成 CPU 空转。这是 jQuery 与 p5.js 协作中最易忽略的耦合点。


4. 源码包结构解析与可定制化改造路径

4.1 文件职责拆解表:哪些文件可删、哪些必须保留

文件名技术职责是否可删除替换方案
p5.js核心渲染引擎,提供 Vector、Canvas、动画循环❌ 不可删必须用 p5.js v1.9.0+(低版本无p5.Vector.random2D
jquery.min.jsDOM 事件绑定、跨浏览器兼容层✅ 可删改用原生addEventListener+TouchEventpolyfill
jquery.qrcode.min.js二维码生成(与飘沙无关)✅ 可删删除后需注释掉index.html中相关 script 标签
protect.js边界检测、粒子回收、力场节流❌ 不可删若删除,粒子越界后持续计算,内存泄漏
index.html页面结构、Canvas 容器、资源加载顺序❌ 不可删<script>加载顺序必须为:p5.js → jquery → protect.js → 自定义脚本

提示jquery.qrcode.min.js在此项目中实际未被调用,属于冗余文件。删除后需检查index.html中是否有$('#qrcode').qrcode(...)类代码,若有则一并移除。

4.2 参数调优对照表:5 个关键变量对视觉效果的影响

参数名默认值调小效果调大效果推荐范围(PC 端)推荐范围(移动端)
particleCount(初始粒子数)300稀疏,飘散感弱卡顿,帧率下降200–500100–250
decayRate(生命周期衰减)1.2粒子存留时间长,堆积感强粒子一闪即逝,缺乏连贯性0.8–1.50.5–1.0
forceStrength(鼠标力场强度)0.05响应迟钝,几乎无牵引粒子剧烈抖动,失去“沙”质感0.02–0.080.01–0.04
airResistance(空气阻力)0.99运动惯性大,轨迹长运动僵硬,缺乏飘逸感0.98–0.9950.97–0.99
maxParticles(最大粒子数)1000无上限,内存持续增长限制峰值,防止 OOM800–1200400–600

修改方式:在sketch.js(或index.html内联脚本)中搜索new Particle循环,调整初始化数量;在Particle.update()中修改vel.mult(0.99)的系数;在safeApplyForce()中调整0.05

4.3 从“飘沙”到“烟雾”的三步材质改造

原始效果偏“干沙”,要实现“烟雾”质感,需改动三个层面:

  1. 颜色通道:将fill(255, 255, 255, this.life)改为fill(200, 200, 220, this.life * 0.7),降低明度、增加蓝紫调、减弱 alpha 透过度;
  2. 运动模型:在update()中加入布朗运动扰动:
    this.vel.add(createVector(random(-0.1, 0.1), random(-0.1, 0.1))); // 每帧微小随机扰动
  3. 渲染叠加:用blendMode(ADD)替代默认BLEND,让粒子重叠区域自动提亮,模拟烟雾透光感:
    function draw() { blendMode(ADD); particles.forEach(p => p.display()); blendMode(BLEND); // 恢复默认模式,避免影响其他元素 }

这三步改造后,粒子不再呈现颗粒分明的“沙粒”,而是边缘弥散、明暗交融的“烟雾团”,且无需引入额外图像资源或滤镜。


5. 性能诊断与移动端适配实战技巧

5.1 用 Chrome DevTools 定位粒子性能瓶颈

打开index.html→ F12 →Performance 标签页→ 点击录制 → 操作鼠标快速移动 → 停止录制。重点关注:

  • Main 线程火焰图:若draw()函数占据超过 16ms(1 帧时长),说明update()display()计算超载;
  • Memory 标签页:勾选Memory heap,观察粒子数组长度是否持续增长(未回收);
  • Rendering 标签页:开启Paint flashing,确认<canvas>是否整块重绘(正常),而非局部闪烁(异常)。

典型问题案例:particles.length从 300 涨到 2000+,火焰图显示checkBounds()耗时突增 → 原因是protect.jssplice()未从尾部遍历,改用for (let i = arr.length; i--; )即可修复。

5.2 移动端触控优化:禁用双指缩放与滚动穿透

iOS Safari 默认允许双指缩放<canvas>,导致飘沙区域失焦。在index.html<head>中添加:

<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">

同时阻止 touchmove 默认行为,避免滚动穿透:

// 在 jQuery 事件绑定后追加 $(document).on('touchmove', function(e) { if ($(e.target).closest('canvas').length) { e.preventDefault(); } });

closest('canvas')确保只拦截 Canvas 区域的 touchmove,不影响页面其他滚动组件。

5.3 降级方案:当p5.js加载失败时的 CSS 备用动画

并非所有环境都能执行 Canvas 脚本(如 strict CSP 策略)。在index.html中加入优雅降级:

<!-- 备用 CSS 飘沙 --> <style> .sand-fallback { position: fixed; top: 0; left: 0; width: 100%; height: 100%; pointer-events: none; z-index: -1; } .sand-fallback::before { content: ""; position: absolute; top: 0; left: 0; width: 100%; height: 100%; background: radial-gradient(circle at 50% 50%, rgba(255,255,255,0.1) 0%, transparent 70%); animation: sandFloat 15s infinite linear; } @keyframes sandFloat { 0% { transform: translate(0, 0) rotate(0deg); } 100% { transform: translate(100vw, 100vh) rotate(360deg); } } </style> <div class="sand-fallback"></div> <script> // 检测 p5.js 是否加载成功 if (typeof p5 === 'undefined') { document.querySelector('.sand-fallback').style.display = 'block'; } else { document.querySelector('.sand-fallback').style.display = 'none'; } </script>

该 CSS 方案用radial-gradient模拟沙粒光晕,animation模拟缓慢位移,虽无交互,但保持视觉连续性。关键在于pointer-events: none确保不阻断下方按钮点击。

粒子系统不是炫技玩具,而是前端工程师对“时间”“空间”“状态”三重维度的编程实践——每一粒沙的诞生、运动、消亡,都在重演物理世界的确定性与随机性。当你把decayRate1.2调到0.9,看到粒子在屏幕上多悬浮半秒,那不是参数变化,是你亲手延长了一段数字生命的呼吸节奏。

本文还有配套的精品资源,点击获取

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/14 11:22:33

Vector 在 Ubuntu 上的安装与部署指南:五种安装方式与运维实战

Vector 在 Ubuntu 上的安装与部署指南&#xff1a;五种安装方式与运维实战 【免费下载链接】vector A high-performance observability data pipeline. 项目地址: https://gitcode.com/GitHub_Trending/vect/vector Vector 是一款高性能的可观测性数据管道&#xff08;o…

作者头像 李华
网站建设 2026/9/14 11:22:28

WTF Solidity 极简入门:第 42 讲 分账合约 PaymentSplit 实战指南

WTF Solidity 极简入门&#xff1a;第 42 讲 分账合约 PaymentSplit 实战指南 【免费下载链接】WTF-Solidity WTF Solidity 极简入门教程&#xff0c;供小白们使用。Now supports English! 官网: https://wtf.academy 项目地址: https://gitcode.com/GitHub_Trending/wt/WTF-…

作者头像 李华
网站建设 2026/9/14 11:20:16

EtherCAT转CC-Link IE Field网关实战:加工厂异构总线对接与配置指南

车间里一台三菱iQ-R系列PLC底下的CC-Link IE Field网络已经稳定跑了五六年&#xff0c;突然要并进几台只有EtherCAT接口的新伺服和视觉系统&#xff0c;这时候很多工程师第一个想骂人的问题就是&#xff1a;这两种总线到底怎么对话&#xff1f;我这两年帮几个加工厂客户做过类似…

作者头像 李华
网站建设 2026/9/14 11:18:57

Unity口型同步工程化方案:MFCC+DTW驱动7维唇形控制

1. 这不是又一个“口型驱动”插件&#xff0c;而是解决Unity里真实痛点的工程化方案我在做AR虚拟人项目时&#xff0c;被口型同步问题卡了整整三周。不是模型不动&#xff0c;是动得“太假”——语音开始0.2秒后嘴才张&#xff0c;音节“p”“b”该爆破时下巴却懒洋洋下垂&…

作者头像 李华
网站建设 2026/9/14 11:17:10

PHP毕业设计全流程避坑指南:从选型到答辩的实战要点

毕业设计选PHP&#xff0c;说白了就是选了一条“看起来容易、做起来全是坑”的路。尤其是最近几年&#xff0c;学校里的题目越来越卷&#xff0c;纯搞一个增删改查的图书管理系统已经很难拿高分了。很多同学一开始觉得PHP写起来快&#xff0c;结果栽在环境配置上&#xff0c;或…

作者头像 李华