Polar Code 🎭

Command Palette

Search for a command to run...

10
Pièce N°10

Module 10 — Le mouvement qui parle, faire danser les divs

Archives CSS — Quand les pixels prennent vie

Date de consignation : Nuit du 20 mars — Lumières dansantes sur les écrans

Le site était beau. Responsive, stylé, sophistiqué. Mais il était mort. Statique. Comme une photo de cadavre bien habillé.

Les utilisateurs cliquaient, mais rien ne répondait. Les menus s'ouvraient brutalement. Les pages changeaient par coup de couteau. L'expérience était chirurgicale. Froide.

C'est là que le mouvement est entré. Pas en force. En douceur. Une transition ici. Une animation là. D'abord timide, puis de plus en plus assuré.

Le CSS n'était plus juste un langage de mise en page. C'était devenu un langage de chorégraphie.


10.1 — TRANSITIONS : L'ART DE LA TRANSFORMATION DOUCE

Changer sans casser

Les transitions, c'est la politesse du web. On ne disparaît pas brusquement. On s'efface. On ne grossit pas d'un coup. On grandit.

/* La base : quatre paramètres qui changent tout */
.element {
  transition: 
    property      /* Quoi */
    duration      /* Combien de temps */
    timing-function /* Comment */
    delay;        /* Quand */
}

/* Exemple concret : un bouton qui respire */
.button {
  background-color: #2c3e50;
  color: white;
  padding: 12px 24px;
  border: none;
  border-radius: 4px;
  cursor: pointer;
  
  /* Transition sur plusieurs propriétés */
  transition: 
    background-color 0.3s ease-in-out,
    transform 0.2s ease-out,
    box-shadow 0.3s cubic-bezier(0.175, 0.885, 0.32, 1.275);
}

.button:hover {
  background-color: #3498db;
  transform: translateY(-2px);
  box-shadow: 0 6px 12px rgba(52, 152, 219, 0.3);
}

.button:active {
  transform: translateY(0);
  transition-duration: 0.1s; /* Plus rapide au clic */
}

⏱️ Les timing functions : le rythme du mouvement

/* Les bases */
.linear      { transition-timing-function: linear; } /* Métronome */
.ease        { transition-timing-function: ease; } /* Défaut : lent/rapide/lent */
.ease-in     { transition-timing-function: ease-in; } /* Lent départ */
.ease-out    { transition-timing-function: ease-out; } /* Lent arrivée */
.ease-in-out { transition-timing-function: ease-in-out; } /* Lent des deux côtés */

/* La magie : cubic-bezier() - créez vos propres courbes */
.custom-bounce {
  transition-timing-function: cubic-bezier(0.68, -0.55, 0.265, 1.55);
  /* Une courbe qui dépasse légèrement avant de revenir */
}

.spring-effect {
  transition-timing-function: cubic-bezier(0.175, 0.885, 0.32, 1.275);
  /* Ressort élégant */
}

/* Utilitaire pratique */
:root {
  --ease-smooth: cubic-bezier(0.4, 0, 0.2, 1);
  --ease-bounce: cubic-bezier(0.175, 0.885, 0.32, 1.275);
  --ease-elastic: cubic-bezier(0.68, -0.55, 0.265, 1.55);
}

.element {
  transition: transform 0.5s var(--ease-bounce);
}

Règle d'or : Transitionnez transform et opacity. Jamais width, height, margin, padding. Le prix à payer est trop élevé.


10.2 — KEYFRAMES : LE SCÉNARIO DU MOUVEMENT

Écrire l'histoire image par image

Les transitions suivent un chemin simple : de A à B. Les animations avec @keyframes peuvent faire A → B → C → D → A.

/* Déclaration de l'animation */
@keyframes slideInFromLeft {
  0% {
    transform: translateX(-100%);
    opacity: 0;
  }
  70% {
    transform: translateX(10px);
    /* Léger dépassement pour effet "rebond" */
  }
  100% {
    transform: translateX(0);
    opacity: 1;
  }
}

/* Application */
.welcome-message {
  animation: 
    slideInFromLeft   /* Nom */
    0.8s              /* Durée */
    var(--ease-bounce) /* Timing function */
    0.3s              /* Délai */
    both;             /* Fill mode */
}

/* Propriétés d'animation détaillées */
.element {
  animation-name: slideInFromLeft;
  animation-duration: 0.8s;
  animation-timing-function: ease-out;
  animation-delay: 0.3s;
  animation-iteration-count: 1; /* ou infinite, ou 3, etc. */
  animation-direction: normal; /* reverse, alternate, alternate-reverse */
  animation-fill-mode: both; /* forwards, backwards, both, none */
  animation-play-state: running; /* ou paused */
}

/* Raccourci */
.element {
  animation: slideInFromLeft 0.8s ease-out 0.3s 1 normal both;
}

🔁 Les boucles et les variations

/* Animation infinie (loader, défilement) */
@keyframes pulse {
  0%, 100% {
    transform: scale(1);
    opacity: 1;
  }
  50% {
    transform: scale(1.1);
    opacity: 0.8;
  }
}

.loading-dot {
  animation: pulse 1.5s ease-in-out infinite;
}

/* Animation alternée (va-et-vient) */
@keyframes shake {
  0%, 100% { transform: translateX(0); }
  25% { transform: translateX(-5px); }
  75% { transform: translateX(5px); }
}

.warning-element {
  animation: shake 0.5s ease-in-out infinite alternate;
}

/* Séquence complexe */
@keyframes multiStep {
  0% {
    transform: translateY(0) rotate(0);
    background: red;
  }
  33% {
    transform: translateY(-20px) rotate(90deg);
    background: yellow;
  }
  66% {
    transform: translateY(10px) rotate(180deg);
    background: blue;
  }
  100% {
    transform: translateY(0) rotate(360deg);
    background: red;
  }
}

10.3 — TRANSFORMATIONS ANIMÉES : LA MAGIE 3D

Donner de la profondeur au plat

/* La boîte à outils transform */
.element {
  /* Translation (déplacement) */
  transform: translateX(100px); /* Horizontal */
  transform: translateY(-50px); /* Vertical */
  transform: translate(100px, -50px); /* Les deux */
  transform: translate3d(100px, -50px, 0); /* Avec axe Z (GPU) */
  
  /* Rotation */
  transform: rotate(45deg); /* 2D */
  transform: rotate3d(1, 0, 0, 45deg); /* 3D */
  
  /* Échelle */
  transform: scale(1.5); /* Uniforme */
  transform: scaleX(1.2) scaleY(0.8); /* Différent par axe */
  transform: scale3d(1.2, 0.8, 1); /* 3D */
  
  /* Inclinaison */
  transform: skewX(15deg);
  transform: skewY(-10deg);
  transform: skew(15deg, -10deg);
  
  /* Origine de la transformation */
  transform-origin: center center; /* Défaut */
  transform-origin: top left;
  transform-origin: 30% 80%;
  
  /* Perspective (effet 3D) */
  perspective: 500px;
}

/* Exemple d'animation de transformation */
@keyframes cardFlip {
  0% {
    transform: perspective(600px) rotateY(0);
  }
  50% {
    transform: perspective(600px) rotateY(90deg);
    opacity: 0.5;
  }
  100% {
    transform: perspective(600px) rotateY(0);
    opacity: 1;
  }
}

.flip-card {
  animation: cardFlip 1s ease-in-out;
}

/* Effet de profondeur au survol */
.card-3d {
  transition: transform 0.5s ease;
  transform-style: preserve-3d;
}

.card-3d:hover {
  transform: 
    perspective(1000px) 
    rotateX(5deg) 
    rotateY(5deg) 
    translateZ(20px);
  box-shadow: 
    0 20px 40px rgba(0,0,0,0.3),
    0 0 0 1px rgba(255,255,255,0.1);
}

10.4 — PERFORMANCE : L'ART DE NE PAS PLANTER LE NAVIGATEUR

Le mouvement doit être fluide, pas saccadé

/* ⚠️ MAUVAIS - déclenche des reflows coûteux */
.slow-element {
  transition: width 0.3s ease, height 0.3s ease, margin 0.3s ease;
}

/* ✅ BON - utilise le GPU */
.fast-element {
  transition: transform 0.3s ease, opacity 0.3s ease;
}

/* Forcer l'utilisation du GPU (ancienne méthode) */
.gpu-accelerated {
  transform: translateZ(0);
  /* ou */
  transform: translate3d(0, 0, 0);
}

/* Méthode moderne : will-change */
.will-animate {
  will-change: transform, opacity;
  /* À utiliser avec parcimonie ! Ne mettez pas will-change partout */
}

/* Exemple optimisé */
.high-performance-card {
  /* Préparation */
  will-change: transform, opacity;
  transform: translateZ(0);
  
  /* Animation */
  transition: 
    transform 0.4s cubic-bezier(0.175, 0.885, 0.32, 1.275),
    opacity 0.3s ease;
}

.high-performance-card:hover {
  transform: translateY(-10px) translateZ(0);
  opacity: 0.9;
}

/* Gérer les préférences de réduction de mouvement */
@media (prefers-reduced-motion: reduce) {
  *,
  *::before,
  *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
    scroll-behavior: auto !important;
  }
  
  /* Alternative : animations simplifiées */
  .reduced-motion {
    animation: none;
    transition: opacity 0.3s ease;
  }
}

Checklist performance :

  1. Utilisez transform et opacity
  2. Évitez d'animer les propriétés qui changent le layout
  3. Utilisez will-change avec parcimonie
  4. Testez sur mobile
  5. Respectez prefers-reduced-motion

10.5 — ANIMATIONS COMPLEXES : DU CHAOS À L'HARMONIE

Orchestrer plusieurs éléments

Loader moderne (3 points)

.loader-dots {
  display: flex;
  gap: 8px;
}

.loader-dots span {
  width: 10px;
  height: 10px;
  border-radius: 50%;
  background: #3498db;
  animation: bounce 1.4s ease-in-out infinite both;
}

.loader-dots span:nth-child(1) {
  animation-delay: -0.32s;
}

.loader-dots span:nth-child(2) {
  animation-delay: -0.16s;
}

@keyframes bounce {
  0%, 80%, 100% {
    transform: scale(0);
    opacity: 0.5;
  }
  40% {
    transform: scale(1);
    opacity: 1;
  }
}

🖼️ Slider automatique CSS-only

.slider-container {
  width: 100%;
  overflow: hidden;
  position: relative;
}

.slider-track {
  display: flex;
  width: 300%; /* 3 slides */
  animation: slide 15s infinite linear;
}

.slide {
  width: 100%;
  flex-shrink: 0;
}

@keyframes slide {
  0%, 25% {
    transform: translateX(0);
  }
  33.33%, 58.33% {
    transform: translateX(-100%);
  }
  66.66%, 91.66% {
    transform: translateX(-200%);
  }
  100% {
    transform: translateX(0);
  }
}

/* Pause au survol */
.slider-container:hover .slider-track {
  animation-play-state: paused;
}

🍔 Menu hamburger animé

.menu-toggle {
  display: none;
}

.hamburger {
  cursor: pointer;
  width: 30px;
  height: 24px;
  position: relative;
  display: flex;
  flex-direction: column;
  justify-content: space-between;
}

.hamburger-line {
  width: 100%;
  height: 3px;
  background: #333;
  border-radius: 3px;
  transition: all 0.4s cubic-bezier(0.175, 0.885, 0.32, 1.275);
}

/* Transformation en croix */
.menu-toggle:checked + .hamburger .hamburger-line:nth-child(1) {
  transform: 
    translateY(10.5px) 
    rotate(45deg);
}

.menu-toggle:checked + .hamburger .hamburger-line:nth-child(2) {
  opacity: 0;
  transform: translateX(-10px);
}

.menu-toggle:checked + .hamburger .hamburger-line:nth-child(3) {
  transform: 
    translateY(-10.5px) 
    rotate(-45deg);
}

/* Menu qui glisse */
.menu {
  position: fixed;
  top: 0;
  right: -100%;
  width: 300px;
  height: 100vh;
  background: white;
  transition: right 0.5s cubic-bezier(0.175, 0.885, 0.32, 1.275);
  box-shadow: -5px 0 15px rgba(0,0,0,0.1);
}

.menu-toggle:checked ~ .menu {
  right: 0;
}

Effet de stagger (décalage)

.stagger-list {
  list-style: none;
}

.stagger-list li {
  opacity: 0;
  transform: translateY(20px);
  animation: fadeInUp 0.5s ease forwards;
}

/* Délai progressif pour chaque élément */
.stagger-list li:nth-child(1) { animation-delay: 0.1s; }
.stagger-list li:nth-child(2) { animation-delay: 0.2s; }
.stagger-list li:nth-child(3) { animation-delay: 0.3s; }
.stagger-list li:nth-child(4) { animation-delay: 0.4s; }
.stagger-list li:nth-child(5) { animation-delay: 0.5s; }

@keyframes fadeInUp {
  to {
    opacity: 1;
    transform: translateY(0);
  }
}

10.6 — CAS PRATIQUES : SUR LE TERRAIN

🟦 SCÈNE 1 : CARTE INTERACTIVE AVEC EFFETS MULTIPLES

.product-card {
  --card-scale: 1;
  --card-rotate: 0;
  
  background: white;
  border-radius: 12px;
  padding: 20px;
  box-shadow: 0 4px 12px rgba(0,0,0,0.1);
  transition: 
    transform 0.6s var(--ease-bounce),
    box-shadow 0.4s ease,
    border-color 0.3s ease;
  
  /* Effet de suivi de souris (avec JS normalement) */
  transform: 
    scale(var(--card-scale))
    rotate(var(--card-rotate));
}

.product-card:hover {
  transform: 
    scale(1.05)
    rotate(1deg);
  box-shadow: 
    0 20px 40px rgba(0,0,0,0.15),
    0 0 0 1px rgba(52, 152, 219, 0.2);
}

/* Animation d'apparition */
@keyframes cardAppear {
  from {
    opacity: 0;
    transform: translateY(30px) scale(0.9);
  }
  to {
    opacity: 1;
    transform: translateY(0) scale(1);
  }
}

.product-card {
  animation: cardAppear 0.8s var(--ease-bounce) both;
}

/* Délai progressif pour les cartes */
.product-card:nth-child(1) { animation-delay: 0.1s; }
.product-card:nth-child(2) { animation-delay: 0.2s; }
.product-card:nth-child(3) { animation-delay: 0.3s; }

🟩 SCÈNE 2 : BOUTON AVEC EFFET DE REMPLISSAGE

.fill-button {
  position: relative;
  padding: 12px 24px;
  background: transparent;
  border: 2px solid #3498db;
  color: #3498db;
  border-radius: 4px;
  cursor: pointer;
  overflow: hidden;
  transition: color 0.4s ease;
}

.fill-button::before {
  content: '';
  position: absolute;
  top: 0;
  left: 0;
  width: 0;
  height: 100%;
  background: #3498db;
  transition: width 0.4s cubic-bezier(0.175, 0.885, 0.32, 1.275);
  z-index: -1;
}

.fill-button:hover {
  color: white;
}

.fill-button:hover::before {
  width: 100%;
}

/* Variante de gauche à droite */
.fill-button.left-right::before {
  width: 100%;
  transform: scaleX(0);
  transform-origin: left;
  transition: transform 0.4s ease;
}

.fill-button.left-right:hover::before {
  transform: scaleX(1);
}

🟥 SCÈNE 3 : NOTIFICATION ANIMÉE

.notification {
  position: fixed;
  top: 20px;
  right: 20px;
  padding: 16px 24px;
  background: #2ecc71;
  color: white;
  border-radius: 8px;
  box-shadow: 0 4px 12px rgba(46, 204, 113, 0.3);
  opacity: 0;
  transform: translateX(100px);
  animation: 
    slideInRight 0.5s ease forwards,
    slideOutRight 0.5s ease 2.5s forwards;
}

@keyframes slideInRight {
  to {
    opacity: 1;
    transform: translateX(0);
  }
}

@keyframes slideOutRight {
  to {
    opacity: 0;
    transform: translateX(100px);
  }
}

10.7 — NOTES DE FIN D'ENQUÊTE

  • La règle des 60 fps : Une animation doit tourner à 60 images par seconde. Sinon, c'est saccadé.
  • Durée idéale : Les micro-interactions : 100-300ms. Les animations d'entrée : 300-500ms. Les animations majeures : 500-1000ms.
  • Accessibilité : Toujours respecter prefers-reduced-motion. Certaines personnes sont sensibles au mouvement.
  • Contextualité : Une animation doit avoir un but : guider, confirmer, divertir. Pas juste être jolie.
  • Consistance : Utilisez les mêmes timing functions, durées et styles d'animation dans toute l'interface.
  • Test mobile : Les animations qui semblent fluides sur desktop peuvent être saccadées sur mobile.

Le secret : Les meilleures animations sont celles qu'on ne remarque pas. Elles font juste partie de l'expérience. Naturelles. Évidentes.


Dernière lumière sur l'écran : 06:18.
Les boutons respirent. Les cartes dansent. Les menus glissent.
Le site n'est plus une série de pages. C'est une performance. Une chorégraphie de pixels.
Le mouvement a donné une âme à l'interface. Il l'a rendue vivante. Humaine.
Demain, on parlera peut-être d'accessibilité. D'inclusion.
Mais pour ce soir, le CSS danse. Et c'est un spectacle.

Fin du dossier Transitions & Animations — Classé et archivé.