Chargement✨ Premium

Wave Shimmer

Squelette carte pur CSS : 7 os, sweep −200 %→+200 % en 2,2 s ; scaleY ±2 % sur l'éclat qui ondule la lueur. Stagger 0 / 120 / 240 ms.

CSSWaveOrganique

Vous aurez accès à la démo interactive avec un compte gratuit.

Cet effet fait partie d'Effect.Labs — 811 effets vanilla, une partie gratuite, une partie premium. Chargement comprend 29 effets, dont 4 gratuits. Découvrir la catégorie →

3 exemples d'usage

Application météo — prévisions en cours d'actualisation — Wave Shimmer exemple 1

① Application météo — prévisions en cours d'actualisation

QuandL'écran d'accueil d'une application météo connaît déjà les conditions du moment (relevé de la station : température, vent, houle, marée) mais rafraîchit son panneau de prévisions — courbe des prochaines heures et tendance à 7 jours — depuis l'API, 1 à 3 s à chaque ouverture ou changement de ville.
PourquoiCe balayage n'est pas un scintillement plat : le dégradé en cloche à sept paliers (pic à 0,14) glisse de gauche à droite en 2,2 s sur une courbe cubic-bezier(.4, 0, .2, 1) qui l'accélère puis le freine, pendant que scaleY oscille 1 → 1,02 → 0,98 → 1,01 — une houle qui traverse l'écran, une rafale qui passe. Sur un sujet vent et mer, le mouvement d'attente rime avec ce qu'il annonce ; un point qui tourne ou une pulsation ne racontent rien de la météo. Et c'est le bloc de 100 px (la future courbe de température) qui rend l'ondulation lisible : ±2 % de hauteur font 2 px sur ce bloc, 0,2 px sur une ligne de 10 px.
RéglagesStructure native conservée : cercle 48 px = icône du jour, titre 65 % = jour, sous-titre 45 % = tendance, bloc 100 px = courbe des prochaines heures, trois lignes 100 / 88 / 55 % = jours suivants. .wave-skel élargi à 100 % de la colonne (308 px, soit des os de 274 px) avec padding: 16px au lieu de 20 pour tenir dans l'écran. Durée 2,2 s, gradient et décalages 0 / 0,12 / 0,24 s inchangés. La carte des conditions actuelles reprend le même habillage que le squelette (fond rgba(255,255,255,.03), bordure .06, rayon 16 px) pour que seul le panneau en attente ondule.
Vignette d'article — chargement d'un fil d'actualité — Wave Shimmer exemple 2

② Vignette d'article — chargement d'un fil d'actualité

QuandUn feed (articles, posts réseau social, résultats e-commerce) affiche ses squelettes pendant le fetch initial ou lors du défilement infini.
PourquoiLe bloc 100 px représente la miniature de l'article ; les trois lignes, le titre et l'extrait. L'ondulation scaleY ±2 % donne une impression de contenu qui se « construit » progressivement, cohérente avec l'arrivée réelle du texte. La teinte neutre blanche-sur-sombre n'introduit pas de couleur parasite dans un flux hétérogène.
RéglagesRetirer l'en-tête (avatar + titre/sous-titre) et garder seulement bloc + lignes pour un format vignette pure. Passer .wave-skel à 100 % de largeur pour une liste verticale. Dupliquer 3 instances avec animation-delay: 0s / 0,15s / 0,3s sur le premier ::after de chaque instance.
Widget KPI — tableau de bord en attente de données — Wave Shimmer exemple 3

③ Widget KPI — tableau de bord en attente de données

QuandUn tableau de bord finance, analytique ou IoT charge ses indicateurs depuis une API externe (1 à 3 s de latence) ; chaque widget affiche son squelette individuellement.
PourquoiLa vitesse 2,2 s est assez lente pour paraître délibérée (calcul ou requête longue) sans agacer. Le bloc 100 px représente un graphique ou une valeur principale, les lignes les métriques secondaires. La neutralité monochrome convient à des interfaces data-denses où la couleur est réservée aux données réelles.
RéglagesRéduire la hauteur du bloc à 60 px ; passer .wave-skel en 100 % de la largeur du widget. Descendre animation-duration à 1,8 s sur ::after si la latence est courte (< 1 s) pour un shimmer plus vif.

Comment ça marche

Structure mesurée : un conteneur .wave-skel de 280 × 276 px (padding 20 px, border-radius 16 px, fond rgba(255,255,255,.03)) abrite sept os. Un cercle .wave-bone-circle.wave-skel-avatar de 48 × 48 px occupe le coin gauche de l'en-tête ; dans le div flex:1 adjacent (176 px) se trouvent le titre (.wave-skel-title, 114 × 14 px, 65 % de 176 px) et le sous-titre (.wave-skel-subtitle, 79 × 10 px, 45 %). Sous l'en-tête : un bloc .wave-skel-block de 238 × 100 px (border-radius 10 px) puis trois lignes .wave-skel-line de 10 px de haut aux largeurs 100 % / 88 % / 55 % (238 px de base, soit 280 − 2 × 20 px de padding). Tous les os partagent background: rgba(255,255,255,.06), position: relative et overflow: hidden qui clippera le pseudo-élément animé.

Mécanique du shimmer : chaque os génère un ::after (position: absolute; inset: 0) portant un dégradé de sept stops en cloche : transparent 0 → .04 → .10 → .14 → .10 → .04 → transparent 100 % sur background-size: 200 % 100 %. L'animation waveFlow (2,2 s, cubic-bezier(.4, 0, .2, 1), infini) déplace background-position de −200 % à +200 % : la bande lumineuse entre par la gauche, atteint le centre à mi-cycle, sort à droite. Simultanément, scaleY oscille 1 → 1,02 → 0,98 → 1,01 → 1 au rythme du sweep — c'est l'ondulation qui distingue cet effet d'un shimmer plat : la lueur rétrécit et s'étale très légèrement en hauteur à chaque passage, clippée par l'overflow: hidden du parent. L'amplitude maximale est ±2 %.

Décalages mesurés (Playwright, reducedMotion natif ignoré) : avatar 0 s, titre 0 s, sous-titre 0,12 s, bloc 0,12 s, première ligne 0,24 s, deuxième et troisième lignes 0 s. Les sélecteurs .wave-skel .wave-bone:nth-child(2)::after et :nth-child(3)::after ciblent tout os qui est deuxième ou troisième enfant de son parent direct au sein de .wave-skel — le sous-titre, deuxième enfant du div flex:1, est donc capturé au même titre que le bloc. Les règles animation-delay posées sur les .wave-bone eux-mêmes (sans ::after) sont sans effet, car l'animation ne vit que sur ::after. Résultat : trois groupes synchronisés (0 / 0,12 / 0,24 s), pas un cascade élément par élément.

Accessibilité

  • prefers-reduced-motion absent — confirmé par mesure : sous reducedMotion: 'reduce' de Playwright, animationPlayState vaut toujours running et animationName reste waveFlow. Ajouter : @media (prefers-reduced-motion: reduce) { .wave-bone::after, .wave-bone-circle::after { animation: none } } pour figer les os en blocs statiques — un repli honnête et lisible.
  • Rôle sémantique manquant : pour un indicateur de chargement, ajouter role="status" et aria-label="Chargement en cours" sur .wave-skel, et aria-busy="true" sur le conteneur parent pendant le chargement. Les lecteurs d'écran annoncent ainsi « Chargement en cours » à l'apparition du squelette. Retirer ces attributs (ou permuter aria-busy à false) quand le vrai contenu est inséré.
  • Sept éléments vides dans l'arbre d'accessibilité : les os sont des div sans texte ni rôle. Si role="status" est posé sur .wave-skel, ajouter aria-hidden="true" sur chacun des os pour éviter que NVDA les liste individuellement.
  • Aucune variable CSS du site (mesuré : zéro var(--…)) : le code fonctionne sur toute page sans conflit de design system. La couleur des os (rgba(255,255,255,.06)) est en dur — sur fond clair, les os deviennent invisibles ; remplacer par rgba(0,0,0,.08) et adapter le gradient de l'éclat en conséquence.

Compatibilité navigateur

Le seul point d'attention est inset: 0 (raccourci de top/right/bottom/left), supporté depuis Chrome 87, Firefox 87 et Safari 14.1 (fin 2020 – début 2021). Sur les navigateurs antérieurs, ::after n'est pas positionné correctement et le shimmer disparaît ; les os restent visibles comme blocs statiques gris.

Chrome 87+✓ Complet
Firefox 87+✓ Complet
Safari 14.1+✓ Complet
Edge 87+✓ Complet
Chrome < 87✓ Os statiques (shimmer absent)
Mobile iOS 14.5+✓ Complet

Sans inset (navigateurs avant fin 2020), les ::after ne remplissent pas les os : pas de shimmer, mais les os restent visibles comme rectangles gris. Sans CSS du tout, les éléments sont inlinés vides — invisibles.

Le code

Structure HTML à coller dans votre page (CSS + JS disponibles avec un compte premium) :

index.html — structure
<div class="wave-skel">
  <div class="wave-skel-header">
    <div class="wave-bone-circle wave-skel-avatar"></div>
    <div style="flex:1">
      <div class="wave-bone wave-skel-title"></div>
      <div class="wave-bone wave-skel-subtitle"></div>
    </div>
  </div>
  <div class="wave-bone wave-skel-block"></div>
  <div class="wave-bone wave-skel-line"></div>
  <!-- … 2 lignes supplémentaires, 3 au total -->
</div>
🔒 Débloquer le code complet — à partir de 2,99 € le 1er mois

HTML + CSS + JS complet, prêt à coller — avec des centaines d'effets premium.

Personnaliser

Options passées à l'API ou attributs data-* :

Option / propriétéDéfautEffet
Durée du sweep (CSS — animation-duration: 2.2s sur ::after) 2,2 s Vitesse du balayage de gauche à droite. 1,5 s : shimmer vif, sensation d'urgence ; 3 s : très doux, convient à un chargement long attendu. Changer la valeur dans la déclaration animation: 2.2s cubic-bezier….
Intensité de l'éclat (CSS — stops du gradient ::after) max 0,14 au centre (rgba(255,255,255,.14)) Luminosité de la bande lumineuse. Monter le pic à .22 pour un shimmer bien visible sur les photos ; descendre à .08 pour un effet très discret sur fond très clair (adapter aussi les stops flanquants en proportion).
Oscillation verticale (keyframes waveFlow — scaleY(1.02) et scaleY(.98)) ±2 % max Amplitude de l'ondulation de la lueur. scaleY(1.005) : quasi-imperceptible, shimmer quasi-plat ; scaleY(1.06) : ondulation marquée. L'effet est clippé par overflow: hidden du parent — l'os lui-même ne change pas de taille.
Hauteur du bloc (CSS — .wave-skel-block { height: 100px }) 100 px Hauteur de la zone de contenu principal. 60 px pour un graphique compact ou une valeur KPI ; 160 px pour une image de couverture ; 200 px pour une vidéo. Border-radius peut être réduit à 6 px pour un format panoramique.
Largeurs des lignes (CSS — .wave-skel-line:nth-child(2) { width: 88% } et :last-child { width: 55% }) 100 % / 88 % / 55 % Imite la longueur réelle des lignes de texte attendu. Un titre long : 95 % ; deux lignes identiques : deux à 100 % ; une dernière ligne courte : 40 %.
Décalages (CSS — ::after de .wave-skel-header+.wave-bone, :nth-child(2), :nth-child(3)) 0 s / 0,12 s / 0,24 s Déphasage de la cascade. Pour un stagger précis élément par élément, remplacer les sélecteurs nth-child par des classes explicites (.wave-skel-subtitle::after { animation-delay: .08s }, etc.) — les nth-child actuels capturent aussi des éléments inattendus (voir how).
Couleur des os (CSS — rgba(255,255,255,.06)) Blanc à 6 % d'opacité Pour fond clair : rgba(0,0,0,.08) sur les os + adapter le gradient en rgba(0,0,0,.06) → .14 → .06. Pour une palette colorée (interface branded) : remplacer par hsla(220,70%,60%,.12) et ajuster les stops du gradient en conséquence.

FAQ

Deux différences dans le CSS. Première : @keyframes waveFlow inclut scaleY(1.02) / scaleY(.98) / scaleY(1.01) que fx-0439 n'a pas — c'est ce scaleY sur ::after qui crée l'ondulation verticale. Deuxième : le gradient de l'éclat compte 7 stops (profil en cloche, pic à .14) contre 3 stops pour fx-0439 (pic à .10), ce qui donne une bande lumineuse plus définie. La durée diffère aussi : 2,2 s avec cubic-bezier(.4, 0, .2, 1) contre 1,8 s en ease-in-out.
Non, c'est une limite des sélecteurs actuels. Les règles :nth-child(2)::after et :nth-child(3)::after capturent tout os qui est respectivement 2ᵉ ou 3ᵉ enfant de son conteneur direct au sein de .wave-skel. Le sous-titre (2ᵉ enfant du div flex:1) est ainsi décalé à 0,12 s au même titre que le bloc, et les lignes 2 et 3 restent à 0 s faute de sélecteur. Pour un cascade ligne par ligne, remplacer par des classes nommées et autant de règles ::after que d'éléments.
Oui, avec trois remplacements. Les os : rgba(255,255,255,.06)rgba(0,0,0,.08). Le fond du conteneur : supprimer background: rgba(255,255,255,.03) ou passer à rgba(0,0,0,.03). Le gradient de l'éclat : inverser les canaux, par exemple rgba(0,0,0,0) → rgba(0,0,0,.04) → rgba(0,0,0,.10) → rgba(0,0,0,.14) → …. La structure, l'animation et les décalages restent inchangés.