Interactions avancées
Objectif
Section titled “Objectif”Maîtriser les interactions au-delà du simple clic. Le web permet des gestes riches : glisser-déposer, secouer, appuis longs, raccourcis clavier. Ces interactions créent des expériences plus immersives et personnelles.
Ce que vous allez apprendre :
- Drag and drop
- Détection de gestes (shake, swipe, long-press)
- Événements clavier et combos
- Inputs utilisateur variés
1. Drag and Drop
Section titled “1. Drag and Drop”Glisser-déposer est essentiel pour manipuler des objets virtuels : trier des cartes, organiser un espace, dessiner un parcours.
Exemple : Carte déplaçable
Section titled “Exemple : Carte déplaçable”<script> let position = $state({ x: 100, y: 100 }); let isDragging = $state(false); let offset = $state({ x: 0, y: 0 });
function handleMouseDown(event) { isDragging = true; // Calculer l'offset entre la position de la souris et le coin de l'élément offset = { x: event.clientX - position.x, y: event.clientY - position.y }; }
function handleMouseMove(event) { if (!isDragging) return;
position = { x: event.clientX - offset.x, y: event.clientY - offset.y }; }
function handleMouseUp() { isDragging = false; }</script>
<svelte:window on:mousemove={handleMouseMove} on:mouseup={handleMouseUp}/>
<div class="card" class:dragging={isDragging} style="left: {position.x}px; top: {position.y}px;" on:mousedown={handleMouseDown} role="button" tabindex="0"> Glissez-moi</div>
<style> .card { position: absolute; padding: 2rem; background: white; border-radius: 8px; box-shadow: 0 2px 8px rgba(0,0,0,0.1); cursor: grab; user-select: none; }
.card.dragging { cursor: grabbing; box-shadow: 0 8px 24px rgba(0,0,0,0.2); z-index: 100; }</style>Points clés :
<svelte:window>: écoute les événements sur toute la page- Calculer l’offset pour que l’élément ne “saute” pas
user-select: none: empêche la sélection de texte pendant le drag- Curseur
grab/grabbingpour feedback visuel
Support tactile
Section titled “Support tactile”<script> function handleTouchStart(event) { const touch = event.touches[0]; isDragging = true; offset = { x: touch.clientX - position.x, y: touch.clientY - position.y }; }
function handleTouchMove(event) { if (!isDragging) return; event.preventDefault(); // Empêche le scroll
const touch = event.touches[0]; position = { x: touch.clientX - offset.x, y: touch.clientY - offset.y }; }
function handleTouchEnd() { isDragging = false; }</script>
<div on:touchstart={handleTouchStart} on:touchmove={handleTouchMove} on:touchend={handleTouchEnd}> <!-- ... --></div>2. Détection de gestes
Section titled “2. Détection de gestes”Les gestes ajoutent une couche d’interactivité physique. Secouer pour réinitialiser, appui long pour révéler, swipe pour naviguer.
Shake (secouer)
Section titled “Shake (secouer)”<script> import { onMount } from 'svelte';
let shakeCount = $state(0); let lastX = 0, lastY = 0, lastZ = 0; const SHAKE_THRESHOLD = 15; // Sensibilité
onMount(() => { if (!window.DeviceMotionEvent) { console.warn('Accéléromètre non disponible'); return; }
function handleMotion(event) { const { x, y, z } = event.accelerationIncludingGravity;
// Calculer le changement d'accélération const deltaX = Math.abs(x - lastX); const deltaY = Math.abs(y - lastY); const deltaZ = Math.abs(z - lastZ);
if (deltaX + deltaY + deltaZ > SHAKE_THRESHOLD) { shakeCount++; }
lastX = x; lastY = y; lastZ = z; }
window.addEventListener('devicemotion', handleMotion);
return () => { window.removeEventListener('devicemotion', handleMotion); }; });</script>
<p>Secousses détectées : {shakeCount}</p>Usage narratif : Secouer pour révéler un secret, effacer un dessin, générer un événement aléatoire.
Long press (appui long)
Section titled “Long press (appui long)”<script> let pressTimer; let isLongPressed = $state(false); const LONG_PRESS_DURATION = 800; // ms
function handlePressStart() { pressTimer = setTimeout(() => { isLongPressed = true; // Action à l'appui long console.log('Appui long détecté'); }, LONG_PRESS_DURATION); }
function handlePressEnd() { clearTimeout(pressTimer); if (!isLongPressed) { // C'était un clic normal console.log('Clic court'); } isLongPressed = false; }</script>
<button on:mousedown={handlePressStart} on:mouseup={handlePressEnd} on:mouseleave={handlePressEnd} on:touchstart={handlePressStart} on:touchend={handlePressEnd} on:touchcancel={handlePressEnd}> Maintenez appuyé</button>Usage narratif : Révéler une information cachée, charger une action, écouter un son.
Swipe (glissement directionnel)
Section titled “Swipe (glissement directionnel)”<script> let touchStartX = $state(0); let touchStartY = $state(0); const SWIPE_THRESHOLD = 50; // Pixels minimum
function handleTouchStart(event) { const touch = event.touches[0]; touchStartX = touch.clientX; touchStartY = touch.clientY; }
function handleTouchEnd(event) { const touch = event.changedTouches[0]; const deltaX = touch.clientX - touchStartX; const deltaY = touch.clientY - touchStartY;
// Détecter la direction dominante if (Math.abs(deltaX) > Math.abs(deltaY)) { // Swipe horizontal if (Math.abs(deltaX) > SWIPE_THRESHOLD) { if (deltaX > 0) { console.log('Swipe droite'); } else { console.log('Swipe gauche'); } } } else { // Swipe vertical if (Math.abs(deltaY) > SWIPE_THRESHOLD) { if (deltaY > 0) { console.log('Swipe bas'); } else { console.log('Swipe haut'); } } } }</script>
<div on:touchstart={handleTouchStart} on:touchend={handleTouchEnd} class="swipe-area"> Swipez dans n'importe quelle direction</div>
<style> .swipe-area { width: 100%; height: 300px; background: #f0f0f0; display: flex; align-items: center; justify-content: center; touch-action: pan-y; /* Autorise le scroll vertical */ }</style>Usage narratif : Navigation entre scènes, exploration spatiale, contrôle de timeline.
3. Événements clavier
Section titled “3. Événements clavier”Le clavier offre des interactions rapides et précises. Utile pour des jeux, des raccourcis, ou des expériences poétiques basées sur la frappe.
Écoute simple
Section titled “Écoute simple”<script> import { onMount } from 'svelte';
let lastKey = $state('');
onMount(() => { function handleKeyPress(event) { lastKey = event.key; }
window.addEventListener('keydown', handleKeyPress);
return () => { window.removeEventListener('keydown', handleKeyPress); }; });</script>
<p>Dernière touche : {lastKey}</p>Combos et modificateurs
Section titled “Combos et modificateurs”<script> function handleKeyDown(event) { // Détection de combo : Ctrl + S if (event.ctrlKey && event.key === 's') { event.preventDefault(); // Empêche la sauvegarde du navigateur console.log('Sauvegarde personnalisée'); }
// Détection de flèches if (event.key === 'ArrowUp') { console.log('Haut'); }
// Shift + clic pour une action alternative if (event.shiftKey) { console.log('Shift maintenu'); } }</script>
<svelte:window on:keydown={handleKeyDown} />Séquence de touches (Konami code)
Section titled “Séquence de touches (Konami code)”<script> const KONAMI_CODE = ['ArrowUp', 'ArrowUp', 'ArrowDown', 'ArrowDown', 'ArrowLeft', 'ArrowRight', 'ArrowLeft', 'ArrowRight', 'b', 'a']; let sequence = [];
function handleKeyDown(event) { sequence = [...sequence, event.key].slice(-KONAMI_CODE.length);
if (JSON.stringify(sequence) === JSON.stringify(KONAMI_CODE)) { console.log('🎉 Code secret activé !'); sequence = []; // Reset } }</script>
<svelte:window on:keydown={handleKeyDown} />Usage narratif : Easter eggs, codes secrets, contrôle de personnage, instrument musical.
4. Inputs utilisateur
Section titled “4. Inputs utilisateur”Les formulaires ne sont pas que pour des sites corporate. Utilisés créativement, ils deviennent des outils de narration.
Input texte réactif
Section titled “Input texte réactif”<script> let userInput = $state('');
let wordCount = $derived(userInput.trim().split(/\s+/).filter(Boolean).length); let mood = $derived( userInput.includes('!') ? 'excité' : userInput.includes('?') ? 'interrogatif' : 'neutre' );</script>
<textarea bind:value={userInput} placeholder="Écrivez quelque chose..."></textarea>
<p>Mots : {wordCount}</p><p>Ton détecté : {mood}</p>Usage narratif : Journal intime, texte dont l’apparence change selon ce que vous écrivez.
Slider comme contrôle narratif
Section titled “Slider comme contrôle narratif”<script> let timeOfDay = $state(12); // 0-24
let skyColor = $derived(`hsl(${210 + timeOfDay * 3}, 70%, ${80 - timeOfDay * 2}%)`); let period = $derived( timeOfDay < 6 ? 'nuit' : timeOfDay < 12 ? 'matin' : timeOfDay < 18 ? 'après-midi' : 'soirée' );</script>
<div class="scene" style="background: {skyColor};"> <p>Il est {timeOfDay}h, c'est le {period}.</p></div>
<input type="range" min="0" max="24" bind:value={timeOfDay} aria-label="Heure de la journée"/>
<style> .scene { width: 100%; height: 300px; display: flex; align-items: center; justify-content: center; transition: background 0.3s ease; color: white; font-size: 1.5rem; }
input[type="range"] { width: 100%; margin-top: 1rem; }</style>Usage narratif : Contrôle du temps, de l’humeur d’un personnage, de l’intensité d’une scène.
Choix multiples visuels
Section titled “Choix multiples visuels”<script> let selectedMood = $state(null);
const moods = [ { id: 'happy', emoji: '😊', label: 'Joyeux' }, { id: 'sad', emoji: '😢', label: 'Triste' }, { id: 'angry', emoji: '😠', label: 'En colère' }, { id: 'neutral', emoji: '😐', label: 'Neutre' } ];</script>
<p>Comment vous sentez-vous ?</p>
<div class="mood-selector"> {#each moods as mood} <button class="mood-btn" class:selected={selectedMood === mood.id} on:click={() => selectedMood = mood.id} > <span class="emoji">{mood.emoji}</span> <span class="label">{mood.label}</span> </button> {/each}</div>
{#if selectedMood} <p>Vous avez choisi : {moods.find(m => m.id === selectedMood)?.label}</p>{/if}
<style> .mood-selector { display: grid; grid-template-columns: repeat(auto-fit, minmax(100px, 1fr)); gap: 1rem; margin: 2rem 0; }
.mood-btn { display: flex; flex-direction: column; align-items: center; gap: 0.5rem; padding: 1rem; background: white; border: 2px solid #ddd; border-radius: 8px; cursor: pointer; transition: all 0.2s ease; }
.mood-btn:hover { transform: translateY(-2px); box-shadow: 0 4px 12px rgba(0,0,0,0.1); }
.mood-btn.selected { border-color: #4CAF50; background: #f0f8f0; }
.emoji { font-size: 2rem; }
.label { font-size: 0.9rem; }</style>Exercice : Tableau de bord interactif
Section titled “Exercice : Tableau de bord interactif”Créez une interface qui combine plusieurs types d’interactions :
Contraintes :
- Un élément déplaçable en drag & drop
- Un slider qui influence l’apparence
- Un raccourci clavier caché
- Une zone swipe (mobile)
- Un input texte qui change la narration
Exemple de concept :
- Tableau de bord d’un vaisseau spatial
- Studio d’enregistrement virtuel
- Interface de création de créature
- Console de contrôle temporel
Ce que vous avez appris
Section titled “Ce que vous avez appris”- ✅ Implémenter du drag & drop (souris + tactile)
- ✅ Détecter des gestes (shake, swipe, long-press)
- ✅ Écouter le clavier (touches, combos, séquences)
- ✅ Utiliser les inputs comme outils narratifs
- ✅ Combiner plusieurs interactions dans une expérience cohérente