lazy-vfx 1.1.1 → 1.1.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/README.md CHANGED
@@ -1,10 +1,9 @@
1
1
  # ☄️ Lazy VFX
2
2
 
3
- _Effortless high-end visual effects for the modern web, built for React & Three.js_
3
+ *Effortless high-end visual effects for the modern web, built for React & Three.js*
4
4
 
5
5
  Lazy VFX is a minimal, shader-driven VFX engine designed for modern web apps using React and Three.js. It abstracts away all the math and plumbing for emitters, GPU-accelerated particles, and real-time shaders, so you can stay focused on building cinematic, interactive experiences.
6
6
 
7
-
8
7
  [Live demo](https://lazy-vfx.vercel.app/demo) - [Fireworks demo](#) - [Wizard Game demo](#)
9
8
 
10
9
  ---
@@ -43,7 +42,6 @@ yarn add lazy-vfx
43
42
  - **VFXParticles:** Defines the particle system and its rendering properties
44
43
  - **VFXEmitter:** Controls how and when particles are emitted into the scene
45
44
 
46
-
47
45
  Add cinematic particles in seconds to any [React Three Fiber](https://docs.pmnd.rs/react-three-fiber) scene:
48
46
 
49
47
  ```jsx
@@ -74,11 +72,11 @@ function Experience() {
74
72
  name="sparks" // A unique identifier for this particle system
75
73
  settings={{
76
74
  nParticals: 10000, // Maximum number of particles to allocate
77
- intensity: 1, // Brightness multiplier
75
+ intensity: 2, // Brightness multiplier
78
76
  renderMode: "billboard", // "billboard" or "mesh" or "stretchBillboard"
79
77
  fadeAlpha: [0.5, 0.5], // Opacity fade in/out settings
80
78
  fadeSize: [0, 0], // Size fade in/out settings
81
- gravity: [0, -10, 0], // Apply gravity (x, y, z)
79
+ gravity: [0, -20, 0], // Apply gravity (x, y, z)
82
80
  }}
83
81
  alphaMap={text}
84
82
  // geometry={<sphereGeometry />}
@@ -87,12 +85,12 @@ function Experience() {
87
85
  {/* Step 2: Define your emitter */}
88
86
  <VFXEmitter
89
87
  debug // Show debug visualization
90
- emitter="particles" // Target the particle system by name
88
+ emitter="sparks" // Target the particle system by name
91
89
  settings={{
92
90
  duration: 4, // Emission cycle duration in seconds
93
91
  delay: 0, // Time delay before starting emission
94
92
  nbParticles: 10000, // Number of particles to emit per cycle
95
- spawnMode: "burst", // Emission mode: 'time' or 'burst'
93
+ spawnMode: "time", // Emission mode: 'time' or 'burst'
96
94
  loop: true, // Continuously emit particles (only if `spawnMode` is 'time')
97
95
 
98
96
  // Position range (min/max)
@@ -107,20 +105,20 @@ function Experience() {
107
105
  rotationSpeedMax: [0, 0, 0],
108
106
 
109
107
  // Particle lifetime range [min, max]
110
- particlesLifetime: [0.1, 1],
108
+ particlesLifetime: [0.1, 5],
111
109
 
112
110
  // Particle speed range [min, max]
113
- speed: [1, 5],
111
+ speed: [1, 10],
114
112
 
115
113
  // Direction range (min/max)
116
114
  directionMin: [-0.5, 0, -0.5],
117
115
  directionMax: [0.5, 1, 0.5],
118
116
 
119
117
  // Color at start - an array of strings for random selection
120
- colorStart: ["#ff0000", "#ffffff"],
118
+ colorStart: ["#ffe500", "#ffe500"],
121
119
 
122
120
  // Color at end - an array of strings for random selection
123
- colorEnd: ["#ffffff", "#ffffff"],
121
+ colorEnd: ["#ffe500", "#ffffff"],
124
122
 
125
123
  // Particle size range [min, max]
126
124
  size: [0.1, 0.5],
@@ -130,6 +128,7 @@ function Experience() {
130
128
  );
131
129
  }
132
130
  ```
131
+
133
132
  ---
134
133
 
135
134
  ## Custom Geometry Example
@@ -169,6 +168,7 @@ const CustomParticles = () => {
169
168
  ```
170
169
 
171
170
  **Note:**
171
+
172
172
  - Ensure the GLTF file at `/models/sword.glb` has a node named `Sword`.
173
173
  - If your model's node hierarchy is different, adjust `nodes.Sword.geometry` accordingly.
174
174
 
@@ -176,61 +176,68 @@ const CustomParticles = () => {
176
176
 
177
177
  ### VFXParticles Component
178
178
 
179
- | Property | Type | Description |
180
- |-------------|-------------------|-----------------------------------------------------|
181
- | name | string | Unique identifier for this particle system |
182
- | settings | object | Configuration options for particles |
183
- | alphaMap | THREE.Texture | Optional texture for particle alpha/transparency |
184
- | geometry | ReactElement | Optional custom geometry for particles |
179
+
180
+ | Property | Type | Description |
181
+ | -------- | ------------- | ------------------------------------------------ |
182
+ | name | string | Unique identifier for this particle system |
183
+ | settings | object | Configuration options for particles |
184
+ | alphaMap | THREE.Texture | Optional texture for particle alpha/transparency |
185
+ | geometry | ReactElement | Optional custom geometry for particles |
186
+
185
187
 
186
188
  #### VFXParticles Settings
187
189
 
188
- | Setting | Type | Default | Description |
189
- |-----------------|------------------------------|-----------------|--------------------------------------------------------------------------|
190
- | nbParticles | number | 1000 | Maximum number of particles |
191
- | intensity | number | 1 | Brightness multiplier |
192
- | renderMode | 'billboard' \| 'mesh' \| 'stretchBillboard' | 'mesh' | How particles are rendered |
193
- | fadeSize | [number, number] | [0.1, 0.9] | Size fade in/out range (0-1 of lifetime) |
194
- | fadeAlpha | [number, number] | [0, 1.0] | Opacity fade in/out range |
195
- | gravity | [number, number, number] | [0, 0, 0] | Gravity force applied to particles |
196
- | blendingMode | THREE.Blending | AdditiveBlending| How particles blend with the scene |
190
+
191
+ | Setting | Type | Default | Description |
192
+ | ------------ | ------------------------ | ---------------- | ---------------------------------------- |
193
+ | nbParticles | number | 1000 | Maximum number of particles |
194
+ | intensity | number | 1 | Brightness multiplier |
195
+ | renderMode | 'billboard' | 'mesh' | 'stretchBillboard' |
196
+ | fadeSize | [number, number] | [0.1, 0.9] | Size fade in/out range (0-1 of lifetime) |
197
+ | fadeAlpha | [number, number] | [0, 1.0] | Opacity fade in/out range |
198
+ | gravity | [number, number, number] | [0, 0, 0] | Gravity force applied to particles |
199
+ | blendingMode | THREE.Blending | AdditiveBlending | How particles blend with the scene |
200
+
197
201
 
198
202
  ---
199
203
 
200
204
  ### VFXEmitter Component
201
205
 
202
- | Property | Type | Description |
203
- |---------------|-----------|-----------------------------------------------------|
204
- | emitter | string | Name of the target particle system |
205
- | settings | object | Configuration options for emission behavior |
206
- | debug | boolean | Show Leva controls to adjust settings |
206
+
207
+ | Property | Type | Description |
208
+ | -------- | ------- | ------------------------------------------- |
209
+ | emitter | string | Name of the target particle system |
210
+ | settings | object | Configuration options for emission behavior |
211
+ | debug | boolean | Show Leva controls to adjust settings |
212
+
207
213
 
208
214
  #### VFXEmitter Settings
209
215
 
210
- | Setting | Type | Default | Description |
211
- |---------------------|-----------------------------|---------------------------|-----------------------------------------------------|
212
- | loop | boolean | true | Continuously emit particles |
213
- | duration | number | 1 | Emission cycle duration in seconds |
214
- | nbParticles | number | 100 | Number of particles to emit per cycle |
215
- | spawnMode | 'time' \| 'burst' | 'time' | How particles are spawned |
216
- | delay | number | 0 | Time delay before starting emission |
217
- | particlesLifetime | [number, number] | [0.1, 1] | Particle lifetime range [min, max] |
218
- | startPositionMin | [number, number, number] | [-0.1, -0.1, -0.1] | Minimum start position |
219
- | startPositionMax | [number, number, number] | [0.1, 0.1, 0.1] | Maximum start position |
220
- | startRotationMin | [number, number, number] | [0, 0, 0] | Minimum start rotation |
221
- | startRotationMax | [number, number, number] | [0, 0, 0] | Maximum start rotation |
222
- | rotationSpeedMin | [number, number, number] | [0, 0, 0] | Minimum rotation speed |
223
- | rotationSpeedMax | [number, number, number] | [0, 0, 0] | Maximum rotation speed |
224
- | directionMin | [number, number, number] | [-1, 0, -1] | Minimum emission direction |
225
- | directionMax | [number, number, number] | [1, 1, 1] | Maximum emission direction |
226
- | size | [number, number] | [0.01, 0.25] | Particle size range [min, max] |
227
- | speed | [number, number] | [1, 12] | Particle speed range [min, max] |
228
- | colorStart | string[] | ['white'] | Colors at start (randomly selected) |
229
- | colorEnd | string[] | ['white'] | Colors at end (randomly selected) |
230
216
 
231
- ---
217
+ | Setting | Type | Default | Description |
218
+ | ----------------- | ------------------------ | ------------------ | ------------------------------------- |
219
+ | loop | boolean | true | Continuously emit particles |
220
+ | duration | number | 1 | Emission cycle duration in seconds |
221
+ | nbParticles | number | 100 | Number of particles to emit per cycle |
222
+ | spawnMode | 'time' | 'burst' | 'time' |
223
+ | delay | number | 0 | Time delay before starting emission |
224
+ | particlesLifetime | [number, number] | [0.1, 1] | Particle lifetime range [min, max] |
225
+ | startPositionMin | [number, number, number] | [-0.1, -0.1, -0.1] | Minimum start position |
226
+ | startPositionMax | [number, number, number] | [0.1, 0.1, 0.1] | Maximum start position |
227
+ | startRotationMin | [number, number, number] | [0, 0, 0] | Minimum start rotation |
228
+ | startRotationMax | [number, number, number] | [0, 0, 0] | Maximum start rotation |
229
+ | rotationSpeedMin | [number, number, number] | [0, 0, 0] | Minimum rotation speed |
230
+ | rotationSpeedMax | [number, number, number] | [0, 0, 0] | Maximum rotation speed |
231
+ | directionMin | [number, number, number] | [-1, 0, -1] | Minimum emission direction |
232
+ | directionMax | [number, number, number] | [1, 1, 1] | Maximum emission direction |
233
+ | size | [number, number] | [0.01, 0.25] | Particle size range [min, max] |
234
+ | speed | [number, number] | [1, 12] | Particle speed range [min, max] |
235
+ | colorStart | string[] | ['white'] | Colors at start (randomly selected) |
236
+ | colorEnd | string[] | ['white'] | Colors at end (randomly selected) |
237
+
232
238
 
239
+ ---
233
240
 
234
241
  ## 📄 License
235
242
 
236
- MIT © [Dev-Sameerkhan](https://github.com/Dev-Sameer-Khan)
243
+ MIT © [Dev-Sameerkhan](https://github.com/Dev-Sameer-Khan)
@@ -673,7 +673,7 @@ var Ue = `varying vec2 vUv;
673
673
  const ve = new te(), pe = new se(), ge = new Re(), Ee = new te(1, 1, 1), be = new Me(), k = new Te(), Ge = ({ name: o, settings: v = {}, alphaMap: l, geometry: a }) => {
674
674
  const {
675
675
  nbParticles: r = 1e3,
676
- intensity: i = 1,
676
+ intensity: i = 2,
677
677
  renderMode: p = "mesh",
678
678
  fadeSize: c = [0.1, 0.9],
679
679
  fadeAlpha: T = [0, 1],
@@ -149,4 +149,4 @@ React keys must be passed directly to JSX without using spread:
149
149
  #else
150
150
  gl_FragColor = vec4(color, alpha);
151
151
  #endif
152
- }`;const fe=new m.Vector3,de=new m.Euler,me=new m.Quaternion,ve=new m.Vector3(1,1,1),pe=new m.Matrix4,k=new m.Color,we=({name:o,settings:g={},alphaMap:u,geometry:a})=>{const{nbParticles:r=1e3,intensity:i=1,renderMode:b="mesh",fadeSize:l=[.1,.9],fadeAlpha:F=[0,1],gravity:_=[0,0,0]}=g,n=c.useRef(),w=c.useMemo(()=>new m.PlaneGeometry(.5,.5),[]),N=()=>{if(!M.current||!n.current)return;[n.current.instanceMatrix,n.current.geometry.getAttribute("instanceColor"),n.current.geometry.getAttribute("instanceColorEnd"),n.current.geometry.getAttribute("instanceDirection"),n.current.geometry.getAttribute("instanceLifetime"),n.current.geometry.getAttribute("instanceSpeed"),n.current.geometry.getAttribute("instanceRotationSpeed")].forEach(d=>{d.clearUpdateRanges(),R.current>s.current?(d.addUpdateRange(0,s.current*d.itemSize),d.addUpdateRange(R.current*d.itemSize,r*d.itemSize-R.current*d.itemSize)):d.addUpdateRange(R.current*d.itemSize,s.current*d.itemSize-R.current*d.itemSize),d.needsUpdate=!0}),R.current=s.current,M.current=!1},s=c.useRef(0),R=c.useRef(0),M=c.useRef(!1),h=(x,d)=>{const B=n.current.geometry.getAttribute("instanceColor"),W=n.current.geometry.getAttribute("instanceColorEnd"),A=n.current.geometry.getAttribute("instanceDirection"),C=n.current.geometry.getAttribute("instanceLifetime"),j=n.current.geometry.getAttribute("instanceSpeed"),D=n.current.geometry.getAttribute("instanceRotationSpeed");for(let V=0;V<x;V++){s.current>=r&&(s.current=0);const{scale:Y,rotation:X,rotationSpeed:G,position:Z,direction:z,lifetime:K,colorStart:U,colorEnd:e,speed:t}=d();fe.set(...Z),de.set(...X),me.setFromEuler(de),ve.set(...Y),pe.compose(fe,me,ve),n.current.setMatrixAt(s.current,pe),k.set(U),B.set([k.r,k.g,k.b],s.current*3),k.set(e),W.set([k.r,k.g,k.b],s.current*3),A.set(z,s.current*3),C.set(K,s.current*2),j.set([t],s.current),D.set(G,s.current*3),s.current++,s.current=s.current%r}n.current.instanceMatrix.needsUpdate=!0,B.needsUpdate=!0,W.needsUpdate=!0,A.needsUpdate=!0,C.needsUpdate=!0,j.needsUpdate=!0,D.needsUpdate=!0,M.current=!0},[P]=c.useState({instanceColor:new Float32Array(r*3),instanceColorEnd:new Float32Array(r*3),instanceDirection:new Float32Array(r*3),instanceLifetime:new Float32Array(r*2),instanceSpeed:new Float32Array(r*1),instanceRotationSpeed:new Float32Array(r*3)}),L=re(x=>x.registerEmitter),I=re(x=>x.unregisterEmitter);c.useEffect(()=>(L(o,h),()=>{I(o)}),[]);const T=c.useMemo(()=>({uTime:{value:0},uIntensity:{value:i},uFadeSize:{value:l},uFadeAlpha:{value:F},uGravity:{value:_},uAlphaMap:{value:u}}),[]);return ie.useFrame(({clock:x})=>{T&&(T.uTime.value=x.elapsedTime)}),S.jsx(S.Fragment,{children:S.jsxs("instancedMesh",{args:[w,null,r],ref:n,onBeforeRender:N,children:[a,S.jsx("shaderMaterial",{uniforms:T,vertexShader:Ae,fragmentShader:_e,blending:m.AdditiveBlending,depthTest:!1,depthWrite:!1,transparent:!0,defines:{BILLBOARD_MODE:b==="billboard",MESH_MODE:b==="mesh",ALPHAMAP:!!u}}),S.jsx("instancedBufferAttribute",{attach:"geometry-attributes-instanceColor",args:[P.instanceColor],itemSize:3,count:r,usage:m.DynamicDrawUsage}),S.jsx("instancedBufferAttribute",{attach:"geometry-attributes-instanceColorEnd",args:[P.instanceColorEnd],itemSize:3,count:r,usage:m.DynamicDrawUsage}),S.jsx("instancedBufferAttribute",{attach:"geometry-attributes-instanceDirection",args:[P.instanceDirection],itemSize:3,count:r,usage:m.DynamicDrawUsage}),S.jsx("instancedBufferAttribute",{attach:"geometry-attributes-instanceLifetime",args:[P.instanceLifetime],itemSize:2,count:r,usage:m.DynamicDrawUsage}),S.jsx("instancedBufferAttribute",{attach:"geometry-attributes-instanceSpeed",args:[P.instanceSpeed],itemSize:1,count:r,usage:m.DynamicDrawUsage}),S.jsx("instancedBufferAttribute",{attach:"geometry-attributes-instanceRotationSpeed",args:[P.instanceRotationSpeed],itemSize:3,count:r,usage:m.DynamicDrawUsage})]})})};y.VFXEmitter=Pe,y.VFXParticles=we,Object.defineProperty(y,Symbol.toStringTag,{value:"Module"})}));
152
+ }`;const fe=new m.Vector3,de=new m.Euler,me=new m.Quaternion,ve=new m.Vector3(1,1,1),pe=new m.Matrix4,k=new m.Color,we=({name:o,settings:g={},alphaMap:u,geometry:a})=>{const{nbParticles:r=1e3,intensity:i=2,renderMode:b="mesh",fadeSize:l=[.1,.9],fadeAlpha:F=[0,1],gravity:_=[0,0,0]}=g,n=c.useRef(),w=c.useMemo(()=>new m.PlaneGeometry(.5,.5),[]),N=()=>{if(!M.current||!n.current)return;[n.current.instanceMatrix,n.current.geometry.getAttribute("instanceColor"),n.current.geometry.getAttribute("instanceColorEnd"),n.current.geometry.getAttribute("instanceDirection"),n.current.geometry.getAttribute("instanceLifetime"),n.current.geometry.getAttribute("instanceSpeed"),n.current.geometry.getAttribute("instanceRotationSpeed")].forEach(d=>{d.clearUpdateRanges(),R.current>s.current?(d.addUpdateRange(0,s.current*d.itemSize),d.addUpdateRange(R.current*d.itemSize,r*d.itemSize-R.current*d.itemSize)):d.addUpdateRange(R.current*d.itemSize,s.current*d.itemSize-R.current*d.itemSize),d.needsUpdate=!0}),R.current=s.current,M.current=!1},s=c.useRef(0),R=c.useRef(0),M=c.useRef(!1),h=(x,d)=>{const B=n.current.geometry.getAttribute("instanceColor"),W=n.current.geometry.getAttribute("instanceColorEnd"),A=n.current.geometry.getAttribute("instanceDirection"),C=n.current.geometry.getAttribute("instanceLifetime"),j=n.current.geometry.getAttribute("instanceSpeed"),D=n.current.geometry.getAttribute("instanceRotationSpeed");for(let V=0;V<x;V++){s.current>=r&&(s.current=0);const{scale:Y,rotation:X,rotationSpeed:G,position:Z,direction:z,lifetime:K,colorStart:U,colorEnd:e,speed:t}=d();fe.set(...Z),de.set(...X),me.setFromEuler(de),ve.set(...Y),pe.compose(fe,me,ve),n.current.setMatrixAt(s.current,pe),k.set(U),B.set([k.r,k.g,k.b],s.current*3),k.set(e),W.set([k.r,k.g,k.b],s.current*3),A.set(z,s.current*3),C.set(K,s.current*2),j.set([t],s.current),D.set(G,s.current*3),s.current++,s.current=s.current%r}n.current.instanceMatrix.needsUpdate=!0,B.needsUpdate=!0,W.needsUpdate=!0,A.needsUpdate=!0,C.needsUpdate=!0,j.needsUpdate=!0,D.needsUpdate=!0,M.current=!0},[P]=c.useState({instanceColor:new Float32Array(r*3),instanceColorEnd:new Float32Array(r*3),instanceDirection:new Float32Array(r*3),instanceLifetime:new Float32Array(r*2),instanceSpeed:new Float32Array(r*1),instanceRotationSpeed:new Float32Array(r*3)}),L=re(x=>x.registerEmitter),I=re(x=>x.unregisterEmitter);c.useEffect(()=>(L(o,h),()=>{I(o)}),[]);const T=c.useMemo(()=>({uTime:{value:0},uIntensity:{value:i},uFadeSize:{value:l},uFadeAlpha:{value:F},uGravity:{value:_},uAlphaMap:{value:u}}),[]);return ie.useFrame(({clock:x})=>{T&&(T.uTime.value=x.elapsedTime)}),S.jsx(S.Fragment,{children:S.jsxs("instancedMesh",{args:[w,null,r],ref:n,onBeforeRender:N,children:[a,S.jsx("shaderMaterial",{uniforms:T,vertexShader:Ae,fragmentShader:_e,blending:m.AdditiveBlending,depthTest:!1,depthWrite:!1,transparent:!0,defines:{BILLBOARD_MODE:b==="billboard",MESH_MODE:b==="mesh",ALPHAMAP:!!u}}),S.jsx("instancedBufferAttribute",{attach:"geometry-attributes-instanceColor",args:[P.instanceColor],itemSize:3,count:r,usage:m.DynamicDrawUsage}),S.jsx("instancedBufferAttribute",{attach:"geometry-attributes-instanceColorEnd",args:[P.instanceColorEnd],itemSize:3,count:r,usage:m.DynamicDrawUsage}),S.jsx("instancedBufferAttribute",{attach:"geometry-attributes-instanceDirection",args:[P.instanceDirection],itemSize:3,count:r,usage:m.DynamicDrawUsage}),S.jsx("instancedBufferAttribute",{attach:"geometry-attributes-instanceLifetime",args:[P.instanceLifetime],itemSize:2,count:r,usage:m.DynamicDrawUsage}),S.jsx("instancedBufferAttribute",{attach:"geometry-attributes-instanceSpeed",args:[P.instanceSpeed],itemSize:1,count:r,usage:m.DynamicDrawUsage}),S.jsx("instancedBufferAttribute",{attach:"geometry-attributes-instanceRotationSpeed",args:[P.instanceRotationSpeed],itemSize:3,count:r,usage:m.DynamicDrawUsage})]})})};y.VFXEmitter=Pe,y.VFXParticles=we,Object.defineProperty(y,Symbol.toStringTag,{value:"Module"})}));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lazy-vfx",
3
- "version": "1.1.1",
3
+ "version": "1.1.2",
4
4
  "description": "Effortless high-end visual effects for React and Three.js",
5
5
  "keywords": [
6
6
  "react",