partycles 1.0.0 → 1.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/README.md +27 -1
- package/dist/animations/index.d.ts +1 -0
- package/dist/animations/index.d.ts.map +1 -1
- package/dist/animations/mobileOptimizations.d.ts +6 -0
- package/dist/animations/mobileOptimizations.d.ts.map +1 -0
- package/dist/animations/useReward.d.ts.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.esm.js +58 -6
- package/dist/index.esm.js.map +1 -1
- package/dist/index.js +58 -4
- package/dist/index.js.map +1 -1
- package/dist/mobileOptimizations.d.ts +6 -0
- package/dist/mobileOptimizations.d.ts.map +1 -0
- package/dist/useReward.d.ts.map +1 -1
- package/package.json +13 -2
package/README.md
CHANGED
@@ -359,7 +359,7 @@ function Achievement({ unlocked, name }) {
|
|
359
359
|
1. **Unique IDs**: Ensure each animated element has a unique ID
|
360
360
|
2. **Performance**: Avoid triggering multiple animations simultaneously
|
361
361
|
3. **Accessibility**: Provide alternative feedback for users who prefer reduced motion
|
362
|
-
4. **Mobile**:
|
362
|
+
4. **Mobile**: Partycles automatically optimizes for mobile devices
|
363
363
|
|
364
364
|
```tsx
|
365
365
|
// Respect user preferences
|
@@ -370,6 +370,32 @@ const { reward } = useReward('buttonId', 'confetti', {
|
|
370
370
|
});
|
371
371
|
```
|
372
372
|
|
373
|
+
## 📱 Mobile Optimization
|
374
|
+
|
375
|
+
Partycles automatically detects mobile devices and optimizes performance:
|
376
|
+
|
377
|
+
- **Reduced particle counts** (60% of desktop)
|
378
|
+
- **Smaller particle sizes** (80% of desktop)
|
379
|
+
- **Shorter lifetimes** (80% of desktop)
|
380
|
+
- **Frame skipping** for smoother performance
|
381
|
+
- **Tab visibility detection** to pause when inactive
|
382
|
+
|
383
|
+
You can also manually check for mobile devices:
|
384
|
+
|
385
|
+
```tsx
|
386
|
+
import { isMobileDevice, optimizeConfigForMobile } from 'partycles';
|
387
|
+
|
388
|
+
if (isMobileDevice()) {
|
389
|
+
// Custom mobile logic
|
390
|
+
}
|
391
|
+
|
392
|
+
// Or manually optimize a config
|
393
|
+
const mobileConfig = optimizeConfigForMobile({
|
394
|
+
particleCount: 100,
|
395
|
+
elementSize: 30
|
396
|
+
});
|
397
|
+
```
|
398
|
+
|
373
399
|
## 🔧 Advanced Usage
|
374
400
|
|
375
401
|
### Custom Physics
|
@@ -1,4 +1,5 @@
|
|
1
1
|
export { useReward } from './useReward';
|
2
2
|
export type { AnimationType, AnimationConfig, UseRewardConfig } from './types';
|
3
3
|
export { emojiPresets } from './animations/emoji';
|
4
|
+
export { isMobileDevice, optimizeConfigForMobile } from './mobileOptimizations';
|
4
5
|
//# sourceMappingURL=index.d.ts.map
|
@@ -1 +1 @@
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AACxC,YAAY,EAAE,aAAa,EAAE,eAAe,EAAE,eAAe,EAAE,MAAM,SAAS,CAAC;AAC/E,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC"}
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AACxC,YAAY,EAAE,aAAa,EAAE,eAAe,EAAE,eAAe,EAAE,MAAM,SAAS,CAAC;AAC/E,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAClD,OAAO,EAAE,cAAc,EAAE,uBAAuB,EAAE,MAAM,uBAAuB,CAAC"}
|
@@ -0,0 +1,6 @@
|
|
1
|
+
import { AnimationConfig } from './types';
|
2
|
+
export declare const isMobileDevice: () => boolean;
|
3
|
+
export declare const optimizeConfigForMobile: (config: AnimationConfig) => AnimationConfig;
|
4
|
+
export declare const shouldSkipFrame: (frameCount: number) => boolean;
|
5
|
+
export declare const simplifyVisualEffects: (animationType: string) => boolean;
|
6
|
+
//# sourceMappingURL=mobileOptimizations.d.ts.map
|
@@ -0,0 +1 @@
|
|
1
|
+
{"version":3,"file":"mobileOptimizations.d.ts","sourceRoot":"","sources":["../src/mobileOptimizations.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,SAAS,CAAC;AAE1C,eAAO,MAAM,cAAc,QAAO,OAcjC,CAAC;AAEF,eAAO,MAAM,uBAAuB,GAAI,QAAQ,eAAe,KAAG,eAoBjE,CAAC;AAGF,eAAO,MAAM,eAAe,GAAI,YAAY,MAAM,KAAG,OAIpD,CAAC;AAGF,eAAO,MAAM,qBAAqB,GAAI,eAAe,MAAM,KAAG,OAK7D,CAAC"}
|
@@ -1 +1 @@
|
|
1
|
-
{"version":3,"file":"useReward.d.ts","sourceRoot":"","sources":["../src/useReward.tsx"],"names":[],"mappings":"AAGA,OAAO,EAAE,aAAa,EAAE,eAAe,EAAY,MAAM,SAAS,CAAC;
|
1
|
+
{"version":3,"file":"useReward.d.ts","sourceRoot":"","sources":["../src/useReward.tsx"],"names":[],"mappings":"AAGA,OAAO,EAAE,aAAa,EAAE,eAAe,EAAY,MAAM,SAAS,CAAC;AAKnE,UAAU,eAAe;IACvB,MAAM,EAAE,MAAM,IAAI,CAAC;IACnB,WAAW,EAAE,OAAO,CAAC;CACtB;AAED,eAAO,MAAM,SAAS,GACpB,WAAW,MAAM,EACjB,eAAe,aAAa,EAC5B,SAAS,eAAe,KACvB,eAuKF,CAAC"}
|
package/dist/index.d.ts
CHANGED
@@ -1,4 +1,5 @@
|
|
1
1
|
export { useReward } from './useReward';
|
2
2
|
export type { AnimationType, AnimationConfig, UseRewardConfig } from './types';
|
3
3
|
export { emojiPresets } from './animations/emoji';
|
4
|
+
export { isMobileDevice, optimizeConfigForMobile } from './mobileOptimizations';
|
4
5
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.d.ts.map
CHANGED
@@ -1 +1 @@
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AACxC,YAAY,EAAE,aAAa,EAAE,eAAe,EAAE,eAAe,EAAE,MAAM,SAAS,CAAC;AAC/E,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC"}
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AACxC,YAAY,EAAE,aAAa,EAAE,eAAe,EAAE,eAAe,EAAE,MAAM,SAAS,CAAC;AAC/E,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAClD,OAAO,EAAE,cAAc,EAAE,uBAAuB,EAAE,MAAM,uBAAuB,CAAC"}
|
package/dist/index.esm.js
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
import React, { useState, useRef,
|
1
|
+
import React, { useState, useRef, useEffect, useCallback } from 'react';
|
2
2
|
import { createRoot } from 'react-dom/client';
|
3
3
|
|
4
4
|
const randomInRange = (min, max) => {
|
@@ -1150,12 +1150,57 @@ const animations = {
|
|
1150
1150
|
},
|
1151
1151
|
};
|
1152
1152
|
|
1153
|
+
const isMobileDevice = () => {
|
1154
|
+
if (typeof window === 'undefined')
|
1155
|
+
return false;
|
1156
|
+
// Check user agent
|
1157
|
+
const userAgent = navigator.userAgent || navigator.vendor || window.opera;
|
1158
|
+
const isMobileUA = /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(userAgent);
|
1159
|
+
// Check viewport width
|
1160
|
+
const isMobileWidth = window.innerWidth <= 768;
|
1161
|
+
// Check touch support
|
1162
|
+
const hasTouch = 'ontouchstart' in window || navigator.maxTouchPoints > 0;
|
1163
|
+
return isMobileUA || (isMobileWidth && hasTouch);
|
1164
|
+
};
|
1165
|
+
const optimizeConfigForMobile = (config) => {
|
1166
|
+
var _a, _b, _c;
|
1167
|
+
if (!isMobileDevice())
|
1168
|
+
return config;
|
1169
|
+
return Object.assign(Object.assign({}, config), {
|
1170
|
+
// Reduce particle count by 40%
|
1171
|
+
particleCount: Math.floor((config.particleCount || 50) * 0.6),
|
1172
|
+
// Reduce element size by 20%
|
1173
|
+
elementSize: Math.floor((config.elementSize || 20) * 0.8),
|
1174
|
+
// Reduce lifetime by 20%
|
1175
|
+
lifetime: Math.floor((config.lifetime || 150) * 0.8),
|
1176
|
+
// Simplify physics
|
1177
|
+
physics: Object.assign(Object.assign({}, config.physics), {
|
1178
|
+
// Reduce precision for mobile
|
1179
|
+
gravity: Math.round((((_a = config.physics) === null || _a === void 0 ? void 0 : _a.gravity) || 0) * 100) / 100, wind: Math.round((((_b = config.physics) === null || _b === void 0 ? void 0 : _b.wind) || 0) * 100) / 100, friction: Math.round((((_c = config.physics) === null || _c === void 0 ? void 0 : _c.friction) || 0.98) * 100) / 100 }) });
|
1180
|
+
};
|
1181
|
+
// Frame skipping for mobile
|
1182
|
+
const shouldSkipFrame = (frameCount) => {
|
1183
|
+
if (!isMobileDevice())
|
1184
|
+
return false;
|
1185
|
+
// Skip every 3rd frame on mobile
|
1186
|
+
return frameCount % 3 === 0;
|
1187
|
+
};
|
1188
|
+
|
1153
1189
|
const useReward = (elementId, animationType, config) => {
|
1154
1190
|
const [isAnimating, setIsAnimating] = useState(false);
|
1155
1191
|
const animationFrameRef = useRef();
|
1156
1192
|
const particlesRef = useRef([]);
|
1157
1193
|
const containerRef = useRef(null);
|
1158
1194
|
const rootRef = useRef(null);
|
1195
|
+
const isTabVisible = useRef(true);
|
1196
|
+
// Monitor tab visibility
|
1197
|
+
useEffect(() => {
|
1198
|
+
const handleVisibilityChange = () => {
|
1199
|
+
isTabVisible.current = !document.hidden;
|
1200
|
+
};
|
1201
|
+
document.addEventListener('visibilitychange', handleVisibilityChange);
|
1202
|
+
return () => document.removeEventListener('visibilitychange', handleVisibilityChange);
|
1203
|
+
}, []);
|
1159
1204
|
const animate = useCallback(() => {
|
1160
1205
|
var _a, _b, _c, _d, _e, _f;
|
1161
1206
|
const element = document.getElementById(elementId);
|
@@ -1171,8 +1216,10 @@ const useReward = (elementId, animationType, config) => {
|
|
1171
1216
|
console.error(`Animation type "${animationType}" not found`);
|
1172
1217
|
return;
|
1173
1218
|
}
|
1219
|
+
// Apply mobile performance optimizations
|
1220
|
+
const optimizedConfig = config ? optimizeConfigForMobile(config) : undefined;
|
1174
1221
|
// Create particles
|
1175
|
-
particlesRef.current = animationHandler.createParticles(origin,
|
1222
|
+
particlesRef.current = animationHandler.createParticles(origin, optimizedConfig || {});
|
1176
1223
|
// Create container
|
1177
1224
|
const container = document.createElement('div');
|
1178
1225
|
container.style.position = 'fixed';
|
@@ -1193,8 +1240,13 @@ const useReward = (elementId, animationType, config) => {
|
|
1193
1240
|
const gravity = (_b = (_a = config === null || config === void 0 ? void 0 : config.physics) === null || _a === void 0 ? void 0 : _a.gravity) !== null && _b !== void 0 ? _b : defaultGravity;
|
1194
1241
|
const friction = (_d = (_c = config === null || config === void 0 ? void 0 : config.physics) === null || _c === void 0 ? void 0 : _c.friction) !== null && _d !== void 0 ? _d : 0.98;
|
1195
1242
|
const wind = (_f = (_e = config === null || config === void 0 ? void 0 : config.physics) === null || _e === void 0 ? void 0 : _e.wind) !== null && _f !== void 0 ? _f : 0;
|
1243
|
+
// Track frame count for mobile optimization
|
1244
|
+
let frameCount = 0;
|
1196
1245
|
const updateParticles = () => {
|
1197
1246
|
let activeParticles = 0;
|
1247
|
+
frameCount++;
|
1248
|
+
// Skip frame rendering on mobile to improve performance
|
1249
|
+
const skipFrame = shouldSkipFrame(frameCount);
|
1198
1250
|
particlesRef.current = particlesRef.current.map((particle) => {
|
1199
1251
|
if (particle.life <= 0)
|
1200
1252
|
return particle;
|
@@ -1222,13 +1274,13 @@ const useReward = (elementId, animationType, config) => {
|
|
1222
1274
|
}
|
1223
1275
|
return particle;
|
1224
1276
|
});
|
1225
|
-
// Render particles
|
1226
|
-
if (rootRef.current) {
|
1277
|
+
// Render particles (skip rendering on mobile for some frames)
|
1278
|
+
if (rootRef.current && !skipFrame) {
|
1227
1279
|
rootRef.current.render(React.createElement(React.Fragment, null, particlesRef.current
|
1228
1280
|
.filter((p) => p.life > 0)
|
1229
1281
|
.map((particle) => (React.createElement("div", { key: particle.id, style: createParticleStyle(particle, containerRect) }, animationHandler.renderParticle(particle))))));
|
1230
1282
|
}
|
1231
|
-
if (activeParticles > 0) {
|
1283
|
+
if (activeParticles > 0 && isTabVisible.current) {
|
1232
1284
|
animationFrameRef.current = requestAnimationFrame(updateParticles);
|
1233
1285
|
}
|
1234
1286
|
else {
|
@@ -1274,5 +1326,5 @@ const useReward = (elementId, animationType, config) => {
|
|
1274
1326
|
return { reward, isAnimating };
|
1275
1327
|
};
|
1276
1328
|
|
1277
|
-
export { emojiPresets, useReward };
|
1329
|
+
export { emojiPresets, isMobileDevice, optimizeConfigForMobile, useReward };
|
1278
1330
|
//# sourceMappingURL=index.esm.js.map
|