react-wanderer
v1.0.1
Published
A React component that creates an animated object that moves randomly within its parent container, bounces off walls, and flees from mouse interactions with realistic physics
Maintainers
Readme
React Wanderer
Un composant React avancé qui crée un wanderer animé qui se déplace à l'écran et réagit aux interactions de la souris. L'wanderer rebondit sur les murs, change de direction aléatoirement, et fuit le curseur de la souris quand il s'approche trop près.
✨ Fonctionnalités
- 🎯 Interactions souris : L'wanderer fuit le curseur quand il s'approche
- 🎲 Mouvement aléatoire : Déplacement continu avec changements de direction aléatoires
- 🏓 Rebonds sur les murs : L'wanderer rebondit sur les bords du conteneur
- 🌀 Animation de rotation : Rotation continue avec vitesse variable
- ⚡ Performance optimisée : Animation fluide à 60 FPS
- 🎛️ Personnalisation avancée : 8 catégories de paramètres configurables
- 📱 Support TypeScript : Définitions TypeScript complètes incluses
- 🔧 Rétrocompatibilité : API simple pour une utilisation basique
📦 Installation
npm install react-wandererou
yarn add react-wanderer🚀 Utilisation basique
import React, { useRef } from "react";
import { Wanderer } from "react-wanderer";
function App() {
const containerRef = useRef<HTMLDivElement>(null);
return (
<div
ref={containerRef}
style={{
width: "100vw",
height: "100vh",
position: "relative",
overflow: "hidden",
}}
>
<Wanderer
src="/path/to/your/wanderer.png"
alt="Animated Wanderer"
width={64}
height={64}
parentRef={containerRef}
/>
</div>
);
}⚙️ Configuration avancée
Le composant Wanderer accepte 8 catégories de paramètres optionnels pour personnaliser complètement son comportement :
Props obligatoires
| Prop | Type | Description |
| ----------- | -------------------------------------- | ----------------------------------------- |
| src | string | URL source de l'image de l'wanderer |
| alt | string | Texte alternatif pour l'accessibilité |
| width | number | Largeur de l'wanderer en pixels |
| height | number | Hauteur de l'wanderer en pixels |
| parentRef | React.RefObject<HTMLElement \| null> | Référence vers l'élément conteneur parent |
🔄 Paramètres de mouvement (movement)
movement={{
baseSpeed: 2, // Vitesse de base (px/frame)
speedVariation: 0, // Variation de vitesse (±px/frame)
speedChangeFrequency: 0, // Fréquence de changement de vitesse (0-1)
enableRandomSpeed: false, // Activer les changements aléatoires de vitesse
}}| Paramètre | Type | Défaut | Description |
| ---------------------- | --------- | ------- | --------------------------------------------- |
| baseSpeed | number | 2 | Vitesse de base en pixels par frame |
| speedVariation | number | 0 | Variation maximale de vitesse (±px/frame) |
| speedChangeFrequency | number | 0 | Probabilité de changement de vitesse (0-1) |
| enableRandomSpeed | boolean | false | Activer les changements aléatoires de vitesse |
🖱️ Interactions souris (mouseInteraction)
mouseInteraction={{
enabled: true, // Activer les interactions souris
detectionDistance: 60, // Distance de détection (px)
safetyZone: 30, // Zone de sécurité (px)
escapeSpeedMultiplier: 2, // Multiplicateur de vitesse de fuite
escapeAngleVariation: Math.PI/3, // Variation d'angle de fuite (radians)
throttleDelay: 100, // Délai de throttling (ms)
}}| Paramètre | Type | Défaut | Description |
| ----------------------- | --------- | ----------- | ------------------------------------------------ |
| enabled | boolean | true | Activer les interactions avec la souris |
| detectionDistance | number | 60 | Distance à laquelle l'wanderer détecte la souris |
| safetyZone | number | 30 | Distance minimale à maintenir avec la souris |
| escapeSpeedMultiplier | number | 2 | Multiplicateur de vitesse lors de la fuite |
| escapeAngleVariation | number | Math.PI/3 | Variation d'angle pour la direction de fuite |
| throttleDelay | number | 100 | Délai entre les calculs d'interaction (ms) |
🎬 Animation (animation)
animation={{
enableRotation: true, // Activer la rotation
rotationDurations: [6,4,2,1,0.5], // Durées de rotation possibles (secondes)
rotationChangeFrequency: 0.005, // Fréquence de changement de rotation
enableSpinVariation: true, // Activer les variations de rotation
}}| Paramètre | Type | Défaut | Description |
| ------------------------- | ---------- | --------------- | --------------------------------------------- |
| enableRotation | boolean | true | Activer l'animation de rotation |
| rotationDurations | number[] | [6,4,2,1,0.5] | Durées de rotation possibles (secondes) |
| rotationChangeFrequency | number | 0.005 | Probabilité de changement de rotation |
| enableSpinVariation | boolean | true | Activer les variations de vitesse de rotation |
🏓 Rebonds (bounce)
bounce={{
enabled: true, // Activer les rebonds
bounceAngleVariation: 0, // Variation d'angle de rebond (radians)
enableRandomBounce: false, // Activer les rebonds aléatoires
}}| Paramètre | Type | Défaut | Description |
| ---------------------- | --------- | ------- | ---------------------------------------------- |
| enabled | boolean | true | Activer les rebonds sur les bords |
| bounceAngleVariation | number | 0 | Variation d'angle lors des rebonds |
| enableRandomBounce | boolean | false | Générer des directions aléatoires après rebond |
🎨 Visuel (visual)
visual={{
className: "", // Classes CSS personnalisées
style: {}, // Styles CSS inline
enableHoverEffects: false, // Activer les effets de survol
hoverScale: 1.1, // Échelle lors du survol
transitionDuration: 0.3, // Durée des transitions (secondes)
}}| Paramètre | Type | Défaut | Description |
| -------------------- | --------------------- | ------- | ------------------------------------ |
| className | string | "" | Classes CSS à appliquer à l'wanderer |
| style | React.CSSProperties | {} | Styles CSS inline |
| enableHoverEffects | boolean | false | Activer les effets visuels au survol |
| hoverScale | number | 1.1 | Facteur d'échelle lors du survol |
| transitionDuration | number | 0.3 | Durée des transitions CSS (secondes) |
🧠 Comportement (behavior)
behavior={{
startPosition: "random", // Position de départ
boundaryBehavior: "bounce", // Comportement aux bords
enableGravity: false, // Activer la gravité
gravityStrength: 0.1, // Force de la gravité
enableFriction: false, // Activer la friction
frictionCoefficient: 0.98, // Coefficient de friction
}}| Paramètre | Type | Défaut | Description |
| --------------------- | ------------------------------------------------ | ---------- | ------------------------------------ |
| startPosition | "random" \| "center" \| {x: number, y: number} | "random" | Position initiale de l'wanderer |
| boundaryBehavior | "bounce" \| "wrap" \| "stop" \| "reverse" | "bounce" | Comportement aux bords du conteneur |
| enableGravity | boolean | false | Activer l'effet de gravité |
| gravityStrength | number | 0.1 | Force de la gravité (px/frame²) |
| enableFriction | boolean | false | Activer la friction (ralentissement) |
| frictionCoefficient | number | 0.98 | Coefficient de friction (0-1) |
⚡ Avancé (advanced)
advanced={{
animationFrameRate: 60, // Taux de rafraîchissement (FPS)
enableDebug: false, // Activer le mode debug
enablePerformanceMode: false, // Activer le mode performance
collisionDetection: "mouse", // Type de détection de collision
customCollisionElements: [], // Éléments de collision personnalisés
}}| Paramètre | Type | Défaut | Description |
| ------------------------- | --------------------------------- | --------- | -------------------------------------------- |
| animationFrameRate | number | 60 | Taux de rafraîchissement de l'animation |
| enableDebug | boolean | false | Afficher les logs de debug dans la console |
| enablePerformanceMode | boolean | false | Optimiser pour les performances |
| collisionDetection | "mouse" \| "elements" \| "both" | "mouse" | Type de détection de collision |
| customCollisionElements | HTMLElement[] | [] | Éléments HTML pour la détection de collision |
📞 Callbacks (callbacks)
callbacks={{
onCollision: (type) => {}, // Appelé lors d'une collision
onSpeedChange: (speed) => {}, // Appelé lors d'un changement de vitesse
onPositionChange: (x, y) => {}, // Appelé lors d'un changement de position
onAnimationComplete: () => {}, // Appelé à la fin d'une animation
}}| Callback | Signature | Description |
| --------------------- | ------------------------------------------------ | --------------------------------------- |
| onCollision | (type: "wall" \| "mouse" \| "element") => void | Appelé lors d'une collision |
| onSpeedChange | (newSpeed: number) => void | Appelé lors d'un changement de vitesse |
| onPositionChange | (x: number, y: number) => void | Appelé lors d'un changement de position |
| onAnimationComplete | () => void | Appelé à la fin d'une animation |
🎯 Exemples d'utilisation
Wanderer rapide et imprévisible
<Wanderer
src="/wanderer.png"
alt="Wanderer Rapide"
width={50}
height={50}
parentRef={containerRef}
movement={{
baseSpeed: 5,
speedVariation: 6,
speedChangeFrequency: 0.05,
enableRandomSpeed: true,
}}
/>Wanderer timide qui fuit la souris
<Wanderer
src="/wanderer.png"
alt="Wanderer Timide"
width={50}
height={50}
parentRef={containerRef}
mouseInteraction={{
detectionDistance: 100,
safetyZone: 50,
escapeSpeedMultiplier: 4,
escapeAngleVariation: Math.PI / 2,
}}
/>Wanderer avec gravité et friction
<Wanderer
src="/wanderer.png"
alt="Wanderer Physique"
width={50}
height={50}
parentRef={containerRef}
behavior={{
enableGravity: true,
gravityStrength: 0.2,
enableFriction: true,
frictionCoefficient: 0.95,
}}
/>Wanderer qui traverse les bords
<Wanderer
src="/wanderer.png"
alt="Wanderer Traverseur"
width={50}
height={50}
parentRef={containerRef}
behavior={{
boundaryBehavior: "wrap",
}}
/>Wanderer avec effets visuels
<Wanderer
src="/wanderer.png"
alt="Wanderer Visuel"
width={50}
height={50}
parentRef={containerRef}
animation={{
enableRotation: false,
}}
visual={{
enableHoverEffects: true,
hoverScale: 1.5,
transitionDuration: 0.5,
className: "drop-shadow-lg",
}}
/>Wanderer avec callbacks
<Wanderer
src="/wanderer.png"
alt="Wanderer Interactif"
width={50}
height={50}
parentRef={containerRef}
callbacks={{
onCollision: (type) => {
console.log(`Collision détectée: ${type}`);
},
onSpeedChange: (speed) => {
console.log(`Nouvelle vitesse: ${speed}`);
},
onPositionChange: (x, y) => {
console.log(`Nouvelle position: (${x}, ${y})`);
},
}}
/>🔧 Comportements aux bords
Le paramètre boundaryBehavior contrôle comment l'wanderer réagit aux bords du conteneur :
"bounce": L'wanderer rebondit sur les bords (comportement par défaut)"wrap": L'wanderer apparaît de l'autre côté du conteneur"stop": L'wanderer s'arrête aux bords"reverse": L'wanderer inverse sa direction aux bords
🎮 Positions de départ
Le paramètre startPosition contrôle la position initiale de l'wanderer :
"random": Position aléatoire dans le conteneur (par défaut)"center": Centre du conteneur{x: number, y: number}: Position spécifique en pixels
🧪 Tests
Ce projet inclut une suite de tests complète utilisant Vitest et Testing Library pour garantir la qualité et la fiabilité du code.
🚀 Scripts de tests disponibles
# Lancer tous les tests
npm test
# Lancer les tests en mode watch
npm run test:watch
# Lancer l'interface graphique des tests
npm run test:ui
# Lancer les tests avec couverture
npm run test:coverage
# Lancer les tests par catégorie
npm run test:utils # Tests des utilitaires
npm run test:hooks # Tests des hooks React
npm run test:components # Tests des composants📊 Couverture des tests
Le projet inclut des tests pour :
🛠️ Utilitaires (src/utils/)
- Physics : Calculs de vélocité, forces physiques
- Boundary : Gestion des collisions avec les bords
- Mouse Interaction : Détection et réaction aux interactions souris
- Animation : Styles d'animation et durées de rotation
- Movement : Logique de mouvement et changements de vitesse
🪝 Hooks React (src/hooks/)
- useWandererState : Gestion de l'état global du wanderer
- useWandererEvents : Gestion des événements souris et hover
- useWandererAnimation : Boucle d'animation et mise à jour des positions
- useWandererInitialization : Initialisation de la position et vélocité
🧩 Composants (src/components/)
- Wanderer : Rendu du composant principal, props, styles
🎯 Exemples de tests
Test d'utilitaire (Physics)
describe("getRandomVelocity", () => {
it("should return velocity with correct speed", () => {
const velocity = getRandomVelocity(5, 1, Math.PI / 4, true);
const speed = Math.sqrt(velocity.dx ** 2 + velocity.dy ** 2);
expect(speed).toBeCloseTo(5, 1);
});
});Test de hook (useWandererState)
describe("useWandererState", () => {
it("should initialize with correct default values", () => {
const { result } = renderHook(() => useWandererState(5));
expect(result.current.speed).toBe(5);
expect(result.current.isHovered).toBe(false);
});
});Test de composant (Wanderer)
describe("Wanderer component", () => {
it("renders an image with correct src and alt", () => {
render(<Wanderer {...baseProps} />);
const img = screen.getByAltText("Avatar");
expect(img).toBeInTheDocument();
expect(img.src).toContain("/avatar.png");
});
});🔧 Configuration des tests
Les tests utilisent :
- Vitest : Runner de tests rapide et moderne
- Testing Library : Utilitaires pour tester les composants React
- jsdom : Environnement DOM pour les tests
- Mocks : Isolation des dépendances externes
📈 Métriques de qualité
- Couverture de code : Tests unitaires pour tous les modules
- Tests d'intégration : Vérification du comportement global
- Tests de régression : Prévention des régressions
- Tests de performance : Validation des optimisations
🛡️ Comportement en cas de props manquantes ou invalides
src :
Si la propsrcest vide (""),nullouundefined, le composant ne rend rien (aucune balise<img>n'est générée).
Cela évite tout comportement inattendu ou warning dans le navigateur.alt :
La propaltest désormais optionnelle. Si elle n'est pas fournie, la valeur par défaut"Animated wanderer"est utilisée pour garantir l'accessibilité.
🧪 Tests
- Les tests vérifient que le composant ne rend rien si
srcest vide ou absent. - Les tests s'assurent que l'attribut
alta bien une valeur par défaut si non fourni. - Le comportement attendu en cas de props manquantes a évolué :
- Avant : le composant rendait une image même si
srcétait vide (ce qui pouvait générer un warning). - Maintenant : le composant ne rend rien si
srcest vide ou absent.
- Avant : le composant rendait une image même si
♿ Accessibilité
- Le composant garantit toujours un texte alternatif (
alt) pour l'accessibilité, même si la prop n'est pas renseignée.
📋 Prérequis
- React 16.8.0 ou supérieur (pour le support des hooks)
- Un élément conteneur avec
position: relativeouabsolute
🌐 Support navigateur
- Navigateurs modernes avec support ES6+
- CSS transforms et animations
- Événements souris
📄 Licence
MIT
🤝 Contribution
Les contributions sont les bienvenues ! N'hésitez pas à soumettre une Pull Request.
