简介基于Three.js的粒子特效网页设计源码面向前端开发者和三维可视化初学者演示如何用JavaScript与CSS构建动态粒子系统并集成3D模型适用于个人网站、产品展示等需要增强视觉冲击力的场景。资源共41个文件容量约6.85MB涵盖17个PNG图片、10个JS脚本、3个GLB模型、2个WASM文件、2个SVG矢量图以及HTML、CSS、JSON等前端配套文件其中JS脚本负责粒子生成与交互GLB/WASM用于加载与解码三维模型图片和样式则完成视觉表现与页面布局。已有128人学习浏览可快速体验Three.js粒子特效的完整实现。项目目录结构清晰包含依赖管理配置便于本地还原运行环境借助示例模型与解码逻辑可深入理解粒子系统、模型加载和前端动画交互的结合方式适合作为网页特效开发的入门素材与灵感来源。1. 粒子特效在网页设计里到底解决什么问题打开一个汽车品牌官网首屏背景里那些缓慢漂移的发光星点往往不是视频而是浏览器实时计算的粒子系统。粒子特效的价值在于用极低的素材成本给页面增加一层动态质感让空白的网页背景不再是纯色或静态图片而是带呼吸感的视觉氛围。Three.js是完成这件事最顺手的工具它把WebGL复杂的矩阵、缓冲区和着色器管线封装成场景、相机、几何体等直觉概念前端工程师只需要管理对象属性和动画循环就能做出过去需要图形学背景才能实现的粒子效果。如果你已经会写基础JavaScript想在网页里加入粒子背景、交互动效或者表白彩蛋这篇内容可以直接照着落地。2. Three.js粒子系统核心Points、BufferGeometry与着色器2.1 为什么选Three.js而不是从零写WebGL纯WebGL渲染一个粒子需要经过获取上下文、编译着色器、创建VBO、绑定属性、启用顶点数组、上传uniform等十几步光是初始化就超过百行而且每一步出错都只会得到黑屏和控制台里一行难懂的代码。Three.js把这一整套流程压缩成三个对象场景Scene、相机Camera、渲染器Renderer而粒子本身则用Points来表示。用原生WebGL画100个点需要自己管理一个Float32Array、写两个着色器、处理窗口大小变化用Three.js则是创建BufferGeometry、绑定位置属性、设置材质、加入场景一共四五行代码。对于做网页设计源码而不是图形学底座的场景Three.js的抽象层把“调效果”和“写管线”彻底分开你可以在浏览器实时调试粒子颜色、大小、运动速度而不需要重新编译着色器。这也是为什么很多开源粒子特效源码都基于Three.js写而不是基于原生WebGL。2.2 Points与BufferGeometry的组织方式在Three.js里粒子系统并不是由独立的小球物体组成的而是把所有粒子位置放进同一个几何体BufferGeometry中再用Points渲染。每个粒子的本质是几何体里的一个顶点顶点的x、y、z坐标就是粒子在三维空间中的位置而一个顶点的其他属性比如颜色、大小、纹理坐标也可以作为单独的BufferAttribute存储在几何体上。一个创建10个粒子点云的最小代码const positions new Float32Array(30); // 10个粒子每个粒子占xyz三个分量 for (let i 0; i 30; i) { positions[i] (Math.random() - 0.5) * 10; // 范围控制在[-5, 5] } const geometry new THREE.BufferGeometry(); geometry.setAttribute(position, new THREE.BufferAttribute(positions, 3)); const material new THREE.PointsMaterial({ size: 0.15, color: 0xffffff, transparent: true, opacity: 0.8 }); const points new THREE.Points(geometry, material); scene.add(points);Float32Array(30)中的30是三个分量乘以10个粒子得到的。setAttribute(position, ...)告诉Three.js每个顶点有三组数值这组数据构成粒子的位置。Points渲染时不会像Mesh那样组装三角形而是把每个顶点画成一个点点的尺寸由材质中的size控制。2.3 着色器决定粒子的最终长相PointsMaterial适合快速出效果但它的能力有限。当你希望粒子大小随距离变化、颜色随高度渐变、或者每个粒子有不同透明度时需要换成ShaderMaterial自己编写着色器。Three.js会在背后把标准uniform比如modelViewMatrix、projectionMatrix和attributeposition注入到着色器代码中。一个最简单的顶点着色器可以让粒子自带“近大远小”的透视效果void main() { vec4 mvPosition modelViewMatrix * vec4(position, 1.0); gl_PointSize (300.0 / -mvPosition.z) * 0.5; gl_Position projectionMatrix * mvPosition; }mvPosition.z是粒子到相机的深度-mvPosition.z永远为正数粒子离相机越远分母越大gl_PointSize越小。这个公式是粒子系统里的常见做法直接设置gl_PointSize可以避免所有粒子在屏幕上显示成同样大小让视角更真实。片元着色器则控制每个点最终绘制的颜色和透明渐变如果你不给它Three.js会默认填充白色。PointsMaterial和ShaderMaterial的适用边界很清晰材质类型适用场景灵活性性能PointsMaterial静态点云、背景粒子、快速原型低只能改颜色、大小、透明度较高内置优化ShaderMaterial动态飘动、鼠标交互、颜色渐变高可以控制每个顶点的生命周期取决于着色器复杂度3. 用Three.js落地一个可运行的粒子特效页面3.1 搭建页面骨架并引入Three.js不需要webpack或vite直接用浏览器模块化就可以跑通。在HTML里用importmap指定three模块的地址业务代码用script typemodule引入这段代码可以原样保存成一个index.html运行。!DOCTYPE html html langzh-CN head meta charsetUTF-8 / meta nameviewport contentwidthdevice-width, initial-scale1.0 / title基于Three.js的粒子特效网页设计源码/title style body { margin: 0; overflow: hidden; } canvas { display: block; } /style /head body script typeimportmap { imports: { three: https://unpkg.com/three/build/three.module.js } } /script script typemodule // 粒子特效逻辑 /script /body /html这段骨架的关键是importmap。它让浏览器在解析import * as THREE from three时把three映射到CDN上的模块文件。不同版本API基本一致实际使用时建议固定一个明确版本号避免CDN默认版本更新导致源码行为改变。overflow: hidden防止粒子飘出视口时出现滚动条。3.2 初始化场景、相机与渲染器在script typemodule中第一步是创建三件套。这里有一个设计上的选择让Canvas背景透明这样粒子层可以覆盖在页面内容上方也可以作为CSS背景层垫在文字下面。const scene new THREE.Scene(); const camera new THREE.PerspectiveCamera(60, innerWidth / innerHeight, 0.1, 1000); camera.position.z 25; const renderer new THREE.WebGLRenderer({ antialias: true, alpha: true }); renderer.setSize(innerWidth, innerHeight); renderer.setPixelRatio(Math.min(devicePixelRatio, 2)); document.body.appendChild(renderer.domElement);PerspectiveCamera第一个参数是视角角度60度接近人眼自然视野第二个参数是宽高比窗口变化时需要同步更新。antialias: true会让粒子边缘更平滑但会消耗性能。alpha: true是让渲染器保留画布透明背景方便后面叠加网页文字和图片。setPixelRatio(Math.min(devicePixelRatio, 2))限制像素比最大为2因为iPhone等设备devicePixelRatio可能达到3超过2对画面提升有限但GPU负担会明显上升。3.3 创建粒子几何体并绑定材质3.3.1 生成粒子坐标并创建BufferGeometry这一步是源码的核心粒子数量、分布范围、颜色都集中在这里调整。先用1500个粒子做一个球旋转效果分布范围控制在radius半径内。const count 1500; const radius 10; const positions new Float32Array(count * 3); for (let i 0; i count; i) { // 生成均匀分布在球体内的坐标 const theta Math.random() * Math.PI * 2; const phi Math.acos((Math.random() * 2) - 1); const r radius * Math.cbrt(Math.random()); const x r * Math.sin(phi) * Math.cos(theta); const y r * Math.sin(phi) * Math.sin(theta); const z r * Math.cos(phi); positions[i * 3] x; positions[i * 3 1] y; positions[i * 3 2] z; } const geometry new THREE.BufferGeometry(); geometry.setAttribute(position, new THREE.BufferAttribute(positions, 3));r * Math.cbrt(Math.random())这一行很多人直接写成radius * Math.random()这会导致粒子在球心附近密度远高于球面。由于体积和半径是三次方关系用cbrt取立方根才能让粒子在球体内部均匀分布。phi和theta是球坐标的两个角度配合三角函数转成三维坐标。3.3.2 用PointsMaterial控制颜色和大小绑定材质时可以让粒子呈现单一颜色也可以用vertexColors让每个粒子带独立颜色。这里先用最简单的方案把粒子做成浅蓝到白色的渐变通过additive混合实现发光效果。const material new THREE.PointsMaterial({ color: 0x66ccff, size: 0.12, transparent: true, opacity: 0.8, blending: THREE.AdditiveBlending, depthWrite: false }); const points new THREE.Points(geometry, material); scene.add(points);AdditiveBlending是粒子特效的经典选项多个粒子叠加后亮度增加产生光晕感。depthWrite: false让粒子不写入深度缓冲否则粒子前后遮挡关系会导致后面的粒子被直接剔除透明效果会很难看。size: 0.12表示粒子在屏幕上的像素尺寸这个值需要根据相机距离和浏览器窗口不断调整如果电脑上看着像针眼就加大到0.2。3.4 动画循环与页面自适应粒子就位后用一个requestAnimationFrame循环让整个粒子球缓慢旋转。旋转不是必须的但旋转能给静态粒子增加动态感这是粒子特效网页源码里最常见的默认动效。function animate() { requestAnimationFrame(animate); points.rotation.y 0.002; points.rotation.x 0.001; renderer.render(scene, camera); } animate(); window.addEventListener(resize, () { camera.aspect innerWidth / innerHeight; camera.updateProjectionMatrix(); renderer.setSize(innerWidth, innerHeight); });points.rotation.y 0.002每次帧加0.002弧度60帧每秒时大约每帧转0.115度转完整圈需要约52秒这个速度不会让用户眩晕。resize事件里必须执行camera.updateProjectionMatrix()否则窗口拉伸后画面会扭曲。你也可以在animate中根据鼠标位置动态调整旋转速度为后续交互做准备。4. 粒子特效的进阶玩法与性能调优4.1 用ShaderMaterial做动态粒子PointsMaterial最大的限制是没有时间概念不能让粒子自己动。改成ShaderMaterial后可以在顶点着色器里读取一个uniform变量uTime对每个粒子的位置叠加正弦波实现波浪飘动效果。这种手法在three.js教程里常被用来做星空、烟花、光雨。const vertexShader uniform float uTime; void main() { vec3 p position; p.y sin(uTime p.x * 2.0) * 0.3; p.x cos(uTime p.z * 2.0) * 0.2; vec4 mvPosition modelViewMatrix * vec4(p, 1.0); gl_PointSize (300.0 / -mvPosition.z) * 0.4; gl_Position projectionMatrix * mvPosition; } ; const material new THREE.ShaderMaterial({ uniforms: { uTime: { value: 0 } }, vertexShader, fragmentShader: void main() { gl_FragColor vec4(0.4, 0.8, 1.0, 0.85); } , transparent: true, blending: THREE.AdditiveBlending, depthWrite: false });动画循环里更新uniformmaterial.uniforms.uTime.value 0.02;。uTime是每帧递增的累计时间sin和cos让它产生周期性上下左右波动。这里的p.x * 2.0是频率系数系数越大粒子在空间上抖动越频繁看起来越“碎”。效果相当于在原有粒子位置上叠加一层波浪扰动既保持整体分布又模拟了粒子群体的呼吸感。4.2 用Canvas纹理让粒子不再是方块默认粒子渲染为正方形显存不够通透。用一张圆形渐变的Canvas纹理替换掉粒子形状是提升质感成本最低的操作。在网页设计美化里这一步能立刻让粒子从“马赛克”变成“光斑”。function createParticleTexture() { const canvas document.createElement(canvas); canvas.width 64; canvas.height 64; const ctx canvas.getContext(2d); const gradient ctx.createRadialGradient(32, 32, 0, 32, 32, 32); gradient.addColorStop(0, rgba(255,255,255,1)); gradient.addColorStop(0.6, rgba(255,255,255,0.6)); gradient.addColorStop(1, rgba(255,255,255,0)); ctx.fillStyle gradient; ctx.fillRect(0, 0, 64, 64); return new THREE.CanvasTexture(canvas); }把纹理赋给PointsMaterial的map属性并把transparent设为trueconst material new THREE.PointsMaterial({ size: 0.2, map: createParticleTexture(), transparent: true, blending: THREE.AdditiveBlending, depthWrite: false });使用Canvas纹理的优势在于完全离线生成不需要等待图片加载也不会因为引入外部资源出现跨域问题。径向渐变中间的白色高亮边缘透明叠加AdditiveBlending发光感会很强。4.3 鼠标交互让粒子围绕光标聚散交互是粒子特效源码中最容易让人感到惊喜的部分。做法是在ShaderMaterial中传入一个uMouse顶点着色器计算粒子到鼠标的距离把距离不远的粒子往鼠标方向拉一下形成凹陷或凸起。let mouse new THREE.Vector2(10, 10); window.addEventListener(pointermove, (e) { mouse.x (e.clientX / innerWidth) * 2 - 1; mouse.y -(e.clientY / innerHeight) * 2 1; }); const material new THREE.ShaderMaterial({ uniforms: { uTime: { value: 0 }, uMouse: { value: mouse } }, vertexShader: uniform vec2 uMouse; void main() { vec3 p position; float dist distance(p.xz, uMouse * 8.0); p.y smoothstep(2.0, 0.0, dist) * 0.8; // ...后续gl_PointSize计算 } , ... });这里uMouse由归一化坐标转换而来uMouse * 8.0是把鼠标位置映射到粒子所在的10单位半径范围内。smoothstep(2.0, 0.0, dist)表示当dist小于2时返回值从0平滑过渡到1越靠近鼠标的粒子被抬得越高形成“粒子跟随光标鼓起”的效果。如果改成负数就变成凹坑。4.4 性能优化从粒子数量到渲染优化粒子虽轻但数量上去了同样会拖垮帧率。比较稳妥的负载区间和经验参数如下参数桌面端推荐值移动端推荐值说明粒子数量5000 - 200001000 - 5000超过2万时动画循环CPU开销明显增加粒子size0.1 - 0.20.1 - 0.3过大反而看着像噪点像素比1 - 21移动端建议固定为1antialias开启关闭移动端关闭可省出可观的GPU占用阴影不使用不使用粒子系统一般不开阴影如果要在动画中持续修改顶点位置需要在初始化时标记为动态缓冲避免每次都重建GPU缓冲区geometry.getAttribute(position).usage THREE.DynamicDrawUsage();更新位置后还要手动把needsUpdate置为trueconst posAttr geometry.getAttribute(position); // 修改posAttr.array里的值 posAttr.needsUpdate true;这个细节很容易被忽略。不加usage时Three.js默认使用静态缓冲但动态更新同样能工作只是每次更新会产生较大性能损耗。对于超过5000粒子的场景标记DynamicDrawUsage后帧率提升非常明显。5. 把粒子特效放进真实网页设计的三个关键步骤5.1 处理粒子背景与页面内容的层级直接把粒子Canvas追加到body里会盖住所有文字和按钮。常见的做法是让Canvas固定定位在页面底层给内容容器设置更高的z-index并让Canvas不响应鼠标事件。#particle-canvas { position: fixed; top: 0; left: 0; z-index: -1; pointer-events: none; } .page-content { position: relative; z-index: 1; }z-index: -1让Canvas成为页面背景内容自动浮在上层。position: fixed保证滚动页面时粒子背景始终固定在视口内不会跟随滚动条滚走。如果你希望粒子跟随页面滚动可以改成position: absolute放在首屏容器内。5.2 移动端适配动态降低粒子数量与像素比移动端GPU能力差别很大不能用桌面端粒子数量直接跑。在初始化粒子之前可以先根据设备给数量打折const isMobile /Android|iPhone|iPad/i.test(navigator.userAgent); const baseCount isMobile ? 1200 : 5000; const pixelRatio isMobile ? 1 : Math.min(devicePixelRatio, 2); renderer.setPixelRatio(pixelRatio);动态修改粒子数量不能直接改变count常见做法是在初始化前确定好数量或者重建几何体。对于粒子背景一般不需要实时调整而是在页面加载时判断一次避免频繁重建带来的性能浪费。5.3 用浏览器工具验证效果与内存占用集成stats.js是最快的帧率验证方式它能把每帧渲染时间显示在屏幕角落。也可以直接使用Chrome DevTools的Performance录制观察渲染一帧的耗时是否稳定在16毫秒以内。内存泄漏的排查看Performance Monitor里的JS heap大小如果持续上涨检查是否在animate循环里反复创建了新的BufferAttribute或纹理。最后留一个小技巧在页面躲藏时暂停动画循环可以大幅降低后台标签页的CPU消耗document.addEventListener(visibilitychange, () { if (document.hidden) { cancelAnimationFrame(animationId); } else { animate(); } });把requestAnimationFrame的返回ID存为animationId页面重新可见时继续循环。这个优化对粒子背景尤其重要因为用户切出标签页后粒子动画还在白白消耗GPU资源。本文还有配套的精品资源点击获取