简介:这是一份面向前端开发初学者与进阶者的交互式视觉特效实战资源,聚焦于动态飘沙特效与粒子烟雾动画的实现,适用于网页背景增强、活动页开场动效或创意交互模块开发。资源以轻量级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()这种基础操作,而是解决两个具体问题:
- IE11 下
mouseenter/mouseleave事件冒泡异常:原生addEventListener在 IE11 中对<canvas>的mouseleave响应延迟或丢失,jQuery 的.on('mouseleave', ...)内部做了事件代理兜底; - 移动端 touch 事件统一处理:
jquery.qrcode.min.js是二维码生成库,但它的touchstart/touchmove绑定逻辑被复用到飘沙上——通过$.event.special.touchstart扩展,将touchstart映射为mousedown,touchmove映射为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/pageY与clientX/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 = 16与requestAnimationFrame的刷新周期对齐,确保力场更新频率不超过渲染帧率。若设为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.js | DOM 事件绑定、跨浏览器兼容层 | ✅ 可删 | 改用原生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–500 | 100–250 |
decayRate(生命周期衰减) | 1.2 | 粒子存留时间长,堆积感强 | 粒子一闪即逝,缺乏连贯性 | 0.8–1.5 | 0.5–1.0 |
forceStrength(鼠标力场强度) | 0.05 | 响应迟钝,几乎无牵引 | 粒子剧烈抖动,失去“沙”质感 | 0.02–0.08 | 0.01–0.04 |
airResistance(空气阻力) | 0.99 | 运动惯性大,轨迹长 | 运动僵硬,缺乏飘逸感 | 0.98–0.995 | 0.97–0.99 |
maxParticles(最大粒子数) | 1000 | 无上限,内存持续增长 | 限制峰值,防止 OOM | 800–1200 | 400–600 |
修改方式:在sketch.js(或index.html内联脚本)中搜索new Particle循环,调整初始化数量;在Particle.update()中修改vel.mult(0.99)的系数;在safeApplyForce()中调整0.05。
4.3 从“飘沙”到“烟雾”的三步材质改造
原始效果偏“干沙”,要实现“烟雾”质感,需改动三个层面:
- 颜色通道:将
fill(255, 255, 255, this.life)改为fill(200, 200, 220, this.life * 0.7),降低明度、增加蓝紫调、减弱 alpha 透过度; - 运动模型:在
update()中加入布朗运动扰动:this.vel.add(createVector(random(-0.1, 0.1), random(-0.1, 0.1))); // 每帧微小随机扰动 - 渲染叠加:用
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.js中splice()未从尾部遍历,改用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确保不阻断下方按钮点击。
粒子系统不是炫技玩具,而是前端工程师对“时间”“空间”“状态”三重维度的编程实践——每一粒沙的诞生、运动、消亡,都在重演物理世界的确定性与随机性。当你把decayRate从1.2调到0.9,看到粒子在屏幕上多悬浮半秒,那不是参数变化,是你亲手延长了一段数字生命的呼吸节奏。
本文还有配套的精品资源,点击获取