js-waves-particles 1.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +144 -0
- package/dist/wave-particles.esm.js +2 -0
- package/dist/wave-particles.esm.js.map +7 -0
- package/dist/wave-particles.iife.js +2 -0
- package/dist/wave-particles.iife.js.map +7 -0
- package/package.json +49 -0
- package/src/browser.js +11 -0
- package/src/index.d.ts +87 -0
- package/src/index.js +551 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Luis 'PlatinumBlade' Moniz
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
# js-waves-particles
|
|
2
|
+
|
|
3
|
+
A lightweight, high-performance canvas animation engine for organic wave and particle effects.
|
|
4
|
+
|
|
5
|
+
[](https://www.npmjs.com/package/js-waves-particles)
|
|
6
|
+
[](https://github.com/PlatinumBlade/js-waves-particles/blob/main/LICENSE)
|
|
7
|
+
[](https://bundlephobia.com/package/js-waves-particles)
|
|
8
|
+
|
|
9
|
+
**[View Live Demo & Playground](https://platinumblade.github.io/js-waves-particles/examples/index.html)**
|
|
10
|
+
|
|
11
|
+
### [Changelog](./CHANGELOG.md)
|
|
12
|
+
|
|
13
|
+
## Key Features
|
|
14
|
+
|
|
15
|
+
- **Multiple Wave Layers**: Smooth, sine-based wave animations with customizable amplitude, frequency, and speed.
|
|
16
|
+
- **Floating Particles**: Dynamic particle system with pulsing opacity and mouse-repulsion.
|
|
17
|
+
- **Mouse/Touch Reactive**: Waves and particles respond to cursor movements and touch events with smoothed
|
|
18
|
+
interpolation.
|
|
19
|
+
- **Fully Customizable**: Override colors, gradients, and animation parameters via a simple config object.
|
|
20
|
+
- **Zero Dependencies**: Pure JavaScript, lightweight, and framework-agnostic.
|
|
21
|
+
- **Fully Typed**: Shipped with TypeScript definitions for an excellent developer experience.
|
|
22
|
+
|
|
23
|
+
## Installation
|
|
24
|
+
|
|
25
|
+
To install the package in your project:
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
npm install js-waves-particles
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
## Quick Start
|
|
32
|
+
|
|
33
|
+
### 1. Modern Bundlers (ESM)
|
|
34
|
+
|
|
35
|
+
Perfect for React, Vue, Angular, and Next.js.
|
|
36
|
+
|
|
37
|
+
```javascript
|
|
38
|
+
import WaveParticles from 'js-waves-particles';
|
|
39
|
+
|
|
40
|
+
// Automatically creates a full-screen canvas behind your content
|
|
41
|
+
const animation = new WaveParticles();
|
|
42
|
+
|
|
43
|
+
// Cleanup (important for Single Page Applications)
|
|
44
|
+
// animation.destroy();
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
### 2. Browser / Script Tag
|
|
48
|
+
|
|
49
|
+
```html
|
|
50
|
+
|
|
51
|
+
<script src="path/to/dist/wave-particles.iife.js"></script>
|
|
52
|
+
<script>
|
|
53
|
+
const animation = new WaveParticles();
|
|
54
|
+
</script>
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
## Configuration
|
|
58
|
+
|
|
59
|
+
You can pass a configuration object to the constructor to customize the visual engine.
|
|
60
|
+
|
|
61
|
+
```javascript
|
|
62
|
+
const animation = new WaveParticles({
|
|
63
|
+
canvas: '#my-canvas', // Optional: selector or HTMLCanvasElement
|
|
64
|
+
config: {
|
|
65
|
+
waves: [
|
|
66
|
+
{
|
|
67
|
+
amplitude: 80,
|
|
68
|
+
frequency: 0.002,
|
|
69
|
+
speed: 0.004,
|
|
70
|
+
yOffset: 0.5,
|
|
71
|
+
color: 'rgba(200, 100, 100, 0.2)',
|
|
72
|
+
layers: 3
|
|
73
|
+
}
|
|
74
|
+
],
|
|
75
|
+
particles: {
|
|
76
|
+
countScale: 5000, // Pixels per particle (lower = denser)
|
|
77
|
+
maxCount: 300 // Maximum limit
|
|
78
|
+
},
|
|
79
|
+
colors: {
|
|
80
|
+
backgroundGradient: ['#ffffff', '#f0f0f0'],
|
|
81
|
+
particleColorPrefixes: ['rgba(255, 0, 0,'] // Alpha is managed automatically
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
});
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
## Framework Integration
|
|
88
|
+
|
|
89
|
+
### React
|
|
90
|
+
|
|
91
|
+
```jsx
|
|
92
|
+
import {useEffect} from 'react';
|
|
93
|
+
import WaveParticles from 'js-waves-particles';
|
|
94
|
+
|
|
95
|
+
export const Background = () => {
|
|
96
|
+
useEffect(() => {
|
|
97
|
+
const animation = new WaveParticles();
|
|
98
|
+
return () => animation.destroy();
|
|
99
|
+
}, []);
|
|
100
|
+
|
|
101
|
+
return null;
|
|
102
|
+
};
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
### Vue 3
|
|
106
|
+
|
|
107
|
+
```html
|
|
108
|
+
|
|
109
|
+
<script setup>
|
|
110
|
+
import {onMounted, onUnmounted} from 'vue';
|
|
111
|
+
import WaveParticles from 'js-waves-particles';
|
|
112
|
+
|
|
113
|
+
let animation;
|
|
114
|
+
onMounted(() => {
|
|
115
|
+
animation = new WaveParticles();
|
|
116
|
+
});
|
|
117
|
+
onUnmounted(() => {
|
|
118
|
+
animation.destroy();
|
|
119
|
+
});
|
|
120
|
+
</script>
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
## API Reference
|
|
124
|
+
|
|
125
|
+
### `.start()`
|
|
126
|
+
|
|
127
|
+
Resumes the animation loop and re-attaches event listeners.
|
|
128
|
+
|
|
129
|
+
### `.stop()`
|
|
130
|
+
|
|
131
|
+
Pauses the animation loop and detaches listeners to save CPU cycles.
|
|
132
|
+
|
|
133
|
+
### `.resize()`
|
|
134
|
+
|
|
135
|
+
Forces the engine to recalculate dimensions and redistribute particles. Handled automatically with
|
|
136
|
+
debouncing on window resize.
|
|
137
|
+
|
|
138
|
+
### `.destroy()`
|
|
139
|
+
|
|
140
|
+
The nuclear option. Stops animation, removes listeners, and cleans up the DOM (if auto-created).
|
|
141
|
+
|
|
142
|
+
## License
|
|
143
|
+
|
|
144
|
+
[MIT](./LICENSE) © 2026 Luis 'PlatinumBlade' Moniz
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
var u=class{constructor(e={}){let{canvas:i,config:t}=e||{};this.canvas=null,this.ctx=null,this.mouseX=0,this.mouseY=0,this.targetMouseX=-1e3,this.targetMouseY=-1e3,this.mouseActive=!1,this.width=0,this.height=0,this.config=this._mergeDefaults(t||{}),this.time=0,this.isRunning=!1,this.particles=[],this._resizeTimeout=null;let s=typeof document<"u"&&i?typeof i=="string"?document.querySelector(i):i:null;if(s){this.canvas=s;let o=s.getBoundingClientRect();this.width=Math.round(o.width||window.innerWidth),this.height=Math.round(o.height||window.innerHeight)}else this._createAutoCanvas();try{if(!this.canvas)throw new Error("No canvas element available.");let o=this.canvas.getContext("2d");if(!o)throw new Error("Could not get 2D rendering context.");this.ctx=o}catch(o){console.error("[WaveParticles] Canvas error:",o);return}this._onMouseMove=this._onMouseMove.bind(this),this._onMouseLeave=this._onMouseLeave.bind(this),this._onTouchMove=this._onTouchMove.bind(this),this._onTouchEnd=this._onTouchEnd.bind(this),this._onResize=this._onResize.bind(this),typeof document<"u"&&(window.addEventListener("resize",this._onResize),this.resize()),this.initParticles(),this.start()}_mergeDefaults(e){let i={waves:[{amplitude:60,frequency:.003,speed:.006,yOffset:.55,color:"rgba(248, 225, 231, 0.25)",lineWidth:1.5,mouseInfluence:.45},{amplitude:50,frequency:.004,speed:.008,yOffset:.6,color:"rgba(212, 165, 165, 0.2)",lineWidth:1,mouseInfluence:.5},{amplitude:70,frequency:.0025,speed:.005,yOffset:.65,color:"rgba(255, 255, 255, 0.2)",lineWidth:1.5,mouseInfluence:.4},{amplitude:40,frequency:.0035,speed:.01,yOffset:.7,color:"rgba(201, 169, 110, 0.12)",lineWidth:1,mouseInfluence:.55}],particles:{countScale:8e3,maxCount:180},colors:{backgroundGradient:["#fdfcfb","#f7ede2","#e8d5d0"],particleColorPrefixes:["rgba(248, 225, 231,","rgba(212, 165, 165,","rgba(201, 169, 110,","rgba(255, 255, 255,","rgba(232, 213, 208,"],mouseGlowStops:[{offset:0,color:"rgba(255, 255, 255, 0.08)"},{offset:.5,color:"rgba(248, 225, 231, 0.04)"},{offset:1,color:"rgba(248, 225, 231, 0)"}]}},t={};for(let s in i)i.hasOwnProperty(s)&&(e&&e[s]?typeof i[s]=="object"&&!Array.isArray(i[s])&&typeof e[s]=="object"?t[s]=Object.assign({},i[s],e[s]):s==="waves"?t.waves=Array.isArray(e.waves)?[...e.waves]:JSON.parse(JSON.stringify(i.waves)):t[s]=e[s]:t[s]=JSON.parse(JSON.stringify(i[s])));return t}_createAutoCanvas(){if(typeof document>"u")return;let e=document.createElement("canvas");e.id="wave-particles-canvas",Object.assign(e.style,{position:"fixed",top:"0",left:"0",width:"100%",height:"100%",zIndex:"-1",pointerEvents:"none"});let i=document.body;i&&(i.insertBefore(e,i.firstChild),this.canvas=e,this.width=window.innerWidth,this.height=window.innerHeight)}_onResize(){this._resizeTimeout&&clearTimeout(this._resizeTimeout),this._resizeTimeout=setTimeout(()=>{this.resize()},250)}resize(){if(!this.canvas)return;let e=this.canvas.getBoundingClientRect(),i,t,s=parseInt(this.canvas.getAttribute("width"),10),o=parseInt(this.canvas.getAttribute("height"),10);s&&o?(i=Math.round(e.width||s),t=Math.round(e.height||o)):(i=window.innerWidth,t=window.innerHeight,!this.canvas.parentNode&&document.body&&document.body.insertBefore(this.canvas,document.body.firstChild)),this.width=i,this.height=t;let n=window.devicePixelRatio||1;this.canvas.width=Math.round(i*n),this.canvas.height=Math.round(t*n),this.ctx&&this.isRunning&&this.initParticles()}initParticles(){let{countScale:e=8e3,maxCount:i=180}=this.config.particles,t=Math.min(Math.floor(this.width*this.height/(e>0?e:8e3)),i);this.particles=Array.from({length:t},()=>({x:Math.random()*this.width,y:Math.random()*this.height,size:Math.random()*4+1.5,speedX:(Math.random()-.5)*.4,speedY:(Math.random()-.5)*.4,opacity:Math.random()*.6+.3,pulse:Math.random()*Math.PI*2,pulseSpeed:Math.random()*.025+.01,colorPrefix:this.config.colors.particleColorPrefixes[Math.floor(Math.random()*this.config.colors.particleColorPrefixes.length)]}))}_onMouseMove(e){let i=this.canvas.getBoundingClientRect();i&&(this.targetMouseX=e.clientX-i.left,this.targetMouseY=e.clientY-i.top,this.mouseActive=!0)}_onMouseLeave(){this.mouseActive=!1}_onTouchMove(e){let i=e.touches[0],t=this.canvas.getBoundingClientRect();!i||!t||(this.targetMouseX=i.clientX-t.left,this.targetMouseY=i.clientY-t.top,this.mouseActive=!0)}_onTouchEnd(){this.mouseActive=!1}drawBackground(e){let i=this.config.colors.backgroundGradient,t=e.createRadialGradient(this.width/2,this.height/2,0,this.width/2,this.height/2,Math.max(this.width,this.height)*.8);i.forEach((s,o)=>t.addColorStop(o/(i.length-1||1),s)),e.fillStyle=t,e.fillRect(0,0,this.width,this.height)}drawWaves(e){let i=this.mouseActive?this.mouseX/this.width:-1,t=this.mouseActive?this.mouseY/this.height:-1;for(let s=0;s<this.config.waves.length;s++){let o=this.config.waves[s],n=Math.max(2,o.layers||2);for(let h=0;h<n;h++){let r=(1-h/n)*.5+.5;e.beginPath();let d=this.height*o.yOffset+h*3,l=this.mouseActive?(i-.5)*(o.mouseInfluence||.45)*150:0;e.moveTo(0,this.height);let f=Math.max(2,Math.floor(this.width/480));for(let a=0;a<=this.width;a+=f){let c=d;if(c+=Math.sin(a*o.frequency+this.time*o.speed+h*.1)*o.amplitude,c+=Math.sin(a*o.frequency*1.5+this.time*o.speed*.7+s)*o.amplitude*.3,this.mouseActive){let v=a-this.mouseX,w=Math.abs(v)/this.width;c+=Math.sin(w*Math.PI)*(t>0?this.mouseY-this.height/2:0)*.1*(o.mouseInfluence||.45)}c+=l*Math.sin(a/this.width*Math.PI),e.lineTo(a,c)}e.lineTo(this.width,this.height),e.closePath();let m=o.color||"rgba(139, 114, 86, 0.25)";e.fillStyle=m.replace(/([\d.]+)\)$/,a=>`${parseFloat(a)*r})`),o.lineWidth!=null&&(e.lineWidth=o.lineWidth),e.fill()}}}drawParticles(e){for(let t of this.particles){t.pulse+=t.pulseSpeed;let s=t.opacity*(Math.sin(t.pulse)*.3+.7);if(this.mouseActive){let n=t.x-this.mouseX,h=t.y-this.mouseY,r=Math.sqrt(n*n+h*h);if(r<150&&r>.1){let d=(1-r/150)*2;t.x+=n/r*d,t.y+=h/r*d}}t.x+=t.speedX,t.y+=t.speedY,t.x<0&&(t.x=this.width),t.x>this.width&&(t.x=0),t.y<0&&(t.y=this.height),t.y>this.height&&(t.y=0);let o=e.createRadialGradient(t.x,t.y,0,t.x,t.y,t.size*2);o.addColorStop(0,t.colorPrefix+s+")"),o.addColorStop(1,t.colorPrefix+"0)"),e.beginPath(),e.fillStyle=o,e.arc(t.x,t.y,t.size*2,0,Math.PI*2),e.fill(),e.beginPath(),e.fillStyle=t.colorPrefix+s*.8+")",e.arc(t.x,t.y,t.size*.5,0,Math.PI*2),e.fill()}}drawMouseGlow(e){if(!this.mouseActive)return;let i=e.createRadialGradient(this.mouseX,this.mouseY,0,this.mouseX,this.mouseY,200);this.config.colors.mouseGlowStops.forEach(s=>i.addColorStop(s.offset,s.color)),e.beginPath(),e.fillStyle=i,e.arc(this.mouseX,this.mouseY,200,0,Math.PI*2),e.fill()}animate(){this.ctx&&(this.time+=1,this.mouseX+=(this.targetMouseX-this.mouseX)*.08,this.mouseY+=(this.targetMouseY-this.mouseY)*.08,this.ctx.clearRect(0,0,this.width,this.height),this.drawBackground(this.ctx),this.drawWaves(this.ctx),this.drawParticles(this.ctx),this.drawMouseGlow(this.ctx),this.isRunning&&requestAnimationFrame(()=>this.animate()))}start(){this.isRunning||(typeof window<"u"&&(window.addEventListener("mousemove",this._onMouseMove,{passive:!0}),window.addEventListener("touchmove",this._onTouchMove,{passive:!1}),window.addEventListener("touchend",this._onTouchEnd,{passive:!0}),window.addEventListener("mouseleave",this._onMouseLeave)),this.isRunning=!0,this.animate())}stop(){this.isRunning=!1,typeof window<"u"&&(window.removeEventListener("mousemove",this._onMouseMove),window.removeEventListener("touchmove",this._onTouchMove),window.removeEventListener("touchend",this._onTouchEnd),window.removeEventListener("mouseleave",this._onMouseLeave))}destroy(){this.stop(),typeof window<"u"&&window.removeEventListener("resize",this._onResize),this.canvas&&this.canvas.id==="wave-particles-canvas"&&this.canvas.parentNode&&this.canvas.parentNode.removeChild(this.canvas),this.canvas=null,this.ctx=null}},p=u;export{p as default};
|
|
2
|
+
//# sourceMappingURL=wave-particles.esm.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../src/index.js"],
|
|
4
|
+
"sourcesContent": ["/**\r\n * js-waves-particles\r\n * A canvas-based wave and particle animation library with mouse-reactive effects.\r\n * Developed by Luis 'PlatinumBlade' Moniz.\r\n */\r\n\r\nclass WaveParticles {\r\n /**\r\n * Initializes a new WaveParticles animation engine.\r\n *\r\n * @param {import('./index').WaveParticlesOptions} [options] - Configuration for the engine.\r\n * @param {HTMLCanvasElement|string} [options.canvas] - The target canvas element or a CSS selector (e.g., '#bg'). If omitted, a full-screen fixed canvas is automatically created.\r\n * @param {import('./index').WaveParticlesConfig} [options.config] - Visual parameters for waves, particles, and colors.\r\n */\r\n constructor(options = {}) {\r\n const {canvas: providedCanvasOrSelector, config} = options || {};\r\n\r\n // --- State ---\r\n this.canvas = null;\r\n this.ctx = null;\r\n this.mouseX = 0;\r\n this.mouseY = 0;\r\n this.targetMouseX = -1000;\r\n this.targetMouseY = -1000;\r\n this.mouseActive = false;\r\n this.width = 0;\r\n this.height = 0;\r\n this.config = this._mergeDefaults(config || {});\r\n this.time = 0;\r\n this.isRunning = false;\r\n this.particles = [];\r\n this._resizeTimeout = null;\r\n\r\n const resolvedCanvas = typeof document !== 'undefined' && providedCanvasOrSelector ? (() => {\r\n if (typeof providedCanvasOrSelector === 'string') {\r\n return /** @type {HTMLCanvasElement|null} */ (document.querySelector(providedCanvasOrSelector));\r\n }\r\n\r\n return /** @type {HTMLCanvasElement | null} */ (providedCanvasOrSelector);\r\n })() : null;\r\n\r\n if (resolvedCanvas) {\r\n this.canvas = resolvedCanvas;\r\n const rect = resolvedCanvas.getBoundingClientRect();\r\n this.width = Math.round(rect.width || window.innerWidth);\r\n this.height = Math.round(rect.height || window.innerHeight);\r\n } else {\r\n this._createAutoCanvas();\r\n }\r\n\r\n try {\r\n if (!this.canvas) {\r\n throw new Error('No canvas element available.');\r\n }\r\n\r\n const ctx = this.canvas.getContext('2d');\r\n\r\n if (!ctx) {\r\n throw new Error('Could not get 2D rendering context.');\r\n }\r\n\r\n this.ctx = ctx;\r\n } catch (e) {\r\n console.error('[WaveParticles] Canvas error:', e);\r\n return;\r\n }\r\n\r\n this._onMouseMove = this._onMouseMove.bind(this);\r\n this._onMouseLeave = this._onMouseLeave.bind(this);\r\n this._onTouchMove = this._onTouchMove.bind(this);\r\n this._onTouchEnd = this._onTouchEnd.bind(this);\r\n this._onResize = this._onResize.bind(this);\r\n\r\n if (typeof document !== 'undefined') {\r\n window.addEventListener('resize', this._onResize);\r\n this.resize(); // Initial size setup\r\n }\r\n\r\n this.initParticles();\r\n this.start();\r\n }\r\n\r\n /**\r\n * Internal method to merge user configuration with library defaults.\r\n * @private\r\n */\r\n _mergeDefaults(userConfig) {\r\n const defaults = {\r\n waves: [\r\n {\r\n amplitude: 60,\r\n frequency: 0.003,\r\n speed: 0.006,\r\n yOffset: 0.55,\r\n color: 'rgba(248, 225, 231, 0.25)',\r\n lineWidth: 1.5,\r\n mouseInfluence: 0.45\r\n },\r\n {\r\n amplitude: 50,\r\n frequency: 0.004,\r\n speed: 0.008,\r\n yOffset: 0.60,\r\n color: 'rgba(212, 165, 165, 0.2)',\r\n lineWidth: 1,\r\n mouseInfluence: 0.5\r\n },\r\n {\r\n amplitude: 70,\r\n frequency: 0.0025,\r\n speed: 0.005,\r\n yOffset: 0.65,\r\n color: 'rgba(255, 255, 255, 0.2)',\r\n lineWidth: 1.5,\r\n mouseInfluence: 0.4\r\n },\r\n {\r\n amplitude: 40,\r\n frequency: 0.0035,\r\n speed: 0.010,\r\n yOffset: 0.70,\r\n color: 'rgba(201, 169, 110, 0.12)',\r\n lineWidth: 1,\r\n mouseInfluence: 0.55\r\n }\r\n ],\r\n particles: {countScale: 8000, maxCount: 180},\r\n colors: {\r\n backgroundGradient: ['#fdfcfb', '#f7ede2', '#e8d5d0'],\r\n particleColorPrefixes: [\r\n 'rgba(248, 225, 231,',\r\n 'rgba(212, 165, 165,',\r\n 'rgba(201, 169, 110,',\r\n 'rgba(255, 255, 255,',\r\n 'rgba(232, 213, 208,'\r\n ],\r\n mouseGlowStops: [\r\n {offset: 0, color: 'rgba(255, 255, 255, 0.08)'},\r\n {offset: 0.5, color: 'rgba(248, 225, 231, 0.04)'},\r\n {offset: 1, color: 'rgba(248, 225, 231, 0)'}\r\n ]\r\n }\r\n };\r\n\r\n const result = {};\r\n\r\n for (const key in defaults) {\r\n if (!defaults.hasOwnProperty(key)) {\r\n continue;\r\n }\r\n\r\n if (!(userConfig && userConfig[key])) {\r\n result[key] = JSON.parse(JSON.stringify(defaults[key]));\r\n } else if (typeof defaults[key] === 'object' && !Array.isArray(defaults[key]) && typeof userConfig[key] === 'object') {\r\n result[key] = Object.assign({}, defaults[key], userConfig[key]);\r\n } else if (key === 'waves') {\r\n result.waves = Array.isArray(userConfig.waves) ? [...userConfig.waves] : JSON.parse(JSON.stringify(defaults.waves));\r\n } else {\r\n result[key] = userConfig[key];\r\n }\r\n }\r\n \r\n return result;\r\n }\r\n\r\n /**\r\n * Automatically creates and injects a canvas element into the DOM if none was provided.\r\n * @private\r\n */\r\n _createAutoCanvas() {\r\n if (typeof document === 'undefined') {\r\n return;\r\n }\r\n\r\n const el = document.createElement('canvas');\r\n\r\n el.id = 'wave-particles-canvas';\r\n\r\n Object.assign(el.style, {\r\n position: 'fixed',\r\n top: '0',\r\n left: '0',\r\n width: '100%',\r\n height: '100%',\r\n zIndex: '-1',\r\n pointerEvents: 'none'\r\n });\r\n\r\n const body = document.body;\r\n\r\n if (!body) {\r\n return;\r\n }\r\n\r\n body.insertBefore(el, body.firstChild);\r\n this.canvas = el;\r\n this.width = window.innerWidth;\r\n this.height = window.innerHeight;\r\n }\r\n\r\n /**\r\n * Event handler for window resize events, including debouncing to prevent performance issues.\r\n * @private\r\n */\r\n _onResize() {\r\n if (this._resizeTimeout) clearTimeout(this._resizeTimeout);\r\n // Debounce resize to prevent flashing and high CPU usage\r\n this._resizeTimeout = setTimeout(() => {\r\n this.resize();\r\n }, 250);\r\n }\r\n\r\n /**\r\n * Recalculates canvas dimensions based on its display size and Device Pixel Ratio.\r\n * Re-initializes particles to match the new surface area.\r\n */\r\n resize() {\r\n if (!this.canvas) {\r\n return;\r\n }\r\n\r\n const rect = this.canvas.getBoundingClientRect();\r\n\r\n let w, h;\r\n\r\n const attrW = parseInt(this.canvas.getAttribute('width'), 10);\r\n const attrH = parseInt(this.canvas.getAttribute('height'), 10);\r\n\r\n if (attrW && attrH) {\r\n w = Math.round(rect.width || attrW);\r\n h = Math.round(rect.height || attrH);\r\n } else {\r\n w = window.innerWidth;\r\n h = window.innerHeight;\r\n\r\n if (!this.canvas.parentNode && document.body) {\r\n document.body.insertBefore(this.canvas, document.body.firstChild);\r\n }\r\n }\r\n \r\n this.width = w;\r\n this.height = h;\r\n\r\n const dpr = window.devicePixelRatio || 1;\r\n\r\n this.canvas.width = Math.round(w * dpr);\r\n this.canvas.height = Math.round(h * dpr);\r\n\r\n if (this.ctx && this.isRunning) {\r\n this.initParticles();\r\n }\r\n }\r\n\r\n /**\r\n * Generates a new set of particles based on the current canvas area and configuration.\r\n */\r\n initParticles() {\r\n const {countScale = 8000, maxCount = 180} = this.config.particles;\r\n\r\n const particleCount = Math.min(Math.floor((this.width * this.height) / (countScale > 0 ? countScale : 8000)), maxCount);\r\n\r\n this.particles = Array.from({length: particleCount}, () => ({\r\n x: Math.random() * this.width,\r\n y: Math.random() * this.height,\r\n size: Math.random() * 4 + 1.5,\r\n speedX: (Math.random() - 0.5) * 0.4,\r\n speedY: (Math.random() - 0.5) * 0.4,\r\n opacity: Math.random() * 0.6 + 0.3,\r\n pulse: Math.random() * Math.PI * 2,\r\n pulseSpeed: Math.random() * 0.025 + 0.01,\r\n colorPrefix: this.config.colors.particleColorPrefixes[Math.floor(Math.random() * this.config.colors.particleColorPrefixes.length)]\r\n }));\r\n }\r\n\r\n _onMouseMove(e) {\r\n const rect = this.canvas.getBoundingClientRect();\r\n \r\n if (!rect) {\r\n return;\r\n }\r\n\r\n // Calculate position relative to the canvas in CSS pixels\r\n this.targetMouseX = e.clientX - rect.left;\r\n this.targetMouseY = e.clientY - rect.top;\r\n this.mouseActive = true;\r\n }\r\n\r\n _onMouseLeave() {\r\n this.mouseActive = false;\r\n }\r\n\r\n _onTouchMove(e) {\r\n const touch = e.touches[0];\r\n const rect = this.canvas.getBoundingClientRect();\r\n \r\n if (!touch || !rect) {\r\n return;\r\n }\r\n\r\n this.targetMouseX = touch.clientX - rect.left;\r\n this.targetMouseY = touch.clientY - rect.top;\r\n this.mouseActive = true;\r\n }\r\n\r\n _onTouchEnd() {\r\n this.mouseActive = false;\r\n }\r\n\r\n /**\r\n * Renders the radial gradient background across the entire canvas.\r\n * @param {CanvasRenderingContext2D} ctx\r\n */\r\n drawBackground(ctx) {\r\n const colors = this.config.colors.backgroundGradient;\r\n \r\n const gradient = ctx.createRadialGradient(this.width / 2, this.height / 2, 0, this.width / 2, this.height / 2, Math.max(this.width, this.height) * 0.8);\r\n \r\n colors.forEach((c, i) => gradient.addColorStop(i / (colors.length - 1 || 1), c));\r\n \r\n ctx.fillStyle = gradient;\r\n \r\n ctx.fillRect(0, 0, this.width, this.height);\r\n }\r\n\r\n /**\r\n * Renders all wave layers, accounting for mouse interaction and layer offsets.\r\n * @param {CanvasRenderingContext2D} ctx\r\n */\r\n drawWaves(ctx) {\r\n const mouseNormX = this.mouseActive ? this.mouseX / this.width : -1;\r\n const mouseNormY = this.mouseActive ? this.mouseY / this.height : -1;\r\n\r\n for (let wi = 0; wi < this.config.waves.length; wi++) {\r\n const wave = this.config.waves[wi];\r\n const numLayers = Math.max(2, wave.layers || 2);\r\n\r\n for (let layer = 0; layer < numLayers; layer++) {\r\n const layerOpacity = (1 - layer / numLayers) * 0.5 + 0.5;\r\n \r\n ctx.beginPath();\r\n \r\n const baseY = this.height * wave.yOffset + layer * 3;\r\n \r\n const mouseEffect = this.mouseActive ? (mouseNormX - 0.5) * (wave.mouseInfluence || 0.45) * 150 : 0;\r\n \r\n ctx.moveTo(0, this.height);\r\n\r\n const step = Math.max(2, Math.floor(this.width / 480));\r\n \r\n for (let x = 0; x <= this.width; x += step) {\r\n let y = baseY;\r\n \r\n y += Math.sin(x * wave.frequency + this.time * wave.speed + layer * 0.1) * wave.amplitude;\r\n y += Math.sin(x * wave.frequency * 1.5 + this.time * wave.speed * 0.7 + wi) * wave.amplitude * 0.3;\r\n \r\n if (this.mouseActive) {\r\n const dx = x - this.mouseX;\r\n const dist = Math.abs(dx) / this.width;\r\n y += Math.sin(dist * Math.PI) * (mouseNormY > 0 ? (this.mouseY - this.height / 2) : 0) * 0.1 * (wave.mouseInfluence || 0.45);\r\n }\r\n \r\n y += mouseEffect * Math.sin((x / this.width) * Math.PI);\r\n \r\n ctx.lineTo(x, y);\r\n }\r\n \r\n ctx.lineTo(this.width, this.height);\r\n \r\n ctx.closePath();\r\n \r\n const baseColor = wave.color || 'rgba(139, 114, 86, 0.25)';\r\n \r\n ctx.fillStyle = baseColor.replace(/([\\d.]+)\\)$/, (m) => `${parseFloat(m) * layerOpacity})`);\r\n \r\n if (wave.lineWidth != null) {\r\n ctx.lineWidth = wave.lineWidth;\r\n }\r\n \r\n ctx.fill();\r\n }\r\n }\r\n }\r\n\r\n /**\r\n * Updates and renders the particle system, including movement and mouse repulsion logic.\r\n * @param {CanvasRenderingContext2D} ctx\r\n */\r\n drawParticles(ctx) {\r\n const maxDist = 150;\r\n \r\n for (const p of this.particles) {\r\n p.pulse += p.pulseSpeed;\r\n \r\n const currentOpacity = p.opacity * (Math.sin(p.pulse) * 0.3 + 0.7);\r\n \r\n if (this.mouseActive) {\r\n const dx = p.x - this.mouseX, dy = p.y - this.mouseY, dist = Math.sqrt(dx * dx + dy * dy);\r\n \r\n if (dist < maxDist && dist > 0.1) {\r\n const force = (1 - dist / maxDist) * 2;\r\n p.x += (dx / dist) * force;\r\n p.y += (dy / dist) * force;\r\n }\r\n }\r\n \r\n p.x += p.speedX;\r\n p.y += p.speedY;\r\n \r\n if (p.x < 0) {\r\n p.x = this.width;\r\n }\r\n \r\n if (p.x > this.width) {\r\n p.x = 0;\r\n }\r\n \r\n if (p.y < 0) {\r\n p.y = this.height;\r\n }\r\n \r\n if (p.y > this.height) {\r\n p.y = 0;\r\n }\r\n\r\n const grad = ctx.createRadialGradient(p.x, p.y, 0, p.x, p.y, p.size * 2);\r\n \r\n grad.addColorStop(0, p.colorPrefix + currentOpacity + ')');\r\n \r\n grad.addColorStop(1, p.colorPrefix + '0)');\r\n \r\n ctx.beginPath();\r\n \r\n ctx.fillStyle = grad;\r\n \r\n ctx.arc(p.x, p.y, p.size * 2, 0, Math.PI * 2);\r\n \r\n ctx.fill();\r\n \r\n ctx.beginPath();\r\n \r\n ctx.fillStyle = p.colorPrefix + (currentOpacity * 0.8) + ')';\r\n \r\n ctx.arc(p.x, p.y, p.size * 0.5, 0, Math.PI * 2);\r\n \r\n ctx.fill();\r\n }\r\n }\r\n\r\n /**\r\n * Renders a soft glow effect centered at the current mouse position.\r\n * @param {CanvasRenderingContext2D} ctx\r\n */\r\n drawMouseGlow(ctx) {\r\n if (!this.mouseActive) {\r\n return;\r\n }\r\n \r\n const gradient = ctx.createRadialGradient(this.mouseX, this.mouseY, 0, this.mouseX, this.mouseY, 200);\r\n \r\n const stops = this.config.colors.mouseGlowStops;\r\n \r\n stops.forEach((s) => gradient.addColorStop(s.offset, s.color));\r\n \r\n ctx.beginPath();\r\n \r\n ctx.fillStyle = gradient;\r\n \r\n ctx.arc(this.mouseX, this.mouseY, 200, 0, Math.PI * 2);\r\n \r\n ctx.fill();\r\n }\r\n\r\n /**\r\n * The main animation loop. Updates state and draws the next frame.\r\n */\r\n animate() {\r\n if (!this.ctx) {\r\n return;\r\n }\r\n \r\n this.time += 1;\r\n \r\n this.mouseX += (this.targetMouseX - this.mouseX) * 0.08;\r\n this.mouseY += (this.targetMouseY - this.mouseY) * 0.08;\r\n \r\n this.ctx.clearRect(0, 0, this.width, this.height);\r\n \r\n this.drawBackground(this.ctx);\r\n this.drawWaves(this.ctx);\r\n this.drawParticles(this.ctx);\r\n this.drawMouseGlow(this.ctx);\r\n \r\n if (this.isRunning) {\r\n requestAnimationFrame(() => this.animate());\r\n }\r\n }\r\n\r\n /**\r\n * Starts or resumes the animation loop and attaches interaction listeners.\r\n */\r\n start() {\r\n if (this.isRunning) {\r\n return;\r\n }\r\n \r\n if (typeof window !== 'undefined') {\r\n window.addEventListener('mousemove', this._onMouseMove, {passive: true});\r\n window.addEventListener('touchmove', this._onTouchMove, {passive: false});\r\n window.addEventListener('touchend', this._onTouchEnd, {passive: true});\r\n window.addEventListener('mouseleave', this._onMouseLeave);\r\n }\r\n \r\n this.isRunning = true;\r\n \r\n this.animate();\r\n }\r\n\r\n /**\r\n * Pauses the animation loop and detaches interaction listeners to save resources.\r\n */\r\n stop() {\r\n this.isRunning = false;\r\n \r\n if (typeof window !== 'undefined') {\r\n window.removeEventListener('mousemove', this._onMouseMove);\r\n window.removeEventListener('touchmove', this._onTouchMove);\r\n window.removeEventListener('touchend', this._onTouchEnd);\r\n window.removeEventListener('mouseleave', this._onMouseLeave);\r\n }\r\n }\r\n\r\n /**\r\n * Stops the animation, removes event listeners, and cleans up any auto-injected DOM elements.\r\n */\r\n destroy() {\r\n this.stop();\r\n \r\n if (typeof window !== 'undefined') {\r\n window.removeEventListener('resize', this._onResize);\r\n }\r\n \r\n if (this.canvas && this.canvas.id === 'wave-particles-canvas' && this.canvas.parentNode) {\r\n this.canvas.parentNode.removeChild(this.canvas);\r\n }\r\n \r\n this.canvas = null;\r\n this.ctx = null;\r\n }\r\n}\r\n\r\nexport default WaveParticles;\r\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": ["WaveParticles", "options", "providedCanvasOrSelector", "config", "resolvedCanvas", "rect", "ctx", "e", "userConfig", "defaults", "result", "key", "el", "body", "w", "h", "attrW", "attrH", "dpr", "countScale", "maxCount", "particleCount", "touch", "colors", "gradient", "c", "i", "mouseNormX", "mouseNormY", "wi", "wave", "numLayers", "layer", "layerOpacity", "baseY", "mouseEffect", "step", "x", "y", "dx", "dist", "baseColor", "m", "p", "currentOpacity", "dy", "force", "grad", "src_default"]
|
|
7
|
+
}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
(()=>{var l=class{constructor(e={}){let{canvas:i,config:t}=e||{};this.canvas=null,this.ctx=null,this.mouseX=0,this.mouseY=0,this.targetMouseX=-1e3,this.targetMouseY=-1e3,this.mouseActive=!1,this.width=0,this.height=0,this.config=this._mergeDefaults(t||{}),this.time=0,this.isRunning=!1,this.particles=[],this._resizeTimeout=null;let s=typeof document<"u"&&i?typeof i=="string"?document.querySelector(i):i:null;if(s){this.canvas=s;let o=s.getBoundingClientRect();this.width=Math.round(o.width||window.innerWidth),this.height=Math.round(o.height||window.innerHeight)}else this._createAutoCanvas();try{if(!this.canvas)throw new Error("No canvas element available.");let o=this.canvas.getContext("2d");if(!o)throw new Error("Could not get 2D rendering context.");this.ctx=o}catch(o){console.error("[WaveParticles] Canvas error:",o);return}this._onMouseMove=this._onMouseMove.bind(this),this._onMouseLeave=this._onMouseLeave.bind(this),this._onTouchMove=this._onTouchMove.bind(this),this._onTouchEnd=this._onTouchEnd.bind(this),this._onResize=this._onResize.bind(this),typeof document<"u"&&(window.addEventListener("resize",this._onResize),this.resize()),this.initParticles(),this.start()}_mergeDefaults(e){let i={waves:[{amplitude:60,frequency:.003,speed:.006,yOffset:.55,color:"rgba(248, 225, 231, 0.25)",lineWidth:1.5,mouseInfluence:.45},{amplitude:50,frequency:.004,speed:.008,yOffset:.6,color:"rgba(212, 165, 165, 0.2)",lineWidth:1,mouseInfluence:.5},{amplitude:70,frequency:.0025,speed:.005,yOffset:.65,color:"rgba(255, 255, 255, 0.2)",lineWidth:1.5,mouseInfluence:.4},{amplitude:40,frequency:.0035,speed:.01,yOffset:.7,color:"rgba(201, 169, 110, 0.12)",lineWidth:1,mouseInfluence:.55}],particles:{countScale:8e3,maxCount:180},colors:{backgroundGradient:["#fdfcfb","#f7ede2","#e8d5d0"],particleColorPrefixes:["rgba(248, 225, 231,","rgba(212, 165, 165,","rgba(201, 169, 110,","rgba(255, 255, 255,","rgba(232, 213, 208,"],mouseGlowStops:[{offset:0,color:"rgba(255, 255, 255, 0.08)"},{offset:.5,color:"rgba(248, 225, 231, 0.04)"},{offset:1,color:"rgba(248, 225, 231, 0)"}]}},t={};for(let s in i)i.hasOwnProperty(s)&&(e&&e[s]?typeof i[s]=="object"&&!Array.isArray(i[s])&&typeof e[s]=="object"?t[s]=Object.assign({},i[s],e[s]):s==="waves"?t.waves=Array.isArray(e.waves)?[...e.waves]:JSON.parse(JSON.stringify(i.waves)):t[s]=e[s]:t[s]=JSON.parse(JSON.stringify(i[s])));return t}_createAutoCanvas(){if(typeof document>"u")return;let e=document.createElement("canvas");e.id="wave-particles-canvas",Object.assign(e.style,{position:"fixed",top:"0",left:"0",width:"100%",height:"100%",zIndex:"-1",pointerEvents:"none"});let i=document.body;i&&(i.insertBefore(e,i.firstChild),this.canvas=e,this.width=window.innerWidth,this.height=window.innerHeight)}_onResize(){this._resizeTimeout&&clearTimeout(this._resizeTimeout),this._resizeTimeout=setTimeout(()=>{this.resize()},250)}resize(){if(!this.canvas)return;let e=this.canvas.getBoundingClientRect(),i,t,s=parseInt(this.canvas.getAttribute("width"),10),o=parseInt(this.canvas.getAttribute("height"),10);s&&o?(i=Math.round(e.width||s),t=Math.round(e.height||o)):(i=window.innerWidth,t=window.innerHeight,!this.canvas.parentNode&&document.body&&document.body.insertBefore(this.canvas,document.body.firstChild)),this.width=i,this.height=t;let n=window.devicePixelRatio||1;this.canvas.width=Math.round(i*n),this.canvas.height=Math.round(t*n),this.ctx&&this.isRunning&&this.initParticles()}initParticles(){let{countScale:e=8e3,maxCount:i=180}=this.config.particles,t=Math.min(Math.floor(this.width*this.height/(e>0?e:8e3)),i);this.particles=Array.from({length:t},()=>({x:Math.random()*this.width,y:Math.random()*this.height,size:Math.random()*4+1.5,speedX:(Math.random()-.5)*.4,speedY:(Math.random()-.5)*.4,opacity:Math.random()*.6+.3,pulse:Math.random()*Math.PI*2,pulseSpeed:Math.random()*.025+.01,colorPrefix:this.config.colors.particleColorPrefixes[Math.floor(Math.random()*this.config.colors.particleColorPrefixes.length)]}))}_onMouseMove(e){let i=this.canvas.getBoundingClientRect();i&&(this.targetMouseX=e.clientX-i.left,this.targetMouseY=e.clientY-i.top,this.mouseActive=!0)}_onMouseLeave(){this.mouseActive=!1}_onTouchMove(e){let i=e.touches[0],t=this.canvas.getBoundingClientRect();!i||!t||(this.targetMouseX=i.clientX-t.left,this.targetMouseY=i.clientY-t.top,this.mouseActive=!0)}_onTouchEnd(){this.mouseActive=!1}drawBackground(e){let i=this.config.colors.backgroundGradient,t=e.createRadialGradient(this.width/2,this.height/2,0,this.width/2,this.height/2,Math.max(this.width,this.height)*.8);i.forEach((s,o)=>t.addColorStop(o/(i.length-1||1),s)),e.fillStyle=t,e.fillRect(0,0,this.width,this.height)}drawWaves(e){let i=this.mouseActive?this.mouseX/this.width:-1,t=this.mouseActive?this.mouseY/this.height:-1;for(let s=0;s<this.config.waves.length;s++){let o=this.config.waves[s],n=Math.max(2,o.layers||2);for(let h=0;h<n;h++){let r=(1-h/n)*.5+.5;e.beginPath();let d=this.height*o.yOffset+h*3,f=this.mouseActive?(i-.5)*(o.mouseInfluence||.45)*150:0;e.moveTo(0,this.height);let m=Math.max(2,Math.floor(this.width/480));for(let a=0;a<=this.width;a+=m){let c=d;if(c+=Math.sin(a*o.frequency+this.time*o.speed+h*.1)*o.amplitude,c+=Math.sin(a*o.frequency*1.5+this.time*o.speed*.7+s)*o.amplitude*.3,this.mouseActive){let w=a-this.mouseX,g=Math.abs(w)/this.width;c+=Math.sin(g*Math.PI)*(t>0?this.mouseY-this.height/2:0)*.1*(o.mouseInfluence||.45)}c+=f*Math.sin(a/this.width*Math.PI),e.lineTo(a,c)}e.lineTo(this.width,this.height),e.closePath();let v=o.color||"rgba(139, 114, 86, 0.25)";e.fillStyle=v.replace(/([\d.]+)\)$/,a=>`${parseFloat(a)*r})`),o.lineWidth!=null&&(e.lineWidth=o.lineWidth),e.fill()}}}drawParticles(e){for(let t of this.particles){t.pulse+=t.pulseSpeed;let s=t.opacity*(Math.sin(t.pulse)*.3+.7);if(this.mouseActive){let n=t.x-this.mouseX,h=t.y-this.mouseY,r=Math.sqrt(n*n+h*h);if(r<150&&r>.1){let d=(1-r/150)*2;t.x+=n/r*d,t.y+=h/r*d}}t.x+=t.speedX,t.y+=t.speedY,t.x<0&&(t.x=this.width),t.x>this.width&&(t.x=0),t.y<0&&(t.y=this.height),t.y>this.height&&(t.y=0);let o=e.createRadialGradient(t.x,t.y,0,t.x,t.y,t.size*2);o.addColorStop(0,t.colorPrefix+s+")"),o.addColorStop(1,t.colorPrefix+"0)"),e.beginPath(),e.fillStyle=o,e.arc(t.x,t.y,t.size*2,0,Math.PI*2),e.fill(),e.beginPath(),e.fillStyle=t.colorPrefix+s*.8+")",e.arc(t.x,t.y,t.size*.5,0,Math.PI*2),e.fill()}}drawMouseGlow(e){if(!this.mouseActive)return;let i=e.createRadialGradient(this.mouseX,this.mouseY,0,this.mouseX,this.mouseY,200);this.config.colors.mouseGlowStops.forEach(s=>i.addColorStop(s.offset,s.color)),e.beginPath(),e.fillStyle=i,e.arc(this.mouseX,this.mouseY,200,0,Math.PI*2),e.fill()}animate(){this.ctx&&(this.time+=1,this.mouseX+=(this.targetMouseX-this.mouseX)*.08,this.mouseY+=(this.targetMouseY-this.mouseY)*.08,this.ctx.clearRect(0,0,this.width,this.height),this.drawBackground(this.ctx),this.drawWaves(this.ctx),this.drawParticles(this.ctx),this.drawMouseGlow(this.ctx),this.isRunning&&requestAnimationFrame(()=>this.animate()))}start(){this.isRunning||(typeof window<"u"&&(window.addEventListener("mousemove",this._onMouseMove,{passive:!0}),window.addEventListener("touchmove",this._onTouchMove,{passive:!1}),window.addEventListener("touchend",this._onTouchEnd,{passive:!0}),window.addEventListener("mouseleave",this._onMouseLeave)),this.isRunning=!0,this.animate())}stop(){this.isRunning=!1,typeof window<"u"&&(window.removeEventListener("mousemove",this._onMouseMove),window.removeEventListener("touchmove",this._onTouchMove),window.removeEventListener("touchend",this._onTouchEnd),window.removeEventListener("mouseleave",this._onMouseLeave))}destroy(){this.stop(),typeof window<"u"&&window.removeEventListener("resize",this._onResize),this.canvas&&this.canvas.id==="wave-particles-canvas"&&this.canvas.parentNode&&this.canvas.parentNode.removeChild(this.canvas),this.canvas=null,this.ctx=null}},u=l;typeof window<"u"?window.WaveParticles=u:typeof globalThis<"u"&&(globalThis.WaveParticles=u);})();
|
|
2
|
+
//# sourceMappingURL=wave-particles.iife.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../src/index.js", "../src/browser.js"],
|
|
4
|
+
"sourcesContent": ["/**\r\n * js-waves-particles\r\n * A canvas-based wave and particle animation library with mouse-reactive effects.\r\n * Developed by Luis 'PlatinumBlade' Moniz.\r\n */\r\n\r\nclass WaveParticles {\r\n /**\r\n * Initializes a new WaveParticles animation engine.\r\n *\r\n * @param {import('./index').WaveParticlesOptions} [options] - Configuration for the engine.\r\n * @param {HTMLCanvasElement|string} [options.canvas] - The target canvas element or a CSS selector (e.g., '#bg'). If omitted, a full-screen fixed canvas is automatically created.\r\n * @param {import('./index').WaveParticlesConfig} [options.config] - Visual parameters for waves, particles, and colors.\r\n */\r\n constructor(options = {}) {\r\n const {canvas: providedCanvasOrSelector, config} = options || {};\r\n\r\n // --- State ---\r\n this.canvas = null;\r\n this.ctx = null;\r\n this.mouseX = 0;\r\n this.mouseY = 0;\r\n this.targetMouseX = -1000;\r\n this.targetMouseY = -1000;\r\n this.mouseActive = false;\r\n this.width = 0;\r\n this.height = 0;\r\n this.config = this._mergeDefaults(config || {});\r\n this.time = 0;\r\n this.isRunning = false;\r\n this.particles = [];\r\n this._resizeTimeout = null;\r\n\r\n const resolvedCanvas = typeof document !== 'undefined' && providedCanvasOrSelector ? (() => {\r\n if (typeof providedCanvasOrSelector === 'string') {\r\n return /** @type {HTMLCanvasElement|null} */ (document.querySelector(providedCanvasOrSelector));\r\n }\r\n\r\n return /** @type {HTMLCanvasElement | null} */ (providedCanvasOrSelector);\r\n })() : null;\r\n\r\n if (resolvedCanvas) {\r\n this.canvas = resolvedCanvas;\r\n const rect = resolvedCanvas.getBoundingClientRect();\r\n this.width = Math.round(rect.width || window.innerWidth);\r\n this.height = Math.round(rect.height || window.innerHeight);\r\n } else {\r\n this._createAutoCanvas();\r\n }\r\n\r\n try {\r\n if (!this.canvas) {\r\n throw new Error('No canvas element available.');\r\n }\r\n\r\n const ctx = this.canvas.getContext('2d');\r\n\r\n if (!ctx) {\r\n throw new Error('Could not get 2D rendering context.');\r\n }\r\n\r\n this.ctx = ctx;\r\n } catch (e) {\r\n console.error('[WaveParticles] Canvas error:', e);\r\n return;\r\n }\r\n\r\n this._onMouseMove = this._onMouseMove.bind(this);\r\n this._onMouseLeave = this._onMouseLeave.bind(this);\r\n this._onTouchMove = this._onTouchMove.bind(this);\r\n this._onTouchEnd = this._onTouchEnd.bind(this);\r\n this._onResize = this._onResize.bind(this);\r\n\r\n if (typeof document !== 'undefined') {\r\n window.addEventListener('resize', this._onResize);\r\n this.resize(); // Initial size setup\r\n }\r\n\r\n this.initParticles();\r\n this.start();\r\n }\r\n\r\n /**\r\n * Internal method to merge user configuration with library defaults.\r\n * @private\r\n */\r\n _mergeDefaults(userConfig) {\r\n const defaults = {\r\n waves: [\r\n {\r\n amplitude: 60,\r\n frequency: 0.003,\r\n speed: 0.006,\r\n yOffset: 0.55,\r\n color: 'rgba(248, 225, 231, 0.25)',\r\n lineWidth: 1.5,\r\n mouseInfluence: 0.45\r\n },\r\n {\r\n amplitude: 50,\r\n frequency: 0.004,\r\n speed: 0.008,\r\n yOffset: 0.60,\r\n color: 'rgba(212, 165, 165, 0.2)',\r\n lineWidth: 1,\r\n mouseInfluence: 0.5\r\n },\r\n {\r\n amplitude: 70,\r\n frequency: 0.0025,\r\n speed: 0.005,\r\n yOffset: 0.65,\r\n color: 'rgba(255, 255, 255, 0.2)',\r\n lineWidth: 1.5,\r\n mouseInfluence: 0.4\r\n },\r\n {\r\n amplitude: 40,\r\n frequency: 0.0035,\r\n speed: 0.010,\r\n yOffset: 0.70,\r\n color: 'rgba(201, 169, 110, 0.12)',\r\n lineWidth: 1,\r\n mouseInfluence: 0.55\r\n }\r\n ],\r\n particles: {countScale: 8000, maxCount: 180},\r\n colors: {\r\n backgroundGradient: ['#fdfcfb', '#f7ede2', '#e8d5d0'],\r\n particleColorPrefixes: [\r\n 'rgba(248, 225, 231,',\r\n 'rgba(212, 165, 165,',\r\n 'rgba(201, 169, 110,',\r\n 'rgba(255, 255, 255,',\r\n 'rgba(232, 213, 208,'\r\n ],\r\n mouseGlowStops: [\r\n {offset: 0, color: 'rgba(255, 255, 255, 0.08)'},\r\n {offset: 0.5, color: 'rgba(248, 225, 231, 0.04)'},\r\n {offset: 1, color: 'rgba(248, 225, 231, 0)'}\r\n ]\r\n }\r\n };\r\n\r\n const result = {};\r\n\r\n for (const key in defaults) {\r\n if (!defaults.hasOwnProperty(key)) {\r\n continue;\r\n }\r\n\r\n if (!(userConfig && userConfig[key])) {\r\n result[key] = JSON.parse(JSON.stringify(defaults[key]));\r\n } else if (typeof defaults[key] === 'object' && !Array.isArray(defaults[key]) && typeof userConfig[key] === 'object') {\r\n result[key] = Object.assign({}, defaults[key], userConfig[key]);\r\n } else if (key === 'waves') {\r\n result.waves = Array.isArray(userConfig.waves) ? [...userConfig.waves] : JSON.parse(JSON.stringify(defaults.waves));\r\n } else {\r\n result[key] = userConfig[key];\r\n }\r\n }\r\n \r\n return result;\r\n }\r\n\r\n /**\r\n * Automatically creates and injects a canvas element into the DOM if none was provided.\r\n * @private\r\n */\r\n _createAutoCanvas() {\r\n if (typeof document === 'undefined') {\r\n return;\r\n }\r\n\r\n const el = document.createElement('canvas');\r\n\r\n el.id = 'wave-particles-canvas';\r\n\r\n Object.assign(el.style, {\r\n position: 'fixed',\r\n top: '0',\r\n left: '0',\r\n width: '100%',\r\n height: '100%',\r\n zIndex: '-1',\r\n pointerEvents: 'none'\r\n });\r\n\r\n const body = document.body;\r\n\r\n if (!body) {\r\n return;\r\n }\r\n\r\n body.insertBefore(el, body.firstChild);\r\n this.canvas = el;\r\n this.width = window.innerWidth;\r\n this.height = window.innerHeight;\r\n }\r\n\r\n /**\r\n * Event handler for window resize events, including debouncing to prevent performance issues.\r\n * @private\r\n */\r\n _onResize() {\r\n if (this._resizeTimeout) clearTimeout(this._resizeTimeout);\r\n // Debounce resize to prevent flashing and high CPU usage\r\n this._resizeTimeout = setTimeout(() => {\r\n this.resize();\r\n }, 250);\r\n }\r\n\r\n /**\r\n * Recalculates canvas dimensions based on its display size and Device Pixel Ratio.\r\n * Re-initializes particles to match the new surface area.\r\n */\r\n resize() {\r\n if (!this.canvas) {\r\n return;\r\n }\r\n\r\n const rect = this.canvas.getBoundingClientRect();\r\n\r\n let w, h;\r\n\r\n const attrW = parseInt(this.canvas.getAttribute('width'), 10);\r\n const attrH = parseInt(this.canvas.getAttribute('height'), 10);\r\n\r\n if (attrW && attrH) {\r\n w = Math.round(rect.width || attrW);\r\n h = Math.round(rect.height || attrH);\r\n } else {\r\n w = window.innerWidth;\r\n h = window.innerHeight;\r\n\r\n if (!this.canvas.parentNode && document.body) {\r\n document.body.insertBefore(this.canvas, document.body.firstChild);\r\n }\r\n }\r\n \r\n this.width = w;\r\n this.height = h;\r\n\r\n const dpr = window.devicePixelRatio || 1;\r\n\r\n this.canvas.width = Math.round(w * dpr);\r\n this.canvas.height = Math.round(h * dpr);\r\n\r\n if (this.ctx && this.isRunning) {\r\n this.initParticles();\r\n }\r\n }\r\n\r\n /**\r\n * Generates a new set of particles based on the current canvas area and configuration.\r\n */\r\n initParticles() {\r\n const {countScale = 8000, maxCount = 180} = this.config.particles;\r\n\r\n const particleCount = Math.min(Math.floor((this.width * this.height) / (countScale > 0 ? countScale : 8000)), maxCount);\r\n\r\n this.particles = Array.from({length: particleCount}, () => ({\r\n x: Math.random() * this.width,\r\n y: Math.random() * this.height,\r\n size: Math.random() * 4 + 1.5,\r\n speedX: (Math.random() - 0.5) * 0.4,\r\n speedY: (Math.random() - 0.5) * 0.4,\r\n opacity: Math.random() * 0.6 + 0.3,\r\n pulse: Math.random() * Math.PI * 2,\r\n pulseSpeed: Math.random() * 0.025 + 0.01,\r\n colorPrefix: this.config.colors.particleColorPrefixes[Math.floor(Math.random() * this.config.colors.particleColorPrefixes.length)]\r\n }));\r\n }\r\n\r\n _onMouseMove(e) {\r\n const rect = this.canvas.getBoundingClientRect();\r\n \r\n if (!rect) {\r\n return;\r\n }\r\n\r\n // Calculate position relative to the canvas in CSS pixels\r\n this.targetMouseX = e.clientX - rect.left;\r\n this.targetMouseY = e.clientY - rect.top;\r\n this.mouseActive = true;\r\n }\r\n\r\n _onMouseLeave() {\r\n this.mouseActive = false;\r\n }\r\n\r\n _onTouchMove(e) {\r\n const touch = e.touches[0];\r\n const rect = this.canvas.getBoundingClientRect();\r\n \r\n if (!touch || !rect) {\r\n return;\r\n }\r\n\r\n this.targetMouseX = touch.clientX - rect.left;\r\n this.targetMouseY = touch.clientY - rect.top;\r\n this.mouseActive = true;\r\n }\r\n\r\n _onTouchEnd() {\r\n this.mouseActive = false;\r\n }\r\n\r\n /**\r\n * Renders the radial gradient background across the entire canvas.\r\n * @param {CanvasRenderingContext2D} ctx\r\n */\r\n drawBackground(ctx) {\r\n const colors = this.config.colors.backgroundGradient;\r\n \r\n const gradient = ctx.createRadialGradient(this.width / 2, this.height / 2, 0, this.width / 2, this.height / 2, Math.max(this.width, this.height) * 0.8);\r\n \r\n colors.forEach((c, i) => gradient.addColorStop(i / (colors.length - 1 || 1), c));\r\n \r\n ctx.fillStyle = gradient;\r\n \r\n ctx.fillRect(0, 0, this.width, this.height);\r\n }\r\n\r\n /**\r\n * Renders all wave layers, accounting for mouse interaction and layer offsets.\r\n * @param {CanvasRenderingContext2D} ctx\r\n */\r\n drawWaves(ctx) {\r\n const mouseNormX = this.mouseActive ? this.mouseX / this.width : -1;\r\n const mouseNormY = this.mouseActive ? this.mouseY / this.height : -1;\r\n\r\n for (let wi = 0; wi < this.config.waves.length; wi++) {\r\n const wave = this.config.waves[wi];\r\n const numLayers = Math.max(2, wave.layers || 2);\r\n\r\n for (let layer = 0; layer < numLayers; layer++) {\r\n const layerOpacity = (1 - layer / numLayers) * 0.5 + 0.5;\r\n \r\n ctx.beginPath();\r\n \r\n const baseY = this.height * wave.yOffset + layer * 3;\r\n \r\n const mouseEffect = this.mouseActive ? (mouseNormX - 0.5) * (wave.mouseInfluence || 0.45) * 150 : 0;\r\n \r\n ctx.moveTo(0, this.height);\r\n\r\n const step = Math.max(2, Math.floor(this.width / 480));\r\n \r\n for (let x = 0; x <= this.width; x += step) {\r\n let y = baseY;\r\n \r\n y += Math.sin(x * wave.frequency + this.time * wave.speed + layer * 0.1) * wave.amplitude;\r\n y += Math.sin(x * wave.frequency * 1.5 + this.time * wave.speed * 0.7 + wi) * wave.amplitude * 0.3;\r\n \r\n if (this.mouseActive) {\r\n const dx = x - this.mouseX;\r\n const dist = Math.abs(dx) / this.width;\r\n y += Math.sin(dist * Math.PI) * (mouseNormY > 0 ? (this.mouseY - this.height / 2) : 0) * 0.1 * (wave.mouseInfluence || 0.45);\r\n }\r\n \r\n y += mouseEffect * Math.sin((x / this.width) * Math.PI);\r\n \r\n ctx.lineTo(x, y);\r\n }\r\n \r\n ctx.lineTo(this.width, this.height);\r\n \r\n ctx.closePath();\r\n \r\n const baseColor = wave.color || 'rgba(139, 114, 86, 0.25)';\r\n \r\n ctx.fillStyle = baseColor.replace(/([\\d.]+)\\)$/, (m) => `${parseFloat(m) * layerOpacity})`);\r\n \r\n if (wave.lineWidth != null) {\r\n ctx.lineWidth = wave.lineWidth;\r\n }\r\n \r\n ctx.fill();\r\n }\r\n }\r\n }\r\n\r\n /**\r\n * Updates and renders the particle system, including movement and mouse repulsion logic.\r\n * @param {CanvasRenderingContext2D} ctx\r\n */\r\n drawParticles(ctx) {\r\n const maxDist = 150;\r\n \r\n for (const p of this.particles) {\r\n p.pulse += p.pulseSpeed;\r\n \r\n const currentOpacity = p.opacity * (Math.sin(p.pulse) * 0.3 + 0.7);\r\n \r\n if (this.mouseActive) {\r\n const dx = p.x - this.mouseX, dy = p.y - this.mouseY, dist = Math.sqrt(dx * dx + dy * dy);\r\n \r\n if (dist < maxDist && dist > 0.1) {\r\n const force = (1 - dist / maxDist) * 2;\r\n p.x += (dx / dist) * force;\r\n p.y += (dy / dist) * force;\r\n }\r\n }\r\n \r\n p.x += p.speedX;\r\n p.y += p.speedY;\r\n \r\n if (p.x < 0) {\r\n p.x = this.width;\r\n }\r\n \r\n if (p.x > this.width) {\r\n p.x = 0;\r\n }\r\n \r\n if (p.y < 0) {\r\n p.y = this.height;\r\n }\r\n \r\n if (p.y > this.height) {\r\n p.y = 0;\r\n }\r\n\r\n const grad = ctx.createRadialGradient(p.x, p.y, 0, p.x, p.y, p.size * 2);\r\n \r\n grad.addColorStop(0, p.colorPrefix + currentOpacity + ')');\r\n \r\n grad.addColorStop(1, p.colorPrefix + '0)');\r\n \r\n ctx.beginPath();\r\n \r\n ctx.fillStyle = grad;\r\n \r\n ctx.arc(p.x, p.y, p.size * 2, 0, Math.PI * 2);\r\n \r\n ctx.fill();\r\n \r\n ctx.beginPath();\r\n \r\n ctx.fillStyle = p.colorPrefix + (currentOpacity * 0.8) + ')';\r\n \r\n ctx.arc(p.x, p.y, p.size * 0.5, 0, Math.PI * 2);\r\n \r\n ctx.fill();\r\n }\r\n }\r\n\r\n /**\r\n * Renders a soft glow effect centered at the current mouse position.\r\n * @param {CanvasRenderingContext2D} ctx\r\n */\r\n drawMouseGlow(ctx) {\r\n if (!this.mouseActive) {\r\n return;\r\n }\r\n \r\n const gradient = ctx.createRadialGradient(this.mouseX, this.mouseY, 0, this.mouseX, this.mouseY, 200);\r\n \r\n const stops = this.config.colors.mouseGlowStops;\r\n \r\n stops.forEach((s) => gradient.addColorStop(s.offset, s.color));\r\n \r\n ctx.beginPath();\r\n \r\n ctx.fillStyle = gradient;\r\n \r\n ctx.arc(this.mouseX, this.mouseY, 200, 0, Math.PI * 2);\r\n \r\n ctx.fill();\r\n }\r\n\r\n /**\r\n * The main animation loop. Updates state and draws the next frame.\r\n */\r\n animate() {\r\n if (!this.ctx) {\r\n return;\r\n }\r\n \r\n this.time += 1;\r\n \r\n this.mouseX += (this.targetMouseX - this.mouseX) * 0.08;\r\n this.mouseY += (this.targetMouseY - this.mouseY) * 0.08;\r\n \r\n this.ctx.clearRect(0, 0, this.width, this.height);\r\n \r\n this.drawBackground(this.ctx);\r\n this.drawWaves(this.ctx);\r\n this.drawParticles(this.ctx);\r\n this.drawMouseGlow(this.ctx);\r\n \r\n if (this.isRunning) {\r\n requestAnimationFrame(() => this.animate());\r\n }\r\n }\r\n\r\n /**\r\n * Starts or resumes the animation loop and attaches interaction listeners.\r\n */\r\n start() {\r\n if (this.isRunning) {\r\n return;\r\n }\r\n \r\n if (typeof window !== 'undefined') {\r\n window.addEventListener('mousemove', this._onMouseMove, {passive: true});\r\n window.addEventListener('touchmove', this._onTouchMove, {passive: false});\r\n window.addEventListener('touchend', this._onTouchEnd, {passive: true});\r\n window.addEventListener('mouseleave', this._onMouseLeave);\r\n }\r\n \r\n this.isRunning = true;\r\n \r\n this.animate();\r\n }\r\n\r\n /**\r\n * Pauses the animation loop and detaches interaction listeners to save resources.\r\n */\r\n stop() {\r\n this.isRunning = false;\r\n \r\n if (typeof window !== 'undefined') {\r\n window.removeEventListener('mousemove', this._onMouseMove);\r\n window.removeEventListener('touchmove', this._onTouchMove);\r\n window.removeEventListener('touchend', this._onTouchEnd);\r\n window.removeEventListener('mouseleave', this._onMouseLeave);\r\n }\r\n }\r\n\r\n /**\r\n * Stops the animation, removes event listeners, and cleans up any auto-injected DOM elements.\r\n */\r\n destroy() {\r\n this.stop();\r\n \r\n if (typeof window !== 'undefined') {\r\n window.removeEventListener('resize', this._onResize);\r\n }\r\n \r\n if (this.canvas && this.canvas.id === 'wave-particles-canvas' && this.canvas.parentNode) {\r\n this.canvas.parentNode.removeChild(this.canvas);\r\n }\r\n \r\n this.canvas = null;\r\n this.ctx = null;\r\n }\r\n}\r\n\r\nexport default WaveParticles;\r\n", "import WaveParticles from './index.js';\r\n\r\n/**\r\n * Browser-only entry point for IIFE / script-tag usage.\r\n * Exposes the WaveParticles class directly on the global object.\r\n */\r\nif (typeof window !== 'undefined') {\r\n window.WaveParticles = WaveParticles;\r\n} else if (typeof globalThis !== 'undefined') {\r\n globalThis.WaveParticles = WaveParticles;\r\n}\r\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": ["WaveParticles", "options", "providedCanvasOrSelector", "config", "resolvedCanvas", "rect", "ctx", "e", "userConfig", "defaults", "result", "key", "el", "body", "w", "h", "attrW", "attrH", "dpr", "countScale", "maxCount", "particleCount", "touch", "colors", "gradient", "c", "i", "mouseNormX", "mouseNormY", "wi", "wave", "numLayers", "layer", "layerOpacity", "baseY", "mouseEffect", "step", "x", "y", "dx", "dist", "baseColor", "m", "p", "currentOpacity", "dy", "force", "grad", "src_default", "src_default"]
|
|
7
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "js-waves-particles",
|
|
3
|
+
"version": "1.0.2",
|
|
4
|
+
"description": "A lightweight, canvas-based wave and particle animation library with mouse-reactive effects. Perfect for adding a subtle, organic feel to backgrounds in web projects.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./dist/wave-particles.iife.js",
|
|
7
|
+
"module": "./dist/wave-particles.esm.js",
|
|
8
|
+
"types": "./src/index.d.ts",
|
|
9
|
+
"exports": {
|
|
10
|
+
".": {
|
|
11
|
+
"import": {
|
|
12
|
+
"types": "./src/index.d.ts",
|
|
13
|
+
"default": "./dist/wave-particles.esm.js"
|
|
14
|
+
},
|
|
15
|
+
"require": {
|
|
16
|
+
"types": "./src/index.d.ts",
|
|
17
|
+
"default": "./dist/wave-particles.iife.js"
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
},
|
|
21
|
+
"files": [
|
|
22
|
+
"dist/",
|
|
23
|
+
"src/"
|
|
24
|
+
],
|
|
25
|
+
"scripts": {
|
|
26
|
+
"build:esm": "node build.js esm",
|
|
27
|
+
"build:umd": "node build.js umd",
|
|
28
|
+
"build": "npm run build:esm && npm run build:umd"
|
|
29
|
+
},
|
|
30
|
+
"devDependencies": {
|
|
31
|
+
"esbuild": "^0.20.0"
|
|
32
|
+
},
|
|
33
|
+
"keywords": [
|
|
34
|
+
"canvas",
|
|
35
|
+
"animation",
|
|
36
|
+
"waves",
|
|
37
|
+
"particles",
|
|
38
|
+
"mouse-reactive",
|
|
39
|
+
"background-animation",
|
|
40
|
+
"visual-effects"
|
|
41
|
+
],
|
|
42
|
+
"author": "Luis 'PlatinumBlade' Moniz",
|
|
43
|
+
"license": "MIT",
|
|
44
|
+
"sideEffects": false,
|
|
45
|
+
"repository": {
|
|
46
|
+
"type": "git",
|
|
47
|
+
"url": "https://github.com/PlatinumBlade/js-waves-particles.git"
|
|
48
|
+
}
|
|
49
|
+
}
|
package/src/browser.js
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import WaveParticles from './index.js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Browser-only entry point for IIFE / script-tag usage.
|
|
5
|
+
* Exposes the WaveParticles class directly on the global object.
|
|
6
|
+
*/
|
|
7
|
+
if (typeof window !== 'undefined') {
|
|
8
|
+
window.WaveParticles = WaveParticles;
|
|
9
|
+
} else if (typeof globalThis !== 'undefined') {
|
|
10
|
+
globalThis.WaveParticles = WaveParticles;
|
|
11
|
+
}
|
package/src/index.d.ts
ADDED
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
export interface WaveConfig {
|
|
2
|
+
/** Peak-to-trough height of the sine wave in pixels. @default 60 */
|
|
3
|
+
amplitude?: number;
|
|
4
|
+
/** Spatial frequency (radians per pixel). Higher = tighter waves. @default 0.003 */
|
|
5
|
+
frequency?: number;
|
|
6
|
+
/** Temporal speed multiplier for animation. Higher = faster oscillation. @default 0.006 */
|
|
7
|
+
speed?: number;
|
|
8
|
+
/** Vertical position as a fraction of canvas height (0–1). @default 0.55 */
|
|
9
|
+
yOffset?: number;
|
|
10
|
+
/** Fill color with alpha for the wave layer. @default 'rgba(248, 225, 231, 0.25)' */
|
|
11
|
+
color?: string;
|
|
12
|
+
/** Stroke width. @default 1.5 */
|
|
13
|
+
lineWidth?: number;
|
|
14
|
+
/** How strongly mouse proximity displaces the wave vertically. @default 0.45 */
|
|
15
|
+
mouseInfluence?: number;
|
|
16
|
+
/** Number of visual layers to render for this wave definition. @default 2 */
|
|
17
|
+
layers?: number;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export interface ParticlesConfig {
|
|
21
|
+
/** Pixels per particle slot (lower = more particles). @default 8000 */
|
|
22
|
+
countScale?: number;
|
|
23
|
+
/** Hard cap on total particles regardless of canvas size. @default 180 */
|
|
24
|
+
maxCount?: number;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export interface ColorsConfig {
|
|
28
|
+
/** Radial gradient stops for the background. @default ['#fdfcfb', '#f7ede2', '#e8d5d0'] */
|
|
29
|
+
backgroundGradient?: string[];
|
|
30
|
+
/** RGBA prefix strings (e.g. 'rgba(248, 225, 231,'). */
|
|
31
|
+
particleColorPrefixes?: string[];
|
|
32
|
+
/** Stops for the mouse-glow radial gradient. */
|
|
33
|
+
mouseGlowStops?: Array<{ offset: number; color: string }>;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export interface WaveParticlesConfig {
|
|
37
|
+
/** Array of wave configurations. */
|
|
38
|
+
waves?: WaveConfig[];
|
|
39
|
+
/** Particle system settings. */
|
|
40
|
+
particles?: ParticlesConfig;
|
|
41
|
+
/** Color palette settings. */
|
|
42
|
+
colors?: ColorsConfig;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export interface WaveParticlesOptions {
|
|
46
|
+
/** Existing canvas element, CSS selector, or null to auto-create. */
|
|
47
|
+
canvas?: HTMLCanvasElement | string | null;
|
|
48
|
+
/** Animation configuration overrides. */
|
|
49
|
+
config?: WaveParticlesConfig;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
declare class WaveParticles {
|
|
53
|
+
/**
|
|
54
|
+
* Create a new WaveParticles instance.
|
|
55
|
+
* @param options Configuration options
|
|
56
|
+
*/
|
|
57
|
+
constructor(options?: WaveParticlesOptions);
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Resumes the animation loop if stopped.
|
|
61
|
+
*/
|
|
62
|
+
start(): void;
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Pauses the animation loop.
|
|
66
|
+
*/
|
|
67
|
+
stop(): void;
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Recalculates dimensions and redistributes particles.
|
|
71
|
+
* Useful if the canvas container size changes manually.
|
|
72
|
+
*/
|
|
73
|
+
resize(): void;
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Initializes or re-initializes particles.
|
|
77
|
+
* Useful if you want to force-refresh the particle system after config changes.
|
|
78
|
+
*/
|
|
79
|
+
initParticles(): void;
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Stops the animation and removes the auto-generated canvas from the DOM.
|
|
83
|
+
*/
|
|
84
|
+
destroy(): void;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export default WaveParticles;
|
package/src/index.js
ADDED
|
@@ -0,0 +1,551 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* js-waves-particles
|
|
3
|
+
* A canvas-based wave and particle animation library with mouse-reactive effects.
|
|
4
|
+
* Developed by Luis 'PlatinumBlade' Moniz.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
class WaveParticles {
|
|
8
|
+
/**
|
|
9
|
+
* Initializes a new WaveParticles animation engine.
|
|
10
|
+
*
|
|
11
|
+
* @param {import('./index').WaveParticlesOptions} [options] - Configuration for the engine.
|
|
12
|
+
* @param {HTMLCanvasElement|string} [options.canvas] - The target canvas element or a CSS selector (e.g., '#bg'). If omitted, a full-screen fixed canvas is automatically created.
|
|
13
|
+
* @param {import('./index').WaveParticlesConfig} [options.config] - Visual parameters for waves, particles, and colors.
|
|
14
|
+
*/
|
|
15
|
+
constructor(options = {}) {
|
|
16
|
+
const {canvas: providedCanvasOrSelector, config} = options || {};
|
|
17
|
+
|
|
18
|
+
// --- State ---
|
|
19
|
+
this.canvas = null;
|
|
20
|
+
this.ctx = null;
|
|
21
|
+
this.mouseX = 0;
|
|
22
|
+
this.mouseY = 0;
|
|
23
|
+
this.targetMouseX = -1000;
|
|
24
|
+
this.targetMouseY = -1000;
|
|
25
|
+
this.mouseActive = false;
|
|
26
|
+
this.width = 0;
|
|
27
|
+
this.height = 0;
|
|
28
|
+
this.config = this._mergeDefaults(config || {});
|
|
29
|
+
this.time = 0;
|
|
30
|
+
this.isRunning = false;
|
|
31
|
+
this.particles = [];
|
|
32
|
+
this._resizeTimeout = null;
|
|
33
|
+
|
|
34
|
+
const resolvedCanvas = typeof document !== 'undefined' && providedCanvasOrSelector ? (() => {
|
|
35
|
+
if (typeof providedCanvasOrSelector === 'string') {
|
|
36
|
+
return /** @type {HTMLCanvasElement|null} */ (document.querySelector(providedCanvasOrSelector));
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
return /** @type {HTMLCanvasElement | null} */ (providedCanvasOrSelector);
|
|
40
|
+
})() : null;
|
|
41
|
+
|
|
42
|
+
if (resolvedCanvas) {
|
|
43
|
+
this.canvas = resolvedCanvas;
|
|
44
|
+
const rect = resolvedCanvas.getBoundingClientRect();
|
|
45
|
+
this.width = Math.round(rect.width || window.innerWidth);
|
|
46
|
+
this.height = Math.round(rect.height || window.innerHeight);
|
|
47
|
+
} else {
|
|
48
|
+
this._createAutoCanvas();
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
try {
|
|
52
|
+
if (!this.canvas) {
|
|
53
|
+
throw new Error('No canvas element available.');
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const ctx = this.canvas.getContext('2d');
|
|
57
|
+
|
|
58
|
+
if (!ctx) {
|
|
59
|
+
throw new Error('Could not get 2D rendering context.');
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
this.ctx = ctx;
|
|
63
|
+
} catch (e) {
|
|
64
|
+
console.error('[WaveParticles] Canvas error:', e);
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
this._onMouseMove = this._onMouseMove.bind(this);
|
|
69
|
+
this._onMouseLeave = this._onMouseLeave.bind(this);
|
|
70
|
+
this._onTouchMove = this._onTouchMove.bind(this);
|
|
71
|
+
this._onTouchEnd = this._onTouchEnd.bind(this);
|
|
72
|
+
this._onResize = this._onResize.bind(this);
|
|
73
|
+
|
|
74
|
+
if (typeof document !== 'undefined') {
|
|
75
|
+
window.addEventListener('resize', this._onResize);
|
|
76
|
+
this.resize(); // Initial size setup
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
this.initParticles();
|
|
80
|
+
this.start();
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Internal method to merge user configuration with library defaults.
|
|
85
|
+
* @private
|
|
86
|
+
*/
|
|
87
|
+
_mergeDefaults(userConfig) {
|
|
88
|
+
const defaults = {
|
|
89
|
+
waves: [
|
|
90
|
+
{
|
|
91
|
+
amplitude: 60,
|
|
92
|
+
frequency: 0.003,
|
|
93
|
+
speed: 0.006,
|
|
94
|
+
yOffset: 0.55,
|
|
95
|
+
color: 'rgba(248, 225, 231, 0.25)',
|
|
96
|
+
lineWidth: 1.5,
|
|
97
|
+
mouseInfluence: 0.45
|
|
98
|
+
},
|
|
99
|
+
{
|
|
100
|
+
amplitude: 50,
|
|
101
|
+
frequency: 0.004,
|
|
102
|
+
speed: 0.008,
|
|
103
|
+
yOffset: 0.60,
|
|
104
|
+
color: 'rgba(212, 165, 165, 0.2)',
|
|
105
|
+
lineWidth: 1,
|
|
106
|
+
mouseInfluence: 0.5
|
|
107
|
+
},
|
|
108
|
+
{
|
|
109
|
+
amplitude: 70,
|
|
110
|
+
frequency: 0.0025,
|
|
111
|
+
speed: 0.005,
|
|
112
|
+
yOffset: 0.65,
|
|
113
|
+
color: 'rgba(255, 255, 255, 0.2)',
|
|
114
|
+
lineWidth: 1.5,
|
|
115
|
+
mouseInfluence: 0.4
|
|
116
|
+
},
|
|
117
|
+
{
|
|
118
|
+
amplitude: 40,
|
|
119
|
+
frequency: 0.0035,
|
|
120
|
+
speed: 0.010,
|
|
121
|
+
yOffset: 0.70,
|
|
122
|
+
color: 'rgba(201, 169, 110, 0.12)',
|
|
123
|
+
lineWidth: 1,
|
|
124
|
+
mouseInfluence: 0.55
|
|
125
|
+
}
|
|
126
|
+
],
|
|
127
|
+
particles: {countScale: 8000, maxCount: 180},
|
|
128
|
+
colors: {
|
|
129
|
+
backgroundGradient: ['#fdfcfb', '#f7ede2', '#e8d5d0'],
|
|
130
|
+
particleColorPrefixes: [
|
|
131
|
+
'rgba(248, 225, 231,',
|
|
132
|
+
'rgba(212, 165, 165,',
|
|
133
|
+
'rgba(201, 169, 110,',
|
|
134
|
+
'rgba(255, 255, 255,',
|
|
135
|
+
'rgba(232, 213, 208,'
|
|
136
|
+
],
|
|
137
|
+
mouseGlowStops: [
|
|
138
|
+
{offset: 0, color: 'rgba(255, 255, 255, 0.08)'},
|
|
139
|
+
{offset: 0.5, color: 'rgba(248, 225, 231, 0.04)'},
|
|
140
|
+
{offset: 1, color: 'rgba(248, 225, 231, 0)'}
|
|
141
|
+
]
|
|
142
|
+
}
|
|
143
|
+
};
|
|
144
|
+
|
|
145
|
+
const result = {};
|
|
146
|
+
|
|
147
|
+
for (const key in defaults) {
|
|
148
|
+
if (!defaults.hasOwnProperty(key)) {
|
|
149
|
+
continue;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
if (!(userConfig && userConfig[key])) {
|
|
153
|
+
result[key] = JSON.parse(JSON.stringify(defaults[key]));
|
|
154
|
+
} else if (typeof defaults[key] === 'object' && !Array.isArray(defaults[key]) && typeof userConfig[key] === 'object') {
|
|
155
|
+
result[key] = Object.assign({}, defaults[key], userConfig[key]);
|
|
156
|
+
} else if (key === 'waves') {
|
|
157
|
+
result.waves = Array.isArray(userConfig.waves) ? [...userConfig.waves] : JSON.parse(JSON.stringify(defaults.waves));
|
|
158
|
+
} else {
|
|
159
|
+
result[key] = userConfig[key];
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
return result;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* Automatically creates and injects a canvas element into the DOM if none was provided.
|
|
168
|
+
* @private
|
|
169
|
+
*/
|
|
170
|
+
_createAutoCanvas() {
|
|
171
|
+
if (typeof document === 'undefined') {
|
|
172
|
+
return;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
const el = document.createElement('canvas');
|
|
176
|
+
|
|
177
|
+
el.id = 'wave-particles-canvas';
|
|
178
|
+
|
|
179
|
+
Object.assign(el.style, {
|
|
180
|
+
position: 'fixed',
|
|
181
|
+
top: '0',
|
|
182
|
+
left: '0',
|
|
183
|
+
width: '100%',
|
|
184
|
+
height: '100%',
|
|
185
|
+
zIndex: '-1',
|
|
186
|
+
pointerEvents: 'none'
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
const body = document.body;
|
|
190
|
+
|
|
191
|
+
if (!body) {
|
|
192
|
+
return;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
body.insertBefore(el, body.firstChild);
|
|
196
|
+
this.canvas = el;
|
|
197
|
+
this.width = window.innerWidth;
|
|
198
|
+
this.height = window.innerHeight;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
/**
|
|
202
|
+
* Event handler for window resize events, including debouncing to prevent performance issues.
|
|
203
|
+
* @private
|
|
204
|
+
*/
|
|
205
|
+
_onResize() {
|
|
206
|
+
if (this._resizeTimeout) clearTimeout(this._resizeTimeout);
|
|
207
|
+
// Debounce resize to prevent flashing and high CPU usage
|
|
208
|
+
this._resizeTimeout = setTimeout(() => {
|
|
209
|
+
this.resize();
|
|
210
|
+
}, 250);
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
/**
|
|
214
|
+
* Recalculates canvas dimensions based on its display size and Device Pixel Ratio.
|
|
215
|
+
* Re-initializes particles to match the new surface area.
|
|
216
|
+
*/
|
|
217
|
+
resize() {
|
|
218
|
+
if (!this.canvas) {
|
|
219
|
+
return;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
const rect = this.canvas.getBoundingClientRect();
|
|
223
|
+
|
|
224
|
+
let w, h;
|
|
225
|
+
|
|
226
|
+
const attrW = parseInt(this.canvas.getAttribute('width'), 10);
|
|
227
|
+
const attrH = parseInt(this.canvas.getAttribute('height'), 10);
|
|
228
|
+
|
|
229
|
+
if (attrW && attrH) {
|
|
230
|
+
w = Math.round(rect.width || attrW);
|
|
231
|
+
h = Math.round(rect.height || attrH);
|
|
232
|
+
} else {
|
|
233
|
+
w = window.innerWidth;
|
|
234
|
+
h = window.innerHeight;
|
|
235
|
+
|
|
236
|
+
if (!this.canvas.parentNode && document.body) {
|
|
237
|
+
document.body.insertBefore(this.canvas, document.body.firstChild);
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
this.width = w;
|
|
242
|
+
this.height = h;
|
|
243
|
+
|
|
244
|
+
const dpr = window.devicePixelRatio || 1;
|
|
245
|
+
|
|
246
|
+
this.canvas.width = Math.round(w * dpr);
|
|
247
|
+
this.canvas.height = Math.round(h * dpr);
|
|
248
|
+
|
|
249
|
+
if (this.ctx && this.isRunning) {
|
|
250
|
+
this.initParticles();
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
/**
|
|
255
|
+
* Generates a new set of particles based on the current canvas area and configuration.
|
|
256
|
+
*/
|
|
257
|
+
initParticles() {
|
|
258
|
+
const {countScale = 8000, maxCount = 180} = this.config.particles;
|
|
259
|
+
|
|
260
|
+
const particleCount = Math.min(Math.floor((this.width * this.height) / (countScale > 0 ? countScale : 8000)), maxCount);
|
|
261
|
+
|
|
262
|
+
this.particles = Array.from({length: particleCount}, () => ({
|
|
263
|
+
x: Math.random() * this.width,
|
|
264
|
+
y: Math.random() * this.height,
|
|
265
|
+
size: Math.random() * 4 + 1.5,
|
|
266
|
+
speedX: (Math.random() - 0.5) * 0.4,
|
|
267
|
+
speedY: (Math.random() - 0.5) * 0.4,
|
|
268
|
+
opacity: Math.random() * 0.6 + 0.3,
|
|
269
|
+
pulse: Math.random() * Math.PI * 2,
|
|
270
|
+
pulseSpeed: Math.random() * 0.025 + 0.01,
|
|
271
|
+
colorPrefix: this.config.colors.particleColorPrefixes[Math.floor(Math.random() * this.config.colors.particleColorPrefixes.length)]
|
|
272
|
+
}));
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
_onMouseMove(e) {
|
|
276
|
+
const rect = this.canvas.getBoundingClientRect();
|
|
277
|
+
|
|
278
|
+
if (!rect) {
|
|
279
|
+
return;
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
// Calculate position relative to the canvas in CSS pixels
|
|
283
|
+
this.targetMouseX = e.clientX - rect.left;
|
|
284
|
+
this.targetMouseY = e.clientY - rect.top;
|
|
285
|
+
this.mouseActive = true;
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
_onMouseLeave() {
|
|
289
|
+
this.mouseActive = false;
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
_onTouchMove(e) {
|
|
293
|
+
const touch = e.touches[0];
|
|
294
|
+
const rect = this.canvas.getBoundingClientRect();
|
|
295
|
+
|
|
296
|
+
if (!touch || !rect) {
|
|
297
|
+
return;
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
this.targetMouseX = touch.clientX - rect.left;
|
|
301
|
+
this.targetMouseY = touch.clientY - rect.top;
|
|
302
|
+
this.mouseActive = true;
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
_onTouchEnd() {
|
|
306
|
+
this.mouseActive = false;
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
/**
|
|
310
|
+
* Renders the radial gradient background across the entire canvas.
|
|
311
|
+
* @param {CanvasRenderingContext2D} ctx
|
|
312
|
+
*/
|
|
313
|
+
drawBackground(ctx) {
|
|
314
|
+
const colors = this.config.colors.backgroundGradient;
|
|
315
|
+
|
|
316
|
+
const gradient = ctx.createRadialGradient(this.width / 2, this.height / 2, 0, this.width / 2, this.height / 2, Math.max(this.width, this.height) * 0.8);
|
|
317
|
+
|
|
318
|
+
colors.forEach((c, i) => gradient.addColorStop(i / (colors.length - 1 || 1), c));
|
|
319
|
+
|
|
320
|
+
ctx.fillStyle = gradient;
|
|
321
|
+
|
|
322
|
+
ctx.fillRect(0, 0, this.width, this.height);
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
/**
|
|
326
|
+
* Renders all wave layers, accounting for mouse interaction and layer offsets.
|
|
327
|
+
* @param {CanvasRenderingContext2D} ctx
|
|
328
|
+
*/
|
|
329
|
+
drawWaves(ctx) {
|
|
330
|
+
const mouseNormX = this.mouseActive ? this.mouseX / this.width : -1;
|
|
331
|
+
const mouseNormY = this.mouseActive ? this.mouseY / this.height : -1;
|
|
332
|
+
|
|
333
|
+
for (let wi = 0; wi < this.config.waves.length; wi++) {
|
|
334
|
+
const wave = this.config.waves[wi];
|
|
335
|
+
const numLayers = Math.max(2, wave.layers || 2);
|
|
336
|
+
|
|
337
|
+
for (let layer = 0; layer < numLayers; layer++) {
|
|
338
|
+
const layerOpacity = (1 - layer / numLayers) * 0.5 + 0.5;
|
|
339
|
+
|
|
340
|
+
ctx.beginPath();
|
|
341
|
+
|
|
342
|
+
const baseY = this.height * wave.yOffset + layer * 3;
|
|
343
|
+
|
|
344
|
+
const mouseEffect = this.mouseActive ? (mouseNormX - 0.5) * (wave.mouseInfluence || 0.45) * 150 : 0;
|
|
345
|
+
|
|
346
|
+
ctx.moveTo(0, this.height);
|
|
347
|
+
|
|
348
|
+
const step = Math.max(2, Math.floor(this.width / 480));
|
|
349
|
+
|
|
350
|
+
for (let x = 0; x <= this.width; x += step) {
|
|
351
|
+
let y = baseY;
|
|
352
|
+
|
|
353
|
+
y += Math.sin(x * wave.frequency + this.time * wave.speed + layer * 0.1) * wave.amplitude;
|
|
354
|
+
y += Math.sin(x * wave.frequency * 1.5 + this.time * wave.speed * 0.7 + wi) * wave.amplitude * 0.3;
|
|
355
|
+
|
|
356
|
+
if (this.mouseActive) {
|
|
357
|
+
const dx = x - this.mouseX;
|
|
358
|
+
const dist = Math.abs(dx) / this.width;
|
|
359
|
+
y += Math.sin(dist * Math.PI) * (mouseNormY > 0 ? (this.mouseY - this.height / 2) : 0) * 0.1 * (wave.mouseInfluence || 0.45);
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
y += mouseEffect * Math.sin((x / this.width) * Math.PI);
|
|
363
|
+
|
|
364
|
+
ctx.lineTo(x, y);
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
ctx.lineTo(this.width, this.height);
|
|
368
|
+
|
|
369
|
+
ctx.closePath();
|
|
370
|
+
|
|
371
|
+
const baseColor = wave.color || 'rgba(139, 114, 86, 0.25)';
|
|
372
|
+
|
|
373
|
+
ctx.fillStyle = baseColor.replace(/([\d.]+)\)$/, (m) => `${parseFloat(m) * layerOpacity})`);
|
|
374
|
+
|
|
375
|
+
if (wave.lineWidth != null) {
|
|
376
|
+
ctx.lineWidth = wave.lineWidth;
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
ctx.fill();
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
/**
|
|
385
|
+
* Updates and renders the particle system, including movement and mouse repulsion logic.
|
|
386
|
+
* @param {CanvasRenderingContext2D} ctx
|
|
387
|
+
*/
|
|
388
|
+
drawParticles(ctx) {
|
|
389
|
+
const maxDist = 150;
|
|
390
|
+
|
|
391
|
+
for (const p of this.particles) {
|
|
392
|
+
p.pulse += p.pulseSpeed;
|
|
393
|
+
|
|
394
|
+
const currentOpacity = p.opacity * (Math.sin(p.pulse) * 0.3 + 0.7);
|
|
395
|
+
|
|
396
|
+
if (this.mouseActive) {
|
|
397
|
+
const dx = p.x - this.mouseX, dy = p.y - this.mouseY, dist = Math.sqrt(dx * dx + dy * dy);
|
|
398
|
+
|
|
399
|
+
if (dist < maxDist && dist > 0.1) {
|
|
400
|
+
const force = (1 - dist / maxDist) * 2;
|
|
401
|
+
p.x += (dx / dist) * force;
|
|
402
|
+
p.y += (dy / dist) * force;
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
p.x += p.speedX;
|
|
407
|
+
p.y += p.speedY;
|
|
408
|
+
|
|
409
|
+
if (p.x < 0) {
|
|
410
|
+
p.x = this.width;
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
if (p.x > this.width) {
|
|
414
|
+
p.x = 0;
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
if (p.y < 0) {
|
|
418
|
+
p.y = this.height;
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
if (p.y > this.height) {
|
|
422
|
+
p.y = 0;
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
const grad = ctx.createRadialGradient(p.x, p.y, 0, p.x, p.y, p.size * 2);
|
|
426
|
+
|
|
427
|
+
grad.addColorStop(0, p.colorPrefix + currentOpacity + ')');
|
|
428
|
+
|
|
429
|
+
grad.addColorStop(1, p.colorPrefix + '0)');
|
|
430
|
+
|
|
431
|
+
ctx.beginPath();
|
|
432
|
+
|
|
433
|
+
ctx.fillStyle = grad;
|
|
434
|
+
|
|
435
|
+
ctx.arc(p.x, p.y, p.size * 2, 0, Math.PI * 2);
|
|
436
|
+
|
|
437
|
+
ctx.fill();
|
|
438
|
+
|
|
439
|
+
ctx.beginPath();
|
|
440
|
+
|
|
441
|
+
ctx.fillStyle = p.colorPrefix + (currentOpacity * 0.8) + ')';
|
|
442
|
+
|
|
443
|
+
ctx.arc(p.x, p.y, p.size * 0.5, 0, Math.PI * 2);
|
|
444
|
+
|
|
445
|
+
ctx.fill();
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
/**
|
|
450
|
+
* Renders a soft glow effect centered at the current mouse position.
|
|
451
|
+
* @param {CanvasRenderingContext2D} ctx
|
|
452
|
+
*/
|
|
453
|
+
drawMouseGlow(ctx) {
|
|
454
|
+
if (!this.mouseActive) {
|
|
455
|
+
return;
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
const gradient = ctx.createRadialGradient(this.mouseX, this.mouseY, 0, this.mouseX, this.mouseY, 200);
|
|
459
|
+
|
|
460
|
+
const stops = this.config.colors.mouseGlowStops;
|
|
461
|
+
|
|
462
|
+
stops.forEach((s) => gradient.addColorStop(s.offset, s.color));
|
|
463
|
+
|
|
464
|
+
ctx.beginPath();
|
|
465
|
+
|
|
466
|
+
ctx.fillStyle = gradient;
|
|
467
|
+
|
|
468
|
+
ctx.arc(this.mouseX, this.mouseY, 200, 0, Math.PI * 2);
|
|
469
|
+
|
|
470
|
+
ctx.fill();
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
/**
|
|
474
|
+
* The main animation loop. Updates state and draws the next frame.
|
|
475
|
+
*/
|
|
476
|
+
animate() {
|
|
477
|
+
if (!this.ctx) {
|
|
478
|
+
return;
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
this.time += 1;
|
|
482
|
+
|
|
483
|
+
this.mouseX += (this.targetMouseX - this.mouseX) * 0.08;
|
|
484
|
+
this.mouseY += (this.targetMouseY - this.mouseY) * 0.08;
|
|
485
|
+
|
|
486
|
+
this.ctx.clearRect(0, 0, this.width, this.height);
|
|
487
|
+
|
|
488
|
+
this.drawBackground(this.ctx);
|
|
489
|
+
this.drawWaves(this.ctx);
|
|
490
|
+
this.drawParticles(this.ctx);
|
|
491
|
+
this.drawMouseGlow(this.ctx);
|
|
492
|
+
|
|
493
|
+
if (this.isRunning) {
|
|
494
|
+
requestAnimationFrame(() => this.animate());
|
|
495
|
+
}
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
/**
|
|
499
|
+
* Starts or resumes the animation loop and attaches interaction listeners.
|
|
500
|
+
*/
|
|
501
|
+
start() {
|
|
502
|
+
if (this.isRunning) {
|
|
503
|
+
return;
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
if (typeof window !== 'undefined') {
|
|
507
|
+
window.addEventListener('mousemove', this._onMouseMove, {passive: true});
|
|
508
|
+
window.addEventListener('touchmove', this._onTouchMove, {passive: false});
|
|
509
|
+
window.addEventListener('touchend', this._onTouchEnd, {passive: true});
|
|
510
|
+
window.addEventListener('mouseleave', this._onMouseLeave);
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
this.isRunning = true;
|
|
514
|
+
|
|
515
|
+
this.animate();
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
/**
|
|
519
|
+
* Pauses the animation loop and detaches interaction listeners to save resources.
|
|
520
|
+
*/
|
|
521
|
+
stop() {
|
|
522
|
+
this.isRunning = false;
|
|
523
|
+
|
|
524
|
+
if (typeof window !== 'undefined') {
|
|
525
|
+
window.removeEventListener('mousemove', this._onMouseMove);
|
|
526
|
+
window.removeEventListener('touchmove', this._onTouchMove);
|
|
527
|
+
window.removeEventListener('touchend', this._onTouchEnd);
|
|
528
|
+
window.removeEventListener('mouseleave', this._onMouseLeave);
|
|
529
|
+
}
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
/**
|
|
533
|
+
* Stops the animation, removes event listeners, and cleans up any auto-injected DOM elements.
|
|
534
|
+
*/
|
|
535
|
+
destroy() {
|
|
536
|
+
this.stop();
|
|
537
|
+
|
|
538
|
+
if (typeof window !== 'undefined') {
|
|
539
|
+
window.removeEventListener('resize', this._onResize);
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
if (this.canvas && this.canvas.id === 'wave-particles-canvas' && this.canvas.parentNode) {
|
|
543
|
+
this.canvas.parentNode.removeChild(this.canvas);
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
this.canvas = null;
|
|
547
|
+
this.ctx = null;
|
|
548
|
+
}
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
export default WaveParticles;
|