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.
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



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,animationPlayStatevaut toujoursrunningetanimationNamerestewaveFlow. 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"etaria-label="Chargement en cours"sur.wave-skel, etaria-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 permuteraria-busyàfalse) quand le vrai contenu est inséré. - Sept éléments vides dans l'arbre d'accessibilité : les os sont des
divsans texte ni rôle. Sirole="status"est posé sur.wave-skel, ajouteraria-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 parrgba(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.
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) :
<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>
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éfaut | Effet |
|---|---|---|
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
@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.: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.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.