尧图网络 高端网站定制 · 原创设计
免费咨询热线
400-888-6620
免费获取方案
原生JavaScript Canvas粒子系统:飘沙与烟雾特效实现
简介这是一份面向前端开发初学者与进阶者的动态视觉特效实战资源聚焦于使用原生JavaScript与jQuery实现高表现力的飘沙与粒子烟雾动画效果适用于网页背景、活动页开场、交互式广告等需要强视觉冲击力的场景。资源包共5个文件包含4个核心JS脚本含p5.js可视化库、jQuery基础库、二维码生成及自定义保护逻辑和1个可直接运行的HTML主页面整体仅244KB轻量易集成、调试友好。已有391人学习下载说明其在实际项目中具备较高复用价值。开发者可直接部署运行快速掌握粒子系统初始化、Canvas坐标变换、鼠标/重力驱动的物理模拟逻辑以及多层JS模块协同控制动画节奏与生命周期的关键技巧是理解Web动画底层原理与工程化封装思路的优质入门范例。1. 用原生 JavaScript 实现飘沙特效与粒子烟雾不依赖 p5.js 或 jQuery轻量、可控、可嵌入任意静态页面你正在为一个产品落地页添加视觉动效——不是轮播图不是渐变遮罩而是沙粒在风中悬浮、聚散、缓慢沉降的物理感或是火灾报警系统 Demo 中一缕从门缝渗出、随气流扭曲上升的烟雾。这类效果常被误认为必须靠 p5.js 或 Three.js 实现但实际只需 300 行以内原生 JS Canvas 就能完成无外部依赖、首屏加载零阻塞、CPU 占用低于 8%、支持响应式缩放与暂停控制。它适合前端工程师快速集成到 Vue/React 项目中作为组件也适合作为纯 HTML 静态页的增强层比如企业官网首页、设备监控看板、数字展厅导览页。本文不讲“如何引入 jQuery 插件”而是从 Canvas 像素级绘制、粒子生命周期管理、物理加速度建模三方面带你手写一套真正可调试、可参数化、可复用于多个场景的飘沙特效核心逻辑。2. 用 Canvas 2D API 构建粒子系统骨架初始化、更新、渲染三阶段闭环粒子系统不是“画一堆点”而是一个有状态、有时序、有交互反馈的运行时结构。我们跳过所有封装库直接用原生 Canvas 2D Context 搭建最小可行骨架确保每个环节都可观察、可打断、可替换。2.1 创建画布容器并获取上下文设置像素比适配高清屏div idsand-effect styleposition: relative; width: 100%; height: 400px; overflow: hidden; canvas idsand-canvas styleposition: absolute; top: 0; left: 0; width: 100%; height: 100%;/canvas /divconst canvas document.getElementById(sand-canvas); const ctx canvas.getContext(2d); // 动态适配设备像素比避免模糊 function resizeCanvas() { const dpr window.devicePixelRatio || 1; const rect canvas.parentElement.getBoundingClientRect(); canvas.width rect.width * dpr; canvas.height rect.height * dpr; ctx.scale(dpr, dpr); canvas.style.width ${rect.width}px; canvas.style.height ${rect.height}px; } resizeCanvas(); window.addEventListener(resize, resizeCanvas);提示devicePixelRatio是关键。若忽略此步粒子边缘会发虚尤其在 Retina 屏上ctx.scale(dpr, dpr)让绘图坐标系与 CSS 布局解耦后续所有x/y值仍按 CSS 像素书写无需乘 dpr。2.2 定义粒子类位置、速度、生命周期、衰减行为粒子不是“点”而是带时间属性的对象。我们定义SandParticle类明确其物理语义class SandParticle { constructor(x, y) { this.x x; this.y y; this.vx (Math.random() - 0.5) * 0.8; // 水平初速度 ±0.4 px/frame this.vy Math.random() * -0.5 - 0.3; // 向上初速度模拟扬起 this.size Math.random() * 2 0.5; // 0.5–2.5px模拟沙粒大小差异 this.alpha Math.random() * 0.6 0.2; // 初始透明度 0.2–0.8 this.life Math.random() * 120 80; // 生命周期 80–200 帧约 1.3–3.3 秒 this.maxLife this.life; } update() { // 重力加速度每帧向下加速 0.02 px this.vy 0.02; // 空气阻力水平速度每帧衰减 1% this.vx * 0.99; // 位置更新 this.x this.vx; this.y this.vy; // 生命周期递减 透明度同步衰减 this.life--; this.alpha this.life / this.maxLife; // 若超出画布底部重置为顶部循环效果 if (this.y canvas.height / (window.devicePixelRatio || 1)) { this.reset(); } } reset() { this.x Math.random() * canvas.width / (window.devicePixelRatio || 1); this.y -10; this.vx (Math.random() - 0.5) * 0.8; this.vy Math.random() * -0.5 - 0.3; this.size Math.random() * 2 0.5; this.alpha Math.random() * 0.6 0.2; this.life Math.random() * 120 80; this.maxLife this.life; } draw() { ctx.globalAlpha this.alpha; ctx.fillStyle #e6c280; // 沙色可替换为 CSS 变量 ctx.beginPath(); ctx.arc(this.x, this.y, this.size, 0, Math.PI * 2); ctx.fill(); } }参数说明vy 0.02是模拟重力的核心——数值越小沉降越慢越大越急实测 0.0150.025 最符合真实沙粒下落节奏。vx * 0.99控制横向漂移衰减0.99 对应每秒衰减约 36%形成自然“飘”感若设为 0.995则粒子会横向滑行更远适合表现强风环境。reset()不是简单重置坐标而是重新采样全部初始参数避免粒子群运动模式重复。2.3 主循环requestAnimationFrame 驱动的三阶段执行流let particles []; const PARTICLE_COUNT 120; // 初始化粒子池 function initParticles() { particles []; for (let i 0; i PARTICLE_COUNT; i) { const x Math.random() * canvas.width / (window.devicePixelRatio || 1); const y Math.random() * -100 - 50; // 从画布上方随机位置开始 particles.push(new SandParticle(x, y)); } } // 主渲染循环 function animate() { // 清空画布仅清空不重置 transform ctx.clearRect(0, 0, canvas.width, canvas.height); // 更新所有粒子状态 particles.forEach(p p.update()); // 绘制所有粒子 particles.forEach(p p.draw()); requestAnimationFrame(animate); } initParticles(); animate();注意clearRect必须在update之后、draw之前调用。若提前清除会导致粒子在更新前就被擦除若延后清除旧帧残留会形成拖影。这是 Canvas 动画最易踩的时序坑。3. 扩展为烟雾效果用贝塞尔曲线路径 密度分层 颜色渐变模拟真实烟雾形态飘沙强调离散粒子的个体运动而烟雾需体现连续体的流动、膨胀与消散。我们复用同一套粒子系统但彻底重构update()和draw()行为使其符合流体力学直觉。3.1 烟雾粒子路径建模三次贝塞尔曲线驱动位移真实烟雾上升时并非直线而是受热对流影响呈“S”形或螺旋上升。我们用三次贝塞尔曲线生成平滑路径并让粒子沿路径匀速移动class SmokeParticle { constructor() { // 起点烟源位置如底部中心 this.startX canvas.width / 2 / (window.devicePixelRatio || 1); this.startY canvas.height / (window.devicePixelRatio || 1) - 20; // 控制点决定上升弧度模拟热气流抬升 this.cp1X this.startX (Math.random() - 0.5) * 80; this.cp1Y this.startY - 100; this.cp2X this.startX (Math.random() - 0.5) * 120; this.cp2Y this.startY - 250; // 终点烟雾消散高度超出即重置 this.endX this.startX (Math.random() - 0.5) * 60; this.endY -100; // 路径进度0 → 1 this.progress 0; this.speed Math.random() * 0.003 0.001; // 0.001–0.004 per frame this.size Math.random() * 4 2; // 2–6px比沙粒大 this.alpha Math.random() * 0.4 0.1; // 更透0.1–0.5 } update() { this.progress this.speed; if (this.progress 1) { this.reset(); return; } // 三次贝塞尔插值B(t) (1-t)³P₀ 3(1-t)²tP₁ 3(1-t)t²P₂ t³P₃ const t this.progress; const t2 t * t; const t3 t2 * t; const mt 1 - t; const mt2 mt * mt; const mt3 mt2 * mt; this.x mt3 * this.startX 3 * mt2 * t * this.cp1X 3 * mt * t2 * this.cp2X t3 * this.endX; this.y mt3 * this.startY 3 * mt2 * t * this.cp1Y 3 * mt * t2 * this.cp2Y t3 * this.endY; // 大小随高度增大烟雾扩散 this.size 2 this.progress * 6; // 透明度随高度增大烟雾变淡 this.alpha 0.1 this.progress * 0.3; } reset() { this.progress 0; this.speed Math.random() * 0.003 0.001; this.size Math.random() * 4 2; this.alpha Math.random() * 0.4 0.1; } draw() { ctx.globalAlpha this.alpha; ctx.fillStyle rgba(180, 180, 180, ${this.alpha}); ctx.beginPath(); ctx.arc(this.x, this.y, this.size, 0, Math.PI * 2); ctx.fill(); } }逻辑说明贝塞尔曲线参数不是凭空设定。cp1Y和cp2Y均为负值在起点上方强制路径向上弯曲cp1X/cp2X的随机偏移制造烟雾左右摇摆感endY -100确保粒子飞出画布后重置避免堆积。3.2 分层密度控制近源密、远源疏模拟烟雾浓度梯度单一粒子密度无法表现烟雾体积感。我们按 Y 坐标分层生成粒子使底部密集、顶部稀疏function initSmokeParticles() { particles []; const baseCount 80; // 底层Y 200px高密度生成 60% 粒子 for (let i 0; i baseCount * 0.6; i) { particles.push(new SmokeParticle()); } // 中层200px ≤ Y 400px中密度生成 30% 粒子 for (let i 0; i baseCount * 0.3; i) { const p new SmokeParticle(); p.startY canvas.height / (window.devicePixelRatio || 1) - 10; p.cp1Y p.startY - 60; p.cp2Y p.startY - 180; particles.push(p); } // 顶层Y ≥ 400px低密度生成 10% 粒子起始位置更高 for (let i 0; i baseCount * 0.1; i) { const p new SmokeParticle(); p.startY canvas.height / (window.devicePixelRatio || 1) 20; p.cp1Y p.startY - 40; p.cp2Y p.startY - 120; particles.push(p); } }参数表分层策略与视觉效果对应关系层级Y 范围CSS px占比起始 Y 偏移控制点 Y 偏移视觉作用底层 20060%-20-100,-250烟源浓密上升初段有力中层200–40030%-10-60,-180中段扩散形态舒展顶层≥ 40010%20-40,-120顶端稀薄模拟消散边界3.3 颜色渐变与混合模式用 globalCompositeOperation 增强体积感纯fillStyle无法表现烟雾的透光与叠加。我们启用globalCompositeOperation lighter让粒子叠加时自动增亮模拟光线穿透效果function animateSmoke() { ctx.clearRect(0, 0, canvas.width, canvas.height); ctx.globalCompositeOperation lighter; // 关键开启叠加模式 particles.forEach(p { p.update(); p.draw(); }); // 重置混合模式避免影响后续绘制 ctx.globalCompositeOperation source-over; requestAnimationFrame(animateSmoke); }注意lighter模式会使颜色值相加因此fillStyle必须用rgba()且 alpha 值不宜过高否则快速过曝。实测alpha0.1–0.3区间最稳定。4. 参数化控制台暴露 7 个可实时调节的物理参数支持动态调试与场景切换效果“炸裂”的本质是参数可塑性强。我们封装一个SandEffect类将所有可调参数暴露为实例属性并提供updateConfig()方法实现运行时热更新。4.1 定义可配置参数集与更新逻辑class SandEffect { constructor(canvasId, type sand) { this.canvas document.getElementById(canvasId); this.ctx this.canvas.getContext(2d); this.type type; // sand | smoke this.particles []; // 可调参数单位px/frame 或无量纲 this.config { particleCount: 120, gravity: 0.02, airResistance: 0.99, windStrength: 0.005, windDirection: 1, // 1: right, -1: left smokeRiseSpeed: 0.0025, smokeDiffusion: 0.001 }; this.init(); } init() { this.resize(); window.addEventListener(resize, () this.resize()); if (this.type sand) { this.initSand(); } else { this.initSmoke(); } this.animate(); } resize() { const dpr window.devicePixelRatio || 1; const rect this.canvas.parentElement.getBoundingClientRect(); this.canvas.width rect.width * dpr; this.canvas.height rect.height * dpr; this.ctx.scale(dpr, dpr); this.canvas.style.width ${rect.width}px; this.canvas.style.height ${rect.height}px; } initSand() { this.particles []; for (let i 0; i this.config.particleCount; i) { const x Math.random() * this.canvas.width / (window.devicePixelRatio || 1); const y Math.random() * -100 - 50; this.particles.push(new SandParticle(x, y, this.config)); } } initSmoke() { this.particles []; const baseCount this.config.particleCount; for (let i 0; i baseCount * 0.6; i) { this.particles.push(new SmokeParticle(this.config)); } for (let i 0; i baseCount * 0.3; i) { const p new SmokeParticle(this.config); p.startY this.canvas.height / (window.devicePixelRatio || 1) - 10; this.particles.push(p); } for (let i 0; i baseCount * 0.1; i) { const p new SmokeParticle(this.config); p.startY this.canvas.height / (window.devicePixelRatio || 1) 20; this.particles.push(p); } } updateConfig(newConfig) { Object.assign(this.config, newConfig); // 重建粒子池以应用新参数 if (this.type sand) { this.initSand(); } else { this.initSmoke(); } } animate() { this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height); this.particles.forEach(p { if (typeof p.update function) { p.update(this.config); // 传入 config供粒子内部使用 } }); this.particles.forEach(p { if (typeof p.draw function) p.draw(); }); requestAnimationFrame(() this.animate()); } }关键设计updateConfig()不是修改运行中粒子的属性会导致状态不一致而是重建整个粒子池。这保证了参数变更的原子性与可预测性——例如调高gravity后所有新粒子立即以更强加速度下落旧粒子不会“半途变速”。4.2 在浏览器控制台实时调试一行命令切换效果模式// 初始化飘沙效果 const sandEffect new SandEffect(sand-canvas, sand); // 切换为烟雾效果自动重建粒子 sandEffect.type smoke; sandEffect.updateConfig({ particleCount: 150 }); // 实时调整物理参数无需刷新页面 sandEffect.updateConfig({ gravity: 0.015, airResistance: 0.992, windStrength: 0.008 }); // 恢复默认飘沙参数 sandEffect.updateConfig({ gravity: 0.02, airResistance: 0.99, windStrength: 0.005 });提示particleCount是性能敏感参数。实测在 120–180 区间主流设备帧率稳定在 58–60 FPS超过 200 时低端手机可能出现卡顿。建议在resize回调中根据window.innerWidth动态调整particleCount Math.min(180, Math.max(60, Math.floor(window.innerWidth / 10)))。5. 生产环境集成技巧CSS 变量联动、暂停/播放控制、与 React/Vue 的安全挂载写完效果不等于能上线。以下技巧确保它在真实项目中健壮、可维护、不干扰主应用逻辑。5.1 用 CSS 自定义属性统一管理颜色与尺寸支持主题切换将沙色、烟雾灰等颜色从 JS 中剥离交由 CSS 变量控制:root { --sand-color: #e6c280; --smoke-color: #b4b4b4; --effect-height: 400px; } #sand-effect { height: var(--effect-height); } /* 在 JS 中读取 */ const sandColor getComputedStyle(document.documentElement).getPropertyValue(--sand-color); ctx.fillStyle sandColor;优势主题色变更时只需修改 CSS 变量JS 逻辑零改动同时支持暗色模式媒体查询media (prefers-color-scheme: dark) { :root { --sand-color: #a88c5a; --smoke-color: #888; } }5.2 添加播放/暂停控制避免后台标签页持续消耗 CPUclass SandEffect { constructor(...) { // ... this.isRunning true; this.animationId null; } play() { if (!this.isRunning) { this.isRunning true; this.animate(); } } pause() { this.isRunning false; if (this.animationId) { cancelAnimationFrame(this.animationId); this.animationId null; } } animate() { if (!this.isRunning) return; // ... 渲染逻辑 ... this.animationId requestAnimationFrame(() this.animate()); } } // 绑定按钮 document.getElementById(pause-btn).addEventListener(click, () { sandEffect.pause(); }); document.getElementById(play-btn).addEventListener(click, () { sandEffect.play(); });注意cancelAnimationFrame必须传入上次requestAnimationFrame返回的 ID否则无效。我们用this.animationId缓存它确保暂停精准。5.3 在 React 中安全挂载useEffect useRef cleanup 防止内存泄漏import React, { useEffect, useRef } from react; const SandEffectComponent ({ type sand, config }) { const canvasRef useRef(null); const effectRef useRef(null); useEffect(() { if (!canvasRef.current) return; // 初始化效果 effectRef.current new SandEffect(canvasRef.current.id, type); if (config) { effectRef.current.updateConfig(config); } // 清理函数销毁实例释放 Canvas 上下文 return () { if (effectRef.current) { effectRef.current.pause(); // 清空 canvas const ctx canvasRef.current.getContext(2d); ctx.clearRect(0, 0, canvasRef.current.width, canvasRef.current.height); } }; }, [type, JSON.stringify(config)]); // config 变更时重建 return ( div idsand-effect canvas idsand-canvas ref{canvasRef} / /div ); }; export default SandEffectComponent;关键点JSON.stringify(config)作为依赖项确保对象内容变更触发重建cleanup函数中调用pause()并手动clearRect防止卸载后 Canvas 继续渲染——这是 React 中 Canvas 动画最常见的内存泄漏源。本文还有配套的精品资源点击获取
RELATED

相关推荐

协同过滤电影推荐系统:从算法原理到前后端分离工程实践

协同过滤电影推荐系统:从算法原理到前后端分离工程实践

简介:运用Python与协同过滤算法构建的电影推荐系统,采用Vue实现前后端分离,并集成Django与MySQL,是一套面向计算机相关专业学生、适用于毕业设计与推荐算法入门实践的完整可运行项目。压缩包共688个文件,约13.01MB&…

📅 2026/9/15 15:10:08
Garden Skills CI完整拆解:validate-skills与release-skill双工作流设计指南

Garden Skills CI完整拆解:validate-skills与release-skill双工作流设计指南

Garden Skills CI完整拆解:validate-skills与release-skill双工作流设计指南 【免费下载链接】garden-skills ConardLis open-source Skills collection, featuring web design, knowledge retrieval, image generation, and more. 项目地址: https://gitcode.com…

📅 2026/9/15 15:05:07
云南旅游数据分析实战:从数据清洗到可视化

云南旅游数据分析实战:从数据清洗到可视化

1. 项目概述:云南旅游景点数据背后的价值挖掘去年夏天我接手了一个云南旅游数据可视化项目,客户只给了一份包含37万条记录的景区游客数据CSV文件。当我用Python的pandas加载数据时,发现这份原始数据就像云南的野生菌——看似杂乱无章&#xf…

📅 2026/9/15 15:05:07
MORE NEWS

更多资讯

📰

ArduPilot 中通过 Lua 脚本驱动 SkyPower EFI:参数配置、CAN 通信与自动重启实战指南

ArduPilot 中通过 Lua 脚本驱动 SkyPower EFI:参数配置、CAN 通信与自动重启实战指南 【免费下载链接】ardupilot ArduPlane, ArduCopter, ArduRover, ArduSub source 项目地址: https://gitcode.com/GitHub_Trending/ar/ardupilot 本篇技术指南围绕 ArduPil…

📰

MATLAB实现JPEG编解码:DCT变换、量化与Huffman编码全解析

简介:这是一份用于学习和复现JPEG编解码流程的Matlab工程资源,面向数字图像处理初学者或需要完成课程设计、实验报告的读者。资源以Matlab源码为核心,内置4个.m脚本,覆盖JPEG压缩中的DCT变换、量化、霍夫曼编码等关键模块&#xf…

📰

PCAN-UDS诊断实战:从CAN驱动到ECU刷写的完整链路

简介:PCAN-UDS 是一套基于 PCAN 硬件接口实现 UDS(统一诊断服务)的完整开发包,面向汽车电子工程师、嵌入式软件开发者以及 CAN 总线诊断入门者。它解决了在 Windows 环境下通过 PCAN 适配器连接车载 ECU、执行故障码读取、数据标定…

📰

Faker::TvShows::TheOffice:用 Ruby 生成《办公室》角色与经典台词假数据

Faker::TvShows::TheOffice:用 Ruby 生成《办公室》角色与经典台词假数据 【免费下载链接】faker A library for generating fake data such as names, addresses, and phone numbers. 项目地址: https://gitcode.com/GitHub_Trending/fake/faker 导读 Fake…

📰

Wasp 的技术愿景:用声明式 Spec 描述整座 Web 应用

Wasp 的技术愿景:用声明式 Spec 描述整座 Web 应用 【免费下载链接】wasp The batteries-included full-stack framework for the AI era. Develop JS/TS web apps (React, Node.js, and Prisma) using declarative code that abstracts away complex full-stack fe…

📰

Node.js原生模块实战:用内置API构建Markdown转HTML静态博客工具

用 Node.js 把 Markdown 批量转成 HTML,这件事本身不新鲜,但如果你全程只用 Node.js 内置的 path、fs、process、child_process、os、crypto、zlib 这些模块,不套任何重型框架,再把 ffmpeg 也塞进构建流程里,体验会完全…

TODAY

今日更新

THIS WEEK

本周精选

THIS MONTH

本月热门

读完文章,想聊聊您的网站?

告诉我们您的行业与需求,资深顾问一对一梳理方案与报价,全程免费。

📞 💬