react-weather-effects 0.0.0-stage → 0.1.0
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 +470 -2
- package/dist/index.cjs +1232 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +101 -0
- package/dist/index.d.mts +101 -0
- package/dist/index.d.ts +101 -0
- package/dist/index.mjs +1223 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +59 -4
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Allwin Antony
|
|
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
CHANGED
|
@@ -1,3 +1,471 @@
|
|
|
1
|
-
#
|
|
1
|
+
# react-weather-effects
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Composable, high-performance rain, snow, and fog visual effects for React, rendered on a single lightweight 2D canvas.
|
|
4
|
+
|
|
5
|
+
[](https://www.npmjs.com/package/react-weather-effects)
|
|
6
|
+
[](https://react-weather-effects-smoky.vercel.app/)
|
|
7
|
+
[](https://github.com/allwin-antony/react-weather-effects)
|
|
8
|
+
[](https://github.com/sponsors/allwin-antony)
|
|
9
|
+
[](https://opensource.org/licenses/MIT)
|
|
10
|
+
|
|
11
|
+
- **Live Demo:** [https://react-weather-effects-smoky.vercel.app/](https://react-weather-effects-smoky.vercel.app/)
|
|
12
|
+
- **GitHub:** [https://github.com/allwin-antony/react-weather-effects](https://github.com/allwin-antony/react-weather-effects)
|
|
13
|
+
- **npm:** [https://www.npmjs.com/package/react-weather-effects](https://www.npmjs.com/package/react-weather-effects)
|
|
14
|
+
- **Sponsor:** [https://github.com/sponsors/allwin-antony](https://github.com/sponsors/allwin-antony)
|
|
15
|
+
|
|
16
|
+
## Installation
|
|
17
|
+
|
|
18
|
+
Install `react-weather-effects` using your package manager of choice:
|
|
19
|
+
|
|
20
|
+
```sh
|
|
21
|
+
# npm
|
|
22
|
+
npm install react-weather-effects
|
|
23
|
+
|
|
24
|
+
# pnpm
|
|
25
|
+
pnpm add react-weather-effects
|
|
26
|
+
|
|
27
|
+
# Yarn
|
|
28
|
+
yarn add react-weather-effects
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
> **Beta Prerelease:** To install the current beta version:
|
|
32
|
+
> ```sh
|
|
33
|
+
> npm install react-weather-effects@beta
|
|
34
|
+
> # or pin specifically:
|
|
35
|
+
> npm install react-weather-effects@0.1.0-beta.0
|
|
36
|
+
> ```
|
|
37
|
+
|
|
38
|
+
### Requirements
|
|
39
|
+
|
|
40
|
+
- **React 18** or later (React 18 and 19 supported as peer dependencies).
|
|
41
|
+
- A modern browser with 2D `<canvas>` support. Contained sizing uses `ResizeObserver`.
|
|
42
|
+
- Full TypeScript declarations, ESM (`.mjs`), and CommonJS (`.cjs`) entry points included.
|
|
43
|
+
|
|
44
|
+
---
|
|
45
|
+
|
|
46
|
+
## Quick Start
|
|
47
|
+
|
|
48
|
+
Wrap any effect component inside `<WeatherCanvas>`:
|
|
49
|
+
|
|
50
|
+
```tsx
|
|
51
|
+
import { WeatherCanvas, Rain } from 'react-weather-effects';
|
|
52
|
+
|
|
53
|
+
export default function App() {
|
|
54
|
+
return (
|
|
55
|
+
<main style={{ minHeight: '100vh', background: '#0f172a', color: '#fff' }}>
|
|
56
|
+
<h1>My Weather App</h1>
|
|
57
|
+
|
|
58
|
+
<WeatherCanvas>
|
|
59
|
+
<Rain intensity={0.6} />
|
|
60
|
+
</WeatherCanvas>
|
|
61
|
+
</main>
|
|
62
|
+
);
|
|
63
|
+
}
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
By default, `<WeatherCanvas>` runs in `mode="fullscreen"`, covering the viewport with a transparent background. It ignores pointer events (`pointer-events: none`), so all underlying interactive elements remain clickable.
|
|
67
|
+
|
|
68
|
+
---
|
|
69
|
+
|
|
70
|
+
## Features
|
|
71
|
+
|
|
72
|
+
- **Single Canvas Engine:** One 2D `<canvas>` manages all effects, batching rendering at 60 FPS without DOM bloat.
|
|
73
|
+
- **Composable:** Combine multiple effects (`Rain`, `Snow`, `Fog`) inside a single canvas.
|
|
74
|
+
- **Configurable:** Adjust intensity, speed, wind angle, colors, and custom snowflake sprite artwork.
|
|
75
|
+
- **Smooth Scene Changes:** Opt into particle-preserving intensity and density fades with `transitionDuration`.
|
|
76
|
+
- **Motion-Aware & Accessible:** Automatically respects `prefers-reduced-motion` and pauses when hidden or offscreen.
|
|
77
|
+
- **Contained & Fullscreen:** Place inside specific cards/heroes (`mode="contained"`) or cover the whole screen.
|
|
78
|
+
- **SSR & Next.js Safe:** Safe for React Server Components (RSC) and Next.js App Router (runs cleanly with `'use client'`).
|
|
79
|
+
|
|
80
|
+
---
|
|
81
|
+
|
|
82
|
+
## Contents
|
|
83
|
+
|
|
84
|
+
- [Usage examples](#usage-examples)
|
|
85
|
+
- [API reference](#api-reference)
|
|
86
|
+
- [Quality and performance](#quality-and-performance)
|
|
87
|
+
- [Motion, accessibility, and lifecycle](#motion-accessibility-and-lifecycle)
|
|
88
|
+
- [Server rendering and Next.js](#server-rendering-and-nextjs)
|
|
89
|
+
- [Troubleshooting](#troubleshooting)
|
|
90
|
+
- [Local development](#local-development)
|
|
91
|
+
- [Sponsoring](#sponsoring)
|
|
92
|
+
- [License](#license)
|
|
93
|
+
|
|
94
|
+
## Usage examples
|
|
95
|
+
|
|
96
|
+
### Keep an effect inside a container
|
|
97
|
+
|
|
98
|
+
Use `mode="contained"` and give the parent a positioning context and a nonzero height. Add `overflow: 'hidden'` when the effect should be clipped to the container's edges.
|
|
99
|
+
|
|
100
|
+
```tsx
|
|
101
|
+
import { WeatherCanvas, Snow } from 'react-weather-effects';
|
|
102
|
+
|
|
103
|
+
export function SnowCard() {
|
|
104
|
+
return (
|
|
105
|
+
<section
|
|
106
|
+
style={{
|
|
107
|
+
position: 'relative',
|
|
108
|
+
height: 320,
|
|
109
|
+
overflow: 'hidden',
|
|
110
|
+
borderRadius: 12,
|
|
111
|
+
background: '#0f172a',
|
|
112
|
+
}}
|
|
113
|
+
>
|
|
114
|
+
<WeatherCanvas mode="contained" wind={{ x: -0.15, y: 0 }}>
|
|
115
|
+
<Snow intensity={0.5} speed={0.8} />
|
|
116
|
+
</WeatherCanvas>
|
|
117
|
+
|
|
118
|
+
<div style={{ position: 'relative', zIndex: 2, padding: 24, color: '#fff' }}>
|
|
119
|
+
<h2>Winter forecast</h2>
|
|
120
|
+
<p>Snow remains behind this content.</p>
|
|
121
|
+
</div>
|
|
122
|
+
</section>
|
|
123
|
+
);
|
|
124
|
+
}
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
Size and style the parent rather than passing `width`, `height`, or `style` to `WeatherCanvas`; those are not part of its public API.
|
|
128
|
+
|
|
129
|
+
### Combine effects
|
|
130
|
+
|
|
131
|
+
```tsx
|
|
132
|
+
import { WeatherCanvas, Rain, Fog } from 'react-weather-effects';
|
|
133
|
+
|
|
134
|
+
export function RainyScene() {
|
|
135
|
+
return (
|
|
136
|
+
<WeatherCanvas quality="medium" wind={{ x: 0.3, y: 0 }}>
|
|
137
|
+
<Fog density={0.25} layers={2} />
|
|
138
|
+
<Rain intensity={0.75} angle={12} speed={1.1} />
|
|
139
|
+
</WeatherCanvas>
|
|
140
|
+
);
|
|
141
|
+
}
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
Fog draws behind rain and snow. Rain and snow share a drawing priority; instances at that priority draw in registration order. Each effect has its own settings and can be mounted or removed independently.
|
|
145
|
+
|
|
146
|
+
### Transition between scenes
|
|
147
|
+
|
|
148
|
+
Set `transitionDuration` on each effect to fade intensity or density changes over that many milliseconds. Keep the canvas and effects mounted, and use `0` to hide an effect instead of conditionally removing it:
|
|
149
|
+
|
|
150
|
+
```tsx
|
|
151
|
+
import { WeatherCanvas, Rain, Snow, Fog } from 'react-weather-effects';
|
|
152
|
+
|
|
153
|
+
export function TransitioningScene({ scene }: { scene: 'rain' | 'snow' | 'clear' }) {
|
|
154
|
+
return (
|
|
155
|
+
<WeatherCanvas>
|
|
156
|
+
<Fog density={scene === 'rain' ? 0.3 : 0} transitionDuration={800} />
|
|
157
|
+
<Rain intensity={scene === 'rain' ? 0.8 : 0} transitionDuration={800} />
|
|
158
|
+
<Snow intensity={scene === 'snow' ? 0.6 : 0} transitionDuration={800} />
|
|
159
|
+
</WeatherCanvas>
|
|
160
|
+
);
|
|
161
|
+
}
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
- The default is `0`, preserving instant updates. Initial mounting and unmounting are also immediate.
|
|
165
|
+
- Rain and snow fade added or removed particles without resetting the pool. Retained particles stay visible and continue moving; fog keeps its existing wisps and texture.
|
|
166
|
+
- Changing the target mid-fade continues from the current visibility. Negative or non-finite durations are treated as `0`.
|
|
167
|
+
- Fades use the shared simulation clock, so hidden/offscreen time does not advance them. Static and paused presentations apply the latest settings immediately without animating.
|
|
168
|
+
- Only rain/snow `intensity` and fog `density` are transitioned. Other props, including rain's existing velocity smoothing, retain their current behavior.
|
|
169
|
+
|
|
170
|
+
### Use custom snow artwork
|
|
171
|
+
|
|
172
|
+
Add a static image to your application's public assets, for example `public/snowflakes/crystal.svg`, and pass its served URL:
|
|
173
|
+
|
|
174
|
+
```tsx
|
|
175
|
+
import { WeatherCanvas, Snow } from 'react-weather-effects';
|
|
176
|
+
|
|
177
|
+
export function CustomSnow() {
|
|
178
|
+
return (
|
|
179
|
+
<WeatherCanvas wind={{ x: 0.15, y: 0 }}>
|
|
180
|
+
<Snow
|
|
181
|
+
sprite="/snowflakes/crystal.svg"
|
|
182
|
+
intensity={0.5}
|
|
183
|
+
minSize={8}
|
|
184
|
+
maxSize={24}
|
|
185
|
+
speed={0.8}
|
|
186
|
+
wobble
|
|
187
|
+
/>
|
|
188
|
+
</WeatherCanvas>
|
|
189
|
+
);
|
|
190
|
+
}
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
Pass a **URL string**, not an SVG React component or raw SVG markup. PNG, SVG, and other browser-supported image formats can be used. Demo artwork is not bundled into the installed library.
|
|
194
|
+
|
|
195
|
+
#### Sprite sizing and caching
|
|
196
|
+
|
|
197
|
+
- `minSize` and `maxSize` are **radii in CSS pixels**, not image widths. A sprite with radius `24` has a longest side of `48` CSS pixels; its original aspect ratio is preserved.
|
|
198
|
+
- Sprites keep their original colors and transparency. The `color` prop applies only to built-in flakes and the loading/error fallback.
|
|
199
|
+
- Each mounted `Snow` instance loads its sprite once, unless the `sprite` URL changes. Built-in flakes appear while loading and remain if loading or rasterization fails.
|
|
200
|
+
- The package rasterizes artwork into a reusable texture, so SVG paths and filters are not rendered separately for every particle on every frame.
|
|
201
|
+
- Texture resolution accounts for flake size and device pixel ratio. It grows in size buckets when needed and is capped at `1024` pixels per side. Very large flakes can therefore look softer.
|
|
202
|
+
- The maximum RGBA pixel storage for one cached texture is approximately **4 MiB**, excluding the source image and browser overhead. This is not a limit on download size or decoded source-image memory.
|
|
203
|
+
- Animated images are used as **static snapshots**. Sprite animation is not supported; particle movement and gentle rotation still work.
|
|
204
|
+
|
|
205
|
+
For remote images, the image server must allow anonymous cross-origin requests with an appropriate `Access-Control-Allow-Origin` header. Serving artwork from your own application is the simplest option.
|
|
206
|
+
|
|
207
|
+
`wobble={false}` disables animated sideways sway and fall flutter. You can control sprite rotation separately with `rotation` (set `false` to let custom sprites drift upright without rotating, or `true` with `wobble={false}` to rotate without swaying) and adjust the spinning rate with `rotationSpeed` (multiplier, default `1`; set `0` to freeze rotation).
|
|
208
|
+
|
|
209
|
+
### Pause and resume a scene
|
|
210
|
+
|
|
211
|
+
```tsx
|
|
212
|
+
import { useState } from 'react';
|
|
213
|
+
import { WeatherCanvas, Snow } from 'react-weather-effects';
|
|
214
|
+
|
|
215
|
+
export function PausableSnow() {
|
|
216
|
+
const [paused, setPaused] = useState(false);
|
|
217
|
+
|
|
218
|
+
return (
|
|
219
|
+
<>
|
|
220
|
+
<button type="button" onClick={() => setPaused((value) => !value)}>
|
|
221
|
+
{paused ? 'Resume weather' : 'Pause weather'}
|
|
222
|
+
</button>
|
|
223
|
+
|
|
224
|
+
<WeatherCanvas paused={paused}>
|
|
225
|
+
<Snow intensity={0.5} />
|
|
226
|
+
</WeatherCanvas>
|
|
227
|
+
</>
|
|
228
|
+
);
|
|
229
|
+
}
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
Changing effect props updates the current scene without remounting its particle pool. Increasing intensity or quality initializes additional particles as needed; lowering them reduces the active count.
|
|
233
|
+
|
|
234
|
+
## API reference
|
|
235
|
+
|
|
236
|
+
All props are optional. `intensity` and `density` are fractions from `0` to `1`, not percentages from `0` to `100`; values outside that interval are clamped.
|
|
237
|
+
|
|
238
|
+
### `WeatherCanvas`
|
|
239
|
+
|
|
240
|
+
| Prop | Type | Default | Description |
|
|
241
|
+
| --- | --- | --- | --- |
|
|
242
|
+
| `children` | `ReactNode` | None | Effect components that share this canvas. |
|
|
243
|
+
| `mode` | `'fullscreen' \| 'contained'` | `'fullscreen'` | Cover the viewport or fill the positioned parent. |
|
|
244
|
+
| `wind` | `{ x: number; y: number }` | `{ x: 0, y: 0 }` | Relative wind input. Positive `x` points right; positive `y` points down. |
|
|
245
|
+
| `quality` | `'low' \| 'medium' \| 'high'` | `'medium'` | Particle budget and maximum device pixel ratio. |
|
|
246
|
+
| `dpr` | `'light' \| 'normal' \| 'heavy'` | `'normal'` | Additional device pixel ratio cap; see [quality and performance](#quality-and-performance). |
|
|
247
|
+
| `reducedMotion` | `'auto' \| 'animated' \| 'static' \| 'off'` | `'auto'` | How to handle motion and system preferences. |
|
|
248
|
+
| `paused` | `boolean` | `false` | Freeze simulation while keeping the scene visible. |
|
|
249
|
+
| `seed` | `string \| number` | Per-instance seed | Seed for reproducible random initialization. |
|
|
250
|
+
| `zIndex` | `number` | `1` | CSS stacking order of the overlay within its ancestor stacking context. |
|
|
251
|
+
| `onContextLost` | `() => void` | None | Called when the 2D context cannot be acquired or is lost. |
|
|
252
|
+
|
|
253
|
+
Wind is an effect-specific input, not a velocity in pixels per second. Rain and snow use both components; fog currently uses horizontal wind only.
|
|
254
|
+
|
|
255
|
+
### `Rain`
|
|
256
|
+
|
|
257
|
+
| Prop | Type | Default | Description |
|
|
258
|
+
| --- | --- | --- | --- |
|
|
259
|
+
| `intensity` | `number` | `0.7` | Fraction of this instance's particle budget to draw. `0` draws no rain. |
|
|
260
|
+
| `transitionDuration` | `number` | `0` | Intensity fade duration in milliseconds. Keep mounted and set intensity to `0` to fade out. |
|
|
261
|
+
| `angle` | `number` | `10` | Degrees from vertical. Positive values lean right; negative values lean left. |
|
|
262
|
+
| `speed` | `number` | `1` | Multiplier for intrinsic falling speed. |
|
|
263
|
+
| `dropLength` | `number` | `1` | Multiplier for raindrop streak length (< 1 for fine drizzle, > 1 for heavy downpour). Alias: `length`. |
|
|
264
|
+
| `dropThickness` | `number` | `1` | Multiplier for raindrop stroke thickness (< 1 for fine mist, > 1 for heavy rain). Alias: `thickness`. |
|
|
265
|
+
| `splashes` | `boolean` | `false` | Enable animated bottom-edge splashes for contained cards or heroes. Alias: `splash`. |
|
|
266
|
+
| `color` | `string` | `'rgba(190, 215, 245, 0.8)'` | CSS color for rain trails and splashes. |
|
|
267
|
+
|
|
268
|
+
With `splashes`, visible drops produce small, varied droplets that rise and fall over a softly expanding ground ripple. Splash size follows `dropThickness`; existing impacts finish their short animation when rain stops. Under heavy rain, excess impacts are skipped instead of restarting active splashes. Splashes stay at the current canvas bottom edge after resizing, and paused/static redraws preserve their state. Use contained mode for a defined ground edge; splashes do not detect other DOM surfaces.
|
|
269
|
+
|
|
270
|
+
### `Snow`
|
|
271
|
+
|
|
272
|
+
| Prop | Type | Default | Description |
|
|
273
|
+
| --- | --- | --- | --- |
|
|
274
|
+
| `intensity` | `number` | `0.5` | Fraction of this instance's particle budget to draw. `0` draws no snow. |
|
|
275
|
+
| `transitionDuration` | `number` | `0` | Intensity fade duration in milliseconds. Keep mounted and set intensity to `0` to fade out. |
|
|
276
|
+
| `speed` | `number` | `1` | Multiplier for intrinsic falling speed and wobble progression. |
|
|
277
|
+
| `color` | `string` | `'rgba(255, 255, 255, 0.85)'` | CSS color for built-in flakes and the sprite fallback. Does not tint artwork. |
|
|
278
|
+
| `sprite` | `string` | None | Static image URL. Omitting it uses soft, built-in snowflakes. |
|
|
279
|
+
| `minSize` | `number` | `1.5`, or `6` with a sprite | Minimum flake radius in CSS pixels. |
|
|
280
|
+
| `maxSize` | `number` | `4`, or `18` with a sprite | Maximum flake radius in CSS pixels. |
|
|
281
|
+
| `wobble` | `boolean` | `true` | Enable animated sideways sway and fall flutter. |
|
|
282
|
+
| `rotation` | `boolean` | `true` (or `wobble`) | Enable sprite rotation/spinning. Defaults to `wobble`. Set `false` to drift without rotating. |
|
|
283
|
+
| `rotationSpeed` | `number` | `1` | Multiplier for sprite rotation/spinning speed. Defaults to `1`. Set to `0` to freeze angle. |
|
|
284
|
+
|
|
285
|
+
Use nonnegative sizes, with `minSize <= maxSize`. Built-in flakes have a feathered glow that extends beyond their nominal radius.
|
|
286
|
+
|
|
287
|
+
### `Fog`
|
|
288
|
+
|
|
289
|
+
| Prop | Type | Default | Description |
|
|
290
|
+
| --- | --- | --- | --- |
|
|
291
|
+
| `density` | `number` | `0.4` | Opacity multiplier. `0` draws no fog. |
|
|
292
|
+
| `transitionDuration` | `number` | `0` | Density fade duration in milliseconds. Keep mounted and set density to `0` to fade out. |
|
|
293
|
+
| `speed` | `number` | `1` | Multiplier for intrinsic drift and shape movement. |
|
|
294
|
+
| `color` | `string` | `'rgba(220, 225, 235, 0.35)'` | CSS color used to tint the cached fog texture. |
|
|
295
|
+
| `layers` | `number` | `3` | Layer count, rounded and clamped between `1` and `6`. |
|
|
296
|
+
|
|
297
|
+
Use nonnegative speed multipliers for the usual falling and drifting behavior. Setting `speed={0}` does not necessarily stop wind-driven movement; use `WeatherCanvas paused` to freeze the entire scene.
|
|
298
|
+
|
|
299
|
+
### TypeScript exports
|
|
300
|
+
|
|
301
|
+
The public entry point exports these types in addition to the components:
|
|
302
|
+
|
|
303
|
+
- `WeatherCanvasProps`, `RainProps`, `SnowProps`, `FogProps`
|
|
304
|
+
- `Quality`, `DPRWeight`, `RenderBudget`
|
|
305
|
+
- `ReducedMotionProp`, `MotionMode`
|
|
306
|
+
|
|
307
|
+
`MotionMode` describes the resolved state (`'animated'`, `'static'`, or `'off'`). The `reducedMotion` prop also accepts `'auto'` through `ReducedMotionProp`.
|
|
308
|
+
|
|
309
|
+
## Quality and performance
|
|
310
|
+
|
|
311
|
+
Each `WeatherCanvas` owns its rendering budget:
|
|
312
|
+
|
|
313
|
+
| Quality | Maximum particles per rain or snow instance | DPR cap |
|
|
314
|
+
| --- | ---: | ---: |
|
|
315
|
+
| `'low'` | 150 | 1 |
|
|
316
|
+
| `'medium'` | 300 | 2 |
|
|
317
|
+
| `'high'` | 600 | 2 |
|
|
318
|
+
|
|
319
|
+
The particle cap is **per effect instance**, not a whole-scene limit. For example, medium quality with `Snow intensity={0.5}` draws 150 flakes. A second identical `Snow` instance can draw another 150. Fog uses a bounded layer count and cached textures instead of this particle count.
|
|
320
|
+
|
|
321
|
+
The `dpr` prop applies another resolution cap:
|
|
322
|
+
|
|
323
|
+
| DPR setting | Additional cap |
|
|
324
|
+
| --- | ---: |
|
|
325
|
+
| `'light'` | 2 |
|
|
326
|
+
| `'normal'` | 2 |
|
|
327
|
+
| `'heavy'` | 1.5 |
|
|
328
|
+
|
|
329
|
+
The effective ratio is the minimum of the device DPR, the quality cap, and the additional DPR cap. `light` and `normal` currently have the same cap. Size props remain in CSS pixels regardless of DPR.
|
|
330
|
+
|
|
331
|
+
For lower rendering cost:
|
|
332
|
+
|
|
333
|
+
1. Start with `quality="medium"`; use `'low'` on constrained devices or large canvases.
|
|
334
|
+
2. Reduce intensity, fog density, or fog layers before adding more effect instances.
|
|
335
|
+
3. Prefer contained scenes when the effect only needs to cover a small part of the page.
|
|
336
|
+
4. Keep sprite files reasonably sized. Caching reduces repeated rendering, but downloading, decoding, and the first rasterization still take work.
|
|
337
|
+
5. Measure the complete scene on target browsers and devices. Large translucent areas and overlapping effects can dominate rendering cost.
|
|
338
|
+
|
|
339
|
+
There is no fixed FPS guarantee or automatic quality downgrade. The engine reuses particle pools and textures, but hardware, canvas area, DPR, and composition still matter.
|
|
340
|
+
|
|
341
|
+
The repository's size check enforces a **5 kB minified-and-gzipped core-only budget**, excluding React. Rain and combined Rain/Snow/Fog sizes are reported separately; the core budget is not a size claim for all effects together.
|
|
342
|
+
|
|
343
|
+
## Motion, accessibility, and lifecycle
|
|
344
|
+
|
|
345
|
+
The default `reducedMotion="auto"` follows the user's `prefers-reduced-motion` setting:
|
|
346
|
+
|
|
347
|
+
| Setting | Behavior |
|
|
348
|
+
| --- | --- |
|
|
349
|
+
| `'auto'` | Animate when the system allows motion; otherwise draw a static scene. The unknown initial preference is treated as static. |
|
|
350
|
+
| `'animated'` | Animate regardless of the system preference. Use only when an explicit application choice is appropriate. |
|
|
351
|
+
| `'static'` | Draw a seeded scene without advancing simulation. |
|
|
352
|
+
| `'off'` | Clear the canvas and suppress effect drawing. |
|
|
353
|
+
|
|
354
|
+
`paused={true}` freezes the existing simulation rather than hiding the canvas. Static and paused scenes can still redraw after prop changes, resizing, or sprite loading, without advancing particle motion.
|
|
355
|
+
|
|
356
|
+
Each active canvas uses one shared animation loop. Scheduling stops when the page is hidden, the canvas is outside the viewport, its size is zero, it is paused, motion is disabled/static, or no effects are mounted. Resuming continues from the retained scene state rather than starting over.
|
|
357
|
+
|
|
358
|
+
The canvas is marked `aria-hidden="true"` and does not receive pointer events. Treat the effects as decorative: provide readable text or other accessible content when weather information itself matters.
|
|
359
|
+
|
|
360
|
+
### Seeds and scene resets
|
|
361
|
+
|
|
362
|
+
A fixed `seed` makes random initialization reproducible, but does not fix frame timing or guarantee identical screenshots across different rendering environments. Repeatable captures also need matching props, viewport, DPR, and simulation steps.
|
|
363
|
+
|
|
364
|
+
Changing `seed` resets the random generator but does not rebuild already initialized particles. To intentionally reset the whole scene, change the canvas's React `key` as well as its `seed`.
|
|
365
|
+
|
|
366
|
+
## Server rendering and Next.js
|
|
367
|
+
|
|
368
|
+
Importing and initially rendering the public components is SSR-safe. Canvas drawing and browser-dependent setup happen after the component mounts on the client.
|
|
369
|
+
|
|
370
|
+
In Next.js App Router or another React Server Components framework, put the weather scene behind a client-component boundary:
|
|
371
|
+
|
|
372
|
+
```tsx
|
|
373
|
+
'use client';
|
|
374
|
+
|
|
375
|
+
import { WeatherCanvas, Snow } from 'react-weather-effects';
|
|
376
|
+
|
|
377
|
+
export default function WeatherOverlay() {
|
|
378
|
+
return (
|
|
379
|
+
<WeatherCanvas>
|
|
380
|
+
<Snow intensity={0.4} />
|
|
381
|
+
</WeatherCanvas>
|
|
382
|
+
);
|
|
383
|
+
}
|
|
384
|
+
```
|
|
385
|
+
|
|
386
|
+
You can then render `WeatherOverlay` from your layout or page. No animation runs on the server, and a sprite may initially show the built-in fallback until its image loads.
|
|
387
|
+
|
|
388
|
+
## Troubleshooting
|
|
389
|
+
|
|
390
|
+
| Symptom | What to check |
|
|
391
|
+
| --- | --- |
|
|
392
|
+
| An effect throws “must be used inside WeatherCanvas.” | Render it as a descendant of `WeatherCanvas`, not as a standalone sibling. |
|
|
393
|
+
| Nothing appears in contained mode. | Give the parent a nonzero height and `position: relative`. Check that it is visible and that `ResizeObserver` is available. |
|
|
394
|
+
| The scene is visible but does not animate. | Check `paused`, `reducedMotion`, system motion preferences, tab visibility, and whether the canvas is in the viewport. |
|
|
395
|
+
| Custom artwork is replaced by soft snowflakes. | Check the image URL, network response, and CORS headers. A loading or failed sprite uses the built-in fallback. |
|
|
396
|
+
| `color` does not change a custom sprite. | Artwork intentionally retains its original colors. Edit the source image to recolor it. |
|
|
397
|
+
| The overlay appears behind another element. | Check `zIndex` and ancestor stacking contexts. A higher value cannot escape its parent's stacking context. |
|
|
398
|
+
| Rendering is slow. | Lower quality and intensity, reduce fog layers, limit the covered area, and profile all active effects together. |
|
|
399
|
+
|
|
400
|
+
If the browser cannot provide a 2D rendering context, the engine stops drawing and scheduling. Use `onContextLost` if your application needs to respond to that condition.
|
|
401
|
+
|
|
402
|
+
## Local development
|
|
403
|
+
|
|
404
|
+
This section is for contributors working from a source checkout. It is not required when using the installed npm package. Use a maintained Node.js LTS release and pnpm.
|
|
405
|
+
|
|
406
|
+
From the repository root, install dependencies, build the library, and start the demo:
|
|
407
|
+
|
|
408
|
+
```sh
|
|
409
|
+
pnpm install
|
|
410
|
+
pnpm build
|
|
411
|
+
pnpm demo
|
|
412
|
+
```
|
|
413
|
+
|
|
414
|
+
The demo runs at `http://localhost:5173` by default. Vite may select another port if that one is occupied; use the address printed in the terminal.
|
|
415
|
+
|
|
416
|
+
The repository is a pnpm workspace:
|
|
417
|
+
|
|
418
|
+
```text
|
|
419
|
+
packages/
|
|
420
|
+
core/ Internal canvas engine, scheduler, budgets, and pools
|
|
421
|
+
effects/ Internal Rain, Snow, and Fog implementations
|
|
422
|
+
react-weather-effects/ Public entry point and distributable bundle
|
|
423
|
+
apps/
|
|
424
|
+
demo/ Vite demonstration application
|
|
425
|
+
test/ Vitest rendering and lifecycle tests
|
|
426
|
+
```
|
|
427
|
+
|
|
428
|
+
The public package bundles the private workspaces. Consumers should not need to install or resolve those private package names.
|
|
429
|
+
|
|
430
|
+
Run these commands from the repository root:
|
|
431
|
+
|
|
432
|
+
| Command | Purpose |
|
|
433
|
+
| --- | --- |
|
|
434
|
+
| `pnpm install` | Install workspace dependencies. |
|
|
435
|
+
| `pnpm build` | Build core, effects, and the public package in dependency order. |
|
|
436
|
+
| `pnpm demo` | Start the local Vite demo. Build the packages first. |
|
|
437
|
+
| `pnpm test` | Run the test suite once. |
|
|
438
|
+
| `pnpm test:watch` | Run tests in watch mode. |
|
|
439
|
+
| `pnpm typecheck` | Check TypeScript project references; writes generated files into `dist`. |
|
|
440
|
+
| `pnpm size` | Measure bundle sizes after a package build. |
|
|
441
|
+
| `pnpm --filter weather-effects-demo build` | Build the demo for production. |
|
|
442
|
+
|
|
443
|
+
Run `pnpm typecheck` and `pnpm build` sequentially, not at the same time: both write to the package `dist` directories. Run `pnpm build` after typechecking and before packing a release so the distributable declarations are bundled correctly.
|
|
444
|
+
|
|
445
|
+
The demo consumes built workspace packages. After changing library source, rebuild the packages and reload the demo; restart its server if it retains cached output.
|
|
446
|
+
|
|
447
|
+
### Test a local package build
|
|
448
|
+
|
|
449
|
+
After running `pnpm build`, create a tarball of the public package:
|
|
450
|
+
|
|
451
|
+
```sh
|
|
452
|
+
pnpm --dir packages/react-weather-effects pack --pack-destination ../..
|
|
453
|
+
```
|
|
454
|
+
|
|
455
|
+
Install the generated tarball in a separate application using its actual path. For the current beta version:
|
|
456
|
+
|
|
457
|
+
```sh
|
|
458
|
+
npm install /path/to/react-weather-effects-0.1.0-beta.0.tgz
|
|
459
|
+
```
|
|
460
|
+
|
|
461
|
+
For implementation contracts, package boundaries, and future plans, see `ARCHITECTURE.md` in the source repository. Keep changes focused, cover rendering and lifecycle behavior with tests, and verify the build, types, and size budget before submitting changes.
|
|
462
|
+
|
|
463
|
+
## Sponsoring
|
|
464
|
+
|
|
465
|
+
If you find `react-weather-effects` useful, please consider sponsoring its ongoing development and maintenance:
|
|
466
|
+
|
|
467
|
+
💖 [**Sponsor on GitHub**](https://github.com/sponsors/allwin-antony)
|
|
468
|
+
|
|
469
|
+
## License
|
|
470
|
+
|
|
471
|
+
Licensed under the MIT license.
|