Animations et feedback
Objectif
Section titled “Objectif”Polir votre expérience avec animations fluides, sons réactifs et effets visuels. Ces détails transforment une interaction fonctionnelle en expérience mémorable.
Ce que vous allez apprendre :
- Transitions Svelte (fade, slide, fly)
- Animations CSS personnalisées
- Sons et audio réactif
- Particules et effets visuels
Pourquoi l’animation compte
Section titled “Pourquoi l’animation compte”L’animation n’est pas décorative. Elle guide l’œil, confirme l’action, crée du rythme et porte l’émotion.
Animation mal utilisée : Distrait, ralentit, frustre
Animation bien utilisée : Guide, rassure, enchante
1. Transitions Svelte
Section titled “1. Transitions Svelte”Svelte fournit des transitions prêtes à l’emploi pour animer l’apparition/disparition d’éléments.
Fade (fondu)
Section titled “Fade (fondu)”<script> import { fade } from 'svelte/transition';
let visible = $state(false);</script>
<button on:click={() => visible = !visible}> Toggle</button>
{#if visible} <p in:fade={{ duration: 300 }}> J'apparais en fondu </p>{/if}Slide (glissement)
Section titled “Slide (glissement)”<script> import { slide } from 'svelte/transition';
let expanded = $state(false);</script>
<button on:click={() => expanded = !expanded}> {expanded ? 'Réduire' : 'Développer'}</button>
{#if expanded} <div in:slide={{ duration: 300 }}> <p>Contenu qui glisse depuis le haut</p> </div>{/if}Fly (vol)
Section titled “Fly (vol)”<script> import { fly } from 'svelte/transition';
let notifications = $state([]);
function addNotification() { const id = Date.now(); notifications = [...notifications, { id, text: 'Nouvelle notification' }];
// Auto-retrait après 3s setTimeout(() => { notifications = notifications.filter(n => n.id !== id); }, 3000); }</script>
<button on:click={addNotification}> Ajouter notification</button>
<div class="notifications"> {#each notifications as notif (notif.id)} <div class="notif" in:fly={{ x: 300, duration: 300 }} out:fly={{ x: 300, duration: 200 }} > {notif.text} </div> {/each}</div>
<style> .notifications { position: fixed; top: 1rem; right: 1rem; display: flex; flex-direction: column; gap: 0.5rem; }
.notif { padding: 1rem; background: #333; color: white; border-radius: 4px; min-width: 200px; }</style>Combiner in et out
Section titled “Combiner in et out”<script> import { fade, fly } from 'svelte/transition';</script>
{#if visible} <div in:fly={{ y: 20, duration: 300 }} out:fade={{ duration: 200 }} > Entre en volant, sort en fondu </div>{/if}Transition avec easing
Section titled “Transition avec easing”<script> import { fly } from 'svelte/transition'; import { elasticOut } from 'svelte/easing';</script>
<div in:fly={{ y: -50, duration: 800, easing: elasticOut }}> Animation avec rebond</div>Easing disponibles :
linear: vitesse constantecubicOut: ralentit à la fin (par défaut)elasticOut: rebond élastiquebounceOut: rebonditbackOut: recul puis avance
2. Animations CSS
Section titled “2. Animations CSS”Pour des animations continues ou plus complexes, CSS offre un contrôle précis.
Animation au survol
Section titled “Animation au survol”<button class="hover-btn"> Survolez-moi</button>
<style> .hover-btn { padding: 1rem 2rem; background: #4CAF50; color: white; border: none; border-radius: 4px; cursor: pointer; transition: all 0.3s ease; }
.hover-btn:hover { background: #45a049; transform: scale(1.05); box-shadow: 0 4px 12px rgba(0,0,0,0.2); }
.hover-btn:active { transform: scale(0.98); }</style>Animation continue (keyframes)
Section titled “Animation continue (keyframes)”<div class="pulse"> Point d'intérêt</div>
<style> .pulse { padding: 1rem; background: #ff6b6b; color: white; border-radius: 50%; animation: pulse 2s ease-in-out infinite; }
@keyframes pulse { 0%, 100% { transform: scale(1); opacity: 1; } 50% { transform: scale(1.1); opacity: 0.8; } }</style>Animation au défilement (scroll-driven)
Section titled “Animation au défilement (scroll-driven)”<script> import { onMount } from 'svelte';
let scrollProgress = 0;
onMount(() => { function handleScroll() { const winScroll = document.documentElement.scrollTop; const height = document.documentElement.scrollHeight - document.documentElement.clientHeight; scrollProgress = (winScroll / height) * 100; }
window.addEventListener('scroll', handleScroll); return () => window.removeEventListener('scroll', handleScroll); });</script>
<div class="progress-bar" style="width: {scrollProgress}%"></div>
<style> .progress-bar { position: fixed; top: 0; left: 0; height: 4px; background: linear-gradient(90deg, #ff6b6b, #4CAF50); transition: width 0.1s ease; z-index: 1000; }</style>Parallaxe simple
Section titled “Parallaxe simple”<script> import { onMount } from 'svelte';
let offsetY = $state(0);
onMount(() => { function handleScroll() { offsetY = window.pageYOffset; }
window.addEventListener('scroll', handleScroll); return () => window.removeEventListener('scroll', handleScroll); });</script>
<div class="parallax-bg" style="transform: translateY({offsetY * 0.5}px);"> <!-- Contenu en arrière-plan --></div>3. Sons et audio réactif
Section titled “3. Sons et audio réactif”Le son renforce l’interaction. Un clic qui fait un bruit devient plus satisfaisant.
Jouer un son simple
Section titled “Jouer un son simple”<script> let audio;
function playSound() { if (audio) { audio.currentTime = 0; // Redémarre si déjà en cours audio.play(); } }</script>
<audio bind:this={audio} src="/sounds/click.mp3"></audio>
<button on:click={playSound}> Clic sonore</button>Sons selon le contexte
Section titled “Sons selon le contexte”<script> function playFeedback(type) { const sounds = { success: '/sounds/success.mp3', error: '/sounds/error.mp3', click: '/sounds/click.mp3' };
const audio = new Audio(sounds[type]); audio.play(); }</script>
<button on:click={() => playFeedback('success')}> Succès</button>
<button on:click={() => playFeedback('error')}> Erreur</button>Audio réactif (Web Audio API)
Section titled “Audio réactif (Web Audio API)”<script> import { onMount } from 'svelte';
let audioContext; let oscillator; let gainNode;
onMount(() => { audioContext = new (window.AudioContext || window.webkitAudioContext)(); gainNode = audioContext.createGain(); gainNode.connect(audioContext.destination); gainNode.gain.value = 0.3; // Volume });
function playTone(frequency) { if (oscillator) { oscillator.stop(); }
oscillator = audioContext.createOscillator(); oscillator.type = 'sine'; oscillator.frequency.value = frequency; oscillator.connect(gainNode); oscillator.start();
// Arrêt après 200ms setTimeout(() => oscillator.stop(), 200); }</script>
<div class="keyboard"> <button on:click={() => playTone(261.63)}>Do</button> <button on:click={() => playTone(293.66)}>Ré</button> <button on:click={() => playTone(329.63)}>Mi</button> <button on:click={() => playTone(349.23)}>Fa</button> <button on:click={() => playTone(392.00)}>Sol</button></div>Feedback sonore subtil
Section titled “Feedback sonore subtil”<script> function playSubtle() { const ctx = new AudioContext(); const osc = ctx.createOscillator(); const gain = ctx.createGain();
osc.connect(gain); gain.connect(ctx.destination);
osc.frequency.value = 800; osc.type = 'sine';
gain.gain.setValueAtTime(0.1, ctx.currentTime); gain.gain.exponentialRampToValueAtTime(0.01, ctx.currentTime + 0.1);
osc.start(ctx.currentTime); osc.stop(ctx.currentTime + 0.1); }</script>
<button on:click={playSubtle}> Feedback subtil</button>4. Particules et effets visuels
Section titled “4. Particules et effets visuels”Les particules ajoutent du dynamisme et de la magie à une expérience.
Canvas : étoiles suivant la souris
Section titled “Canvas : étoiles suivant la souris”<script> import { onMount } from 'svelte';
let canvas; let ctx; let particles = [];
class Particle { constructor(x, y) { this.x = x; this.y = y; this.size = Math.random() * 3 + 1; this.speedX = (Math.random() - 0.5) * 2; this.speedY = (Math.random() - 0.5) * 2; this.life = 1; }
update() { this.x += this.speedX; this.y += this.speedY; this.life -= 0.02; }
draw() { ctx.fillStyle = `rgba(255, 255, 255, ${this.life})`; ctx.beginPath(); ctx.arc(this.x, this.y, this.size, 0, Math.PI * 2); ctx.fill(); } }
onMount(() => { ctx = canvas.getContext('2d'); canvas.width = window.innerWidth; canvas.height = window.innerHeight;
function animate() { ctx.fillStyle = 'rgba(0, 0, 0, 0.1)'; ctx.fillRect(0, 0, canvas.width, canvas.height);
particles = particles.filter(p => p.life > 0);
particles.forEach(p => { p.update(); p.draw(); });
requestAnimationFrame(animate); }
animate(); });
function handleMouseMove(e) { for (let i = 0; i < 3; i++) { particles.push(new Particle(e.clientX, e.clientY)); } }</script>
<canvas bind:this={canvas} on:mousemove={handleMouseMove}></canvas>
<style> canvas { position: fixed; top: 0; left: 0; pointer-events: none; z-index: 1000; }</style>Confetti.js (bibliothèque)
Section titled “Confetti.js (bibliothèque)”<script> import { onMount } from 'svelte';
let canvas;
onMount(async () => { // Import dynamique const confetti = (await import('canvas-confetti')).default; const myConfetti = confetti.create(canvas, { resize: true });
return () => { myConfetti.reset(); }; });
function celebrate() { const myConfetti = canvas.__confetti; myConfetti({ particleCount: 100, spread: 70, origin: { y: 0.6 } }); }</script>
<button on:click={celebrate}> 🎉 Célébrer</button>
<canvas bind:this={canvas}></canvas>Effet de surlignage (highlight)
Section titled “Effet de surlignage (highlight)”<script> let highlighted = $state(false);
function highlight() { highlighted = true; setTimeout(() => highlighted = false, 1000); }</script>
<div class="card" class:highlight={highlighted} on:click={highlight} role="button" tabindex="0"> Cliquez pour surligner</div>
<style> .card { padding: 2rem; background: white; border: 2px solid transparent; transition: all 0.3s ease; }
.card.highlight { border-color: gold; box-shadow: 0 0 20px rgba(255, 215, 0, 0.5); animation: pulse-border 1s ease; }
@keyframes pulse-border { 0%, 100% { box-shadow: 0 0 20px rgba(255, 215, 0, 0.5); } 50% { box-shadow: 0 0 40px rgba(255, 215, 0, 0.8); } }</style>Bonnes pratiques
Section titled “Bonnes pratiques”1. Performance
Section titled “1. Performance”Privilégier transform et opacity (accélération GPU) :
/* ✅ Rapide */transform: translateX(100px);opacity: 0.5;
/* ❌ Lent */left: 100px;width: 200px;2. Accessibilité
Section titled “2. Accessibilité”Respecter prefers-reduced-motion :
<script> import { onMount } from 'svelte';
let scrollProgress = $state(0);
onMount(() => {```svelte<script> import { onMount } from 'svelte';
let prefersReducedMotion = $state(false);
onMount(() => { const mediaQuery = window.matchMedia('(prefers-reduced-motion: reduce)'); prefersReducedMotion = mediaQuery.matches; });</script>
{#if visible} <div in:fade={{ duration: prefersReducedMotion ? 0 : 300 }} > Contenu animé (ou pas) </div>{/if}Ou via CSS :
@media (prefers-reduced-motion: reduce) { * { animation-duration: 0.01ms !important; transition-duration: 0.01ms !important; }}3. Feedback immédiat
Section titled “3. Feedback immédiat”L’animation doit confirmer l’action immédiatement, pas après un délai.
<!-- ✅ Bon : feedback instantané --><button on:click={handleClick} class:clicked={isClicked}> Cliquer</button>
<!-- ❌ Mauvais : délai avant feedback --><button on:click={() => setTimeout(handleClick, 500)}> Cliquer</button>Exercice : Interface polie
Section titled “Exercice : Interface polie”Créez une interface qui combine :
- Transitions Svelte pour les apparitions/disparitions
- Animation CSS au survol
- Son de feedback sur action
- Un effet visuel (particules ou highlight)
Exemples de concept :
- Tableau de bord avec notifications animées
- Jeu simple avec feedback visuel et sonore
- Interface de vote avec célébration
- Générateur qui “crée” visuellement
Ce que vous avez appris
Section titled “Ce que vous avez appris”- ✅ Utiliser les transitions Svelte (fade, slide, fly)
- ✅ Créer des animations CSS (hover, keyframes, scroll)
- ✅ Intégrer du son (simple et Web Audio API)
- ✅ Ajouter des effets visuels (canvas, particules)
- ✅ Respecter les bonnes pratiques (performance, accessibilité)