Scroll✨ Premium

Rotate In

Six tuiles à icône SVG en grille de 2 colonnes dans un bloc de 280 px : chacune part à opacity: 0, rotate(-15deg) scale(.8) et se pose (1, 0°, 1) pendant les 46 premiers pixels de son entrée par le bas — animation-timeline: view(), animation-range: entry entry 50%, 2,3 Ko de CSS, zéro JavaScript. Les deux tuiles d'une rangée se posent ensemble : les délais de 40 à 200 ms écrits pour les échelonner sont inertes sur une timeline de défilement (mesuré).

CSSRotateStagger

Mis à jour le

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

3 exemples d'usage

Badges obtenus par un élève — application d'apprentissage des langues — Rotate In exemple 1

① Badges obtenus par un élève — application d'apprentissage des langues

QuandÉcran « Mes badges » d'une application mobile : en-tête avec le compte de badges et la série en cours, puis la grille des récompenses — 7 jours de suite, 100 mots, premier dialogue, oreille fine… — chacune avec son pictogramme et sa date, que l'élève fait défiler du pouce ; les trois derniers badges, pas encore obtenus, sont grisés.
PourquoiUn badge est un objet qu'on épingle : la rotation −15° → 0 combinée au scale .8 → 1 mime la pose d'un pin's sur un tableau. Le voisin fx-0552 fait juste monter les cases de 30 px en fondu, ce qui ne dit rien de la récompense.
RéglagesÉcran de 236 × 330 px, bloc .sdc-scroll-container de 216 px sous l'en-tête, plateau en height: auto (les 900 px vendus remplacés par le contenu), gap: 8px, dix tuiles de 102 × 89 px (icône 30 px, titre 10,5 px, date 9,5 px) en thème clair : fond #fffaf0, bordure #f1e3c9, icône #ea580c, titre #1c1917, badges non obtenus en gris à bordure pointillée. La règle d'animation est celle du code : une tuile se pose en 44,5 px. Au repos, la rangée 3 est entrée de 11 px : −11,3°, opacité .25 ; course 287 px. La grille avance seule à 85 px/s jusqu'au premier geste — un artifice de capture, pas l'effet.
Services d'un parc de loisirs — borne d'accueil tactile — Rotate In exemple 2

② Services d'un parc de loisirs — borne d'accueil tactile

QuandÉcran d'accueil d'une borne : à gauche « Où trouver ? » et la consigne, à droite la grille des services — parking, restauration, infirmerie, casiers, poussettes, objets trouvés, toilettes, boutique, point info, navette — avec pictogramme, que le visiteur fait défiler du doigt.
PourquoiSur une borne regardée à un mètre, une tuile de 150 × 99 px doit bouger franchement pour être remarquée : la rotation combinée au scale se voit ; la montée en fondu de fx-0535 va dans le sens du défilement, et de loin la tuile semble seulement arriver un peu plus vite.
RéglagesBloc de 340 × 360 px (toute la hauteur de la borne), gap: 12px, dix tuiles de 150 × 99 px — icône 40 px #2dd4bf à stroke-width: 1.6, titre 13,5 px, fond blanc à 6 %, bordure à 10 %, rayon 16 — sur fond #07111f ; une tuile se pose en 49,5 px. Au repos, trois rangées sont posées et la quatrième, entrée de 18 px, est à −9,7° et .35 ; course 209 px : la liste avance seule à 80 px/s, marque une pause en bas et reboucle jusqu'au premier geste.
Pictogrammes des consignes d'une piscine municipale — page règlement du site de la ville — Rotate In exemple 3

③ Pictogrammes des consignes d'une piscine municipale — page règlement du site de la ville

QuandPage « Règlement de la piscine » du site d'une commune : barre du site, titre, chapeau, puis les consignes — douche obligatoire, bonnet obligatoire, pas de plongeon, pas de course, pas de nourriture, pas de verre, enfants accompagnés, lignes d'eau — en tuiles pictogramme + libellé, deux par ligne, obligations en bleu, interdictions cerclées et barrées de rouge.
PourquoiUne consigne de piscine est un panneau qu'on fixe au mur : la tuile arrive de travers (−15°) et un peu petite (.8), puis se redresse à 0° en prenant sa taille — le geste d'un panneau qu'on met d'aplomb ; sur les interdictions, le cercle et sa barre rouge pivotent avec la tuile, ce qui porte l'œil sur ce qui est défendu. Une montée (fx-0535) fait arriver la tuile comme une ligne de liste, sans rien redresser ; un agrandissement (fx-0566) grossit le cercle tel quel, comme une photo qu'on cadre.
RéglagesThème clair : tuiles blanches, bordure #d3e2ee, titre #0f3554 12,5 px, icône 36 px #0369a1 (interdictions : #dc2626), bloc de 496 × 230 px, huit tuiles de 240 × 92 px, gap: 12px ; une tuile se pose en 46 px. Au repos, deux lignes sont posées et la troisième, entrée de 16 px, est à −9,8° et .34 ; course 195 px, défilement automatique à 75 px/s jusqu'au premier geste.

Comment ça marche

2,3 Ko de CSS, 2,2 Ko de HTML, champ JavaScript vide. Le cadre .demo-preview (flex centré, min-height: 300px, position: relative, fond #0a0a0f, texte blanc, box-sizing: border-box pour tout son contenu) enferme le bloc défilant .sdc-scroll-container — 100% × 280px, overflow-y: auto, scroll-behavior: smooth, barre de 4 px par ::-webkit-scrollbar — et l'indice .sdc-scroll-hint « Scrollez ↓ » (absolu, 8 px du bas du cadre, 10,4 px, blanc à 30 %, pulsation .3 ↔ .7 en 2 s). Dans le bloc, le plateau .sb-rotate-content : 900 px de haut, padding: 40px 12px, grille de 2 colonnes, gap: 12px. Six .sb-rotate-card (padding: 16px, rayon 12 px, fond blanc à 4 %, bordure blanc à 8 %), chacune avec un svg de 32 px (stroke: #8b5cf6, épaisseur 1,5) et un h4 de 12,8 px graisse 700, blanc. Mesuré : tuiles de 614 × 93 px à 1 280 px de large, 169 × 93 à 390 ; rangées à 40, 145 et 250 px du haut du plateau ; course 620 px. Aucun id, aucune variable du site, aucune erreur console ; collé deux fois, 12 tuiles fonctionnent. Seule scorie : un style="font-family: Inter, …" en ligne sur chaque h4 — Inter n'est pas chargée, le titre tombe sur la police système.

La mécanique tient en une règle. État de base : opacity: 0; transform: rotate(-15deg) scale(.8). L'animation sbRotateIn n'a qu'une image-clé, to { opacity: 1; transform: rotate(0deg) scale(1) }, et n'est pas pilotée par le temps mais par la position : animation-timeline: view() lie sa progression à la place de la tuile dans le conteneur de défilement le plus proche — ici .sdc-scroll-container ; sans lui, la page (mesuré). animation-range: entry entry 50% borne cette progression : 0 quand le bord haut de la tuile touche le bord bas du bloc, 1 quand elle est entrée à moitié — 46,5 px pour une tuile de 93 px, en linear, avec animation-duration: auto (la plage est la durée) et animation-fill-mode: both (état de base avant, image finale après). Mesuré, plateau poussé sous le pli : 20 px après l'entrée, opacité .43, −8,55°, échelle .886 ; 40 px : .86, −2,1°, .972 ; 46,5 px : posée. C'est une progression, pas un déclenchement : une tuile qu'on fait redescendre sous sa mi-hauteur se réincline. Les animation-delay de 40 à 200 ms posés par :nth-child(2) à (6) sont inertes : les deux tuiles d'une rangée affichent les mêmes valeurs à chaque position (.645 et .645 pour les tuiles 5 et 6 au repos) — un délai en millisecondes n'a pas de sens sur une timeline graduée en pixels. Le tag « Stagger » n'est pas tenu ; l'échelonnement, c'est l'ordre des rangées.

La démo vendue est bornée par sa géométrie : trois rangées de 93 px à partir de 40 px dans un bloc de 280. Les rangées 1 et 2 sont entières au repos, donc posées ; la rangée 3 est déjà à 64,5 % de sa fenêtre (opacité .645, −5,32°, échelle .929) et finit de se poser à 17 px de défilement ; il reste 603 px de plateau vide. L'effet est intact, c'est la démo qui le cache (voir la FAQ). Deux détails : scroll-behavior: smooth n'agit que sur les défilements programmés (scrollTop = 620 se relit 0 et arrive en 392 ms) ; une tuile à −15° réduite à .8 tient dans sa colonne (boîte englobante de 494 px pour une tuile de 614), aucune barre horizontale n'apparaît.

Accessibilité

  • prefers-reduced-motion absent du code : mesuré sous émulation, rien ne change (rangée 3 à .645 au repos, posée en 17 px, indice qui pulse). Le mouvement est piloté par le lecteur — animation déclenchée par l'interaction (WCAG 2.3.3) ; pour la neutraliser : @media (prefers-reduced-motion: reduce) { .sb-rotate-card { animation: none; opacity: 1; transform: none } .sdc-scroll-container { scroll-behavior: auto } .sdc-scroll-hint { animation: none } }. opacity et transform sont indispensables : animation: none seul laisse la tuile invisible et inclinée.
  • Clavier : mesuré, Chromium 151 et Firefox 153 donnent le focus au bloc par Tab sans tabindex (flèche bas : 40 et 51 px ; Page suivante : 285 et 303) ; WebKit 26 non — le focus reste sur body. Ajoutez tabindex="0", un aria-label et un style :focus-visible sur .sdc-scroll-container. Au doigt, le geste doit commencer dans le bloc.
  • Contraste : titre blanc sur la tuile (#0a0a0f + 4 % de blanc) 18,4:1 ; icône #8b5cf6 4,3:1 (seuil 3:1 des éléments graphiques, tenu). L'opacité descend à 0 pendant l'entrée — c'est le principe —, mais une tuile bloquée à mi-entrée en bas d'un bloc court reste pâle (la rangée 3 de la démo, à .645, jusqu'au premier cran). L'indice pulse entre 1,2:1 et 1,8:1 : décoratif, posez aria-hidden="true" dessus.
  • Lecteurs d'écran et repli : une tuile à opacity: 0 reste dans l'arbre d'accessibilité — les six titres sont lus dès le chargement, rien n'est caché. Six h4 sans h2/h3 au-dessus cassent le plan : prenez le niveau qui suit votre titre courant. Les svg n'ont ni title ni aria-hidden : ajoutez aria-hidden="true". « Scrollez ↓ » est en français en dur. Là où view() n'existe pas, l'animation dure 0 s et fill-mode: both applique l'image finale : tout est visible, à plat (mesuré dans Firefox).

Compatibilité navigateur

CSS seul : animation-timeline: view(), animation-range et animation-duration: auto — les trois arrivent ensemble dans Chrome et Edge 115 et dans Safari 26 (iOS 26). Firefox : aucune version (le module existe derrière layout.css.scroll-driven-animations.enabled, désactivé par défaut). Le reste est classique : grille, transform, @keyframes, overflow-y, scroll-behavior. Zéro dépendance, zéro JavaScript, aucune variable CSS.

Chrome 115+✓ Complet
Firefox (toutes versions)✓ Tuiles à plat, lisibles — aucune animation
Safari 26+✓ Complet (avant : tuiles à plat)
Edge 115+✓ Complet
Mobile iOS 26+✓ Complet (avant : tuiles à plat)
Android Chrome 115+✓ Complet

Mesuré dans Firefox 153, code vendu, page vierge : CSS.supports('animation-timeline: view()') est faux, animation-duration: auto est rejeté et se calcule 0s, la timeline retombe sur l'horloge du document — une animation de 0 s avec animation-fill-mode: both applique aussitôt son image finale. Les six tuiles sont à opacity: 1, rotate(0) scale(1), 94 px de haut, dès le chargement et à toute position : le visiteur Firefox voit la grille posée, à plat, lisible, dans un bloc de 280 px qui défile sur 620 px avec la barre native ; ni rotation ni fondu. Même chose dans Safari 18 et Chrome 114. Le code ne contient pas de repli @supports : ce résultat tient au couple 0s + both. Pour le rendre explicite : @supports not (animation-timeline: view()) { .sb-rotate-card { animation: none; opacity: 1; transform: none } }. Sans le cadre .demo-preview, les titres blancs deviennent invisibles sur une page claire : gardez-le ou donnez un fond aux tuiles.

Le code

Structure HTML à coller dans votre page (le CSS + JS sont inclus avec Premium) :

index.html — structure
<div class="demo-preview">
  <div class="sdc-scroll-container">
    <div class="sb-rotate-content">
      <div class="sb-rotate-card">
        <svg viewBox="0 0 24 24">…</svg>
        <h4>…</h4>
      </div>
      … × 6
    </div>
  </div>
  <span class="sdc-scroll-hint">…</span>
</div>
Débloquer le code complet — 9,90 € HT/mois

Le code complet HTML + CSS + JS de Rotate In, prêt à coller — et les 811 effets. Ou 299 € une fois, accès à vie.

Personnaliser

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

Option / propriétéDéfautEffet
Inclinaison et échelle de départ (CSS — .sb-rotate-card { transform: rotate(-15deg) scale(.8) }) −15° / .8 L'image finale est rotate(0deg) scale(1) : seul le départ se règle. rotate(-6deg) scale(.92) pose discrètement, rotate(15deg) tourne dans l'autre sens, un translateY(20px) ajouté aux deux états fait monter en plus. transform-origin: 0 100% fait pivoter comme une page qu'on tourne.
Fenêtre d'entrée (CSS — animation-range: entry entry 50%) de 0 à 50 % de l'entrée (46,5 px pour une tuile de 93 px) Graduée sur la hauteur de la tuile : entry entry 100% pose la tuile une fois entière (93 px de course), entry 20% entry 70% la fait attendre avant de bouger, entry 0% cover 30% continue jusqu'à 30 % de la traversée du bloc.
Courbe (CSS — animation-timing-function: linear) linear La courbe s'applique à la progression du défilement : ease-out pose vite puis freine, cubic-bezier(.34,1.56,.64,1) dépasse 0° puis revient — un petit rebond. Seul linear suit le doigt au pixel près.
Conteneur de défilement (CSS — .sdc-scroll-container { height: 280px }, .sb-rotate-content { height: 900px }) bloc de 280 px, plateau de 900 px (course 620) view() se rattache au conteneur de défilement le plus proche : sans le bloc, la page fait office (mesuré). Passez le plateau en height: auto pour que la course suive le contenu — les 900 px vendus laissent 603 px de vide. Un ancêtre en overflow: hidden est aussi un conteneur de défilement pour view() ; préférez overflow: clip.
Grille (CSS — grid-template-columns: repeat(2, 1fr), gap: 12px, padding: 40px 12px) 2 colonnes, 12 px, tuiles de 614 × 93 px à 1 280 px de large Le nombre de colonnes décide combien de tuiles se posent d'un bloc (trois colonnes, trois tuiles), pas les délais. Le padding-top fixe combien de rangées sont déjà posées au chargement : 40 px en laissent deux dans un bloc de 280.
Tuile (CSS — .sb-rotate-card, svg, h4) padding 16 px, rayon 12, fond blanc 4 %, bordure 8 % ; icône 32 px #8b5cf6 (1,5) ; titre 12,8 px 700 blanc L'icône est un SVG en ligne au trait (stroke="currentColor" dans le HTML, écrasé par le stroke du CSS) : toute icône 24 × 24 au trait s'y colle. Sur fond clair, donnez à la tuile un fond et une bordure opaques et au titre une couleur sombre — les valeurs vendues sont pensées pour #0a0a0f. Retirez le style en ligne des h4 pour hériter de votre police.
Délais :nth-child (CSS — animation-delay: 40ms … 200ms) 40, 80, 120, 160, 200 ms — sans effet Mesurés inertes sous view(). Supprimez-les, ou décalez la plage de la deuxième colonne pour un vrai décalage dans la rangée : .sb-rotate-card:nth-child(2n) { animation-range: entry 15% entry 65% } — mesuré, à 40 px d'entrée la première tuile est à .43 / −8,5°, la seconde à .13 / −13° ; les deux sont posées à 80 px.
Défilement doux et indice (CSS — scroll-behavior: smooth, .sdc-scroll-hint) smooth / « Scrollez ↓ » pulsant 2 s smooth ne lisse que les positions posées par script ou par ancre (mesuré : scrollTop = 620 arrive en 392 ms) ; passez à auto si un bouton pilote le bloc. L'indice s'accroche au cadre .demo-preview : sans ce cadre, il tombe au bas de la page ; supprimez le span et sdcHintPulse avec lui.

FAQ

Parce que ses six tuiles tiennent presque toutes dans le bloc au repos. Trois rangées de 93 px à partir de 40 px dans un bloc de 280 : les rangées 1 et 2 sont entières, donc posées, et la rangée 3 est déjà entrée de 30 px sur les 46,5 de sa fenêtre — 64,5 %, mesuré : opacité .645, −5,32°, échelle .929. Le premier cran la termine (tout est posé à 17 px) et les 603 px restants ne font défiler que du vide. L'effet est intact, c'est la géométrie de la démo qui le cache : mesuré avec le plateau poussé de 300 px sous le pli, la rangée 1 part de 0 / −15° / .8 et se pose en 46,5 px, puis la 2, puis la 3. Trois réglages au choix : plus de tuiles (huit ou dix, comme les scènes de cette page), un bloc plus court, ou height: auto sur le plateau et un padding-top qui repousse la grille sous le pli. Le voisin fx-0552 a la même géométrie et la même limite.
Le squelette est le même : bloc de 280 px, plateau de 900, grille de 2 colonnes, six éléments, animation-timeline: view(), animation-duration: auto, fill-mode: both, et des animation-delay par :nth-child — 60 à 300 ms chez lui, 40 à 200 ici — tout aussi inertes. Trois écarts. Le mouvement : fx-0552 anime translateY(30px) → 0 avec l'opacité (sbFadeUp), une montée ; fx-0542 anime rotate(-15deg) scale(.8) → rotate(0) scale(1), une pose. La fenêtre : entry entry 40% chez lui, entry entry 50% ici — 37 px de course contre 46,5 pour une tuile de 93 px. Le contenu : des cases de texte « Item 1 » à « Item 6 » contre des tuiles icône SVG + titre. Prenez Rotate In quand la tuile est un objet — badge, pictogramme, vignette — dont la pose doit se voir sur 120 px ; Stagger Reveal quand c'est une ligne de liste qui doit simplement arriver. Ni l'un ni l'autre n'échelonne réellement les deux éléments d'une rangée ; pour cela, décalez la plage de la deuxième colonne (voir « Personnaliser »).
Oui : view() prend le conteneur de défilement le plus proche, et sans .sdc-scroll-container c'est la fenêtre. Mesuré, grille posée 600 px sous le haut d'une fenêtre de 400 px : les tuiles restent à 0 / −15° / .8 sous le pli, puis se posent en 46,5 px chacune à mesure que la page descend. Gardez la règle .sb-rotate-card et le @keyframes, supprimez le bloc et les 900 px du plateau, et vérifiez qu'aucun ancêtre n'est en overflow: hidden — il deviendrait le conteneur de référence ; overflow: clip n'a pas cet effet. Quant à la réinclinaison : l'animation est une progression liée à la position, pas un déclencheur. Quand une tuile ressort par le bas sous sa mi-hauteur, sa progression redescend et elle se réincline (mesuré au retour en haut de la démo : la rangée 3 revient à .645). C'est le comportement de view(), pas un bogue ; pour une pose définitive, il faut un script — un IntersectionObserver qui pose une classe — que cet effet n'a pas.