GalerieGratuit

Basic Marquee

Une ligne de textes qui défile de droite à gauche à vitesse constante, sans fin ni trou quelle que soit la largeur, et qui s'arrête au survol ou d'un clic sur Pause.

CSSMinimal

Mis à jour le

  • Welcome to Effect.Labs
  • Premium Web Effects
  • Modern CSS Animations
  • Infinite Scrolling
Survolez ou cliquez la scène pour interagir

Cet effet est gratuit — la catégorie Galerie en contient 44 au total, dont 6 gratuits. Effect.Labs regroupe 840 effets vanilla. Découvrir la catégorie →

Exemples d'usage

INFOS
  • Bassin nordique fermé pour entretien jusqu'au 14 novembre
  • Ouverture dès 7 h du lundi au vendredi
  • Aquagym du jeudi : complet
  • Bonnet de bain obligatoire

Aujourd'hui

Dernière entrée 45 minutes avant la fermeture

  • Grand bassin · 25 m7:00 – 20:30
  • Bassin ludique10:00 – 18:00
  • Bassin nordiqueFermé
  • Pataugeoire10:00 – 18:00

① Infos pratiques d'une piscine — une fine ligne sous l'en-tête

QuandSur l'accueil de la piscine de Brévalie, un bandeau jaune de 32 px sous le menu fait passer les nouvelles du jour : bassin nordique fermé, horaires, aquagym complet. En dessous, les horaires du jour et les tarifs restent à leur place.
PourquoiQuatre informations courtes qui changent souvent tiennent sur une ligne sans pousser le reste de la page. La vitesse basse (40 px/s) laisse lire une phrase entière avant qu'elle sorte, et le bouton Pause, réduit à 22 px, reste au bout du bandeau.
Réglagesdata-speed="40", --bmq-height: auto, --bmq-padding: 0, --bmq-bg: transparent, --bmq-color: #5b4100, --bmq-size: 12px, --bmq-space: 2.2rem, --bmq-button-size: 22px ; une pastille ajoutée devant chaque <li> par la feuille de la page.
  • Citron
  • Sureau
  • Pamplemousse
  • Gingembre
  • Rhubarbe
  • Menthe

Limonade artisanale

Six parfums, peu de sucre, beaucoup de bulles.

Du vrai fruit, du sucre de canne, rien d'autre.

Bouteille consignée 33 clCaisses de 12Livrée en 48 h

② Noms des parfums en très grand, derrière la bouteille — haut de page d'une limonaderie

QuandSur l'accueil de la limonaderie Corlidane, les six parfums (citron, sureau, pamplemousse, gingembre, rhubarbe, menthe) passent en capitales de 92 px derrière la bouteille dessinée à droite, entre le menu et l'accroche « Six parfums, peu de sucre, beaucoup de bulles ».
PourquoiLa bande sert de décor : elle donne au fond une couleur et un mouvement, et annonce toute la gamme sans liste à lire. Sa teinte, proche du fond, ne concurrence jamais le titre ; la bouteille posée devant donne de la profondeur. Le bouton Pause, posé au bout de la bande, reste à portée.
Réglagesdata-speed="45", --bmq-size: 92px, --bmq-space: 30px, --bmq-color: #efb725, --bmq-bg: transparent, --bmq-height: auto, --bmq-padding: 0, --bmq-button-size: 22px ; capitales et graisse 900 posées par la feuille de la page, bouteille placée au-dessus de la bande.

Nouveau clip

« Les nuits sans carte »

Extrait de l'album du 6 mars. Tournée dans toute la France ce printemps.

③ Dates de tournée entre deux sections — chaque date est un lien

QuandSur le site du groupe Vellumbre, une bande orange sépare le nouveau clip de la boutique ; elle fait passer les cinq dates de mars, chacune avec son lien « Billets ». La souris posée dessus arrête la bande le temps de cliquer.
PourquoiUne coupure franche entre deux sections qui sert à quelque chose : on voit les villes en passant, on clique sans chercher la page Concerts. Au clavier, Tab parcourt les cinq liens une seule fois, bande immobile.
Réglagesdata-speed="55", --bmq-height: 42px, --bmq-size: 13px, --bmq-space: 12px, --bmq-color: #160b08 ; dans chaque <li>, un <a href> en pastille bordée, fond orange porté par la section.

Comment ça marche

Vous écrivez vos messages une seule fois, chacun dans un <li> de la liste .bmq-group. Au chargement, le script mesure la largeur de cette liste et celle de la fenêtre .bmq-viewport, puis ajoute derrière autant de copies qu'il en faut pour couvrir la fenêtre plus une : une copie sur un téléphone, deux dans une zone de 1 900 px avec le texte de démonstration (environ 1 240 px de long). L'ancienne version doublait le texte une fois pour toutes et laissait un vide à droite dès que la zone dépassait la longueur du texte.

L'animation CSS bmq-scroll déplace la piste d'exactement une longueur de liste, puis reprend : la copie suivante a pris la place de l'original au pixel près, le raccord ne se voit pas. La durée est calculée par le script à partir de data-speed (85 px par seconde par défaut) : un texte deux fois plus long met deux fois plus de temps, il ne file pas deux fois plus vite. data-direction="right" inverse le sens.

Un ResizeObserver refait le compte quand la zone ou le texte changent de taille (rotation du téléphone, police chargée après coup), et un MutationObserver refait les copies si vous changez le contenu de la liste par script. Chaque bande est traitée séparément : plusieurs bandes dans la même page ont chacune leur vitesse et leur bouton.

Accessibilité

  • Pause : le bouton rond (aria-pressed) arrête et relance la bande, à la souris, au doigt ou au clavier ; le survol de la bande l'arrête aussi le temps de lire. Une bande qui bouge plus de 5 secondes doit pouvoir être arrêtée (critère WCAG 2.2.2).
  • Lecteurs d'écran : seule la liste d'origine est lue, une fois ; les copies portent aria-hidden="true". Mesuré avant réparation : chaque message était lu deux fois.
  • Liens dans la bande : au clavier, Tab passe par les liens de la liste d'origine seulement (ceux des copies reçoivent tabindex="-1"). Dès qu'un lien a le focus, la bande revient au début, s'immobilise et amène ce lien entièrement en vue ; elle repart quand le focus la quitte.
  • Mouvement réduit : avec prefers-reduced-motion: reduce, rien ne défile seul, les copies et le bouton disparaissent, et la ligne se fait défiler à la main (doigt, pavé tactile, Maj + molette, barre fine) pour lire la fin ; quand elle dépasse, elle reçoit aussi le focus au clavier (Tab) et se fait défiler aux flèches. Sans JavaScript, elle reste immobile et défilable à la main.
  • Couleur : l'indigo #6366f1 sur le fond #1a1a24 donne un contraste de 3,9:1, au-dessus du seuil de 3:1 admis pour un texte de 24 px en gras, mais trop faible pour un petit texte ; sur un fond clair, changez --bmq-color.

Compatibilité navigateur

CSS et 4,5 Ko de JavaScript commenté, aucune dépendance. Animation CSS dont le décalage vient d'une variable (var(--bmq-shift) dans @keyframes), ResizeObserver (Chrome 64, Firefox 69, Safari 13.1), MutationObserver, prefers-reduced-motion (Chrome 74, Firefox 63, Safari 10.1). Mesuré dans Chromium 151, Firefox 153 et WebKit 26.5 : aucun vide à 390, 800 et 1 900 px, deux bandes indépendantes, script collé deux fois sans copie en trop, 0 erreur.

Chrome 74+✓ Complet (mesuré)
Firefox 69+✓ Complet (mesuré)
Safari 13.1+✓ Complet (moteur WebKit mesuré)
Edge 79+✓ Complet (moteur Chromium)
Mobile iOS 13.4+✓ Complet (moteur WebKit mesuré)
Android Chrome 74+✓ Complet (moteur Chromium)

Sans JavaScript, la liste reste immobile, affichée une seule fois et défilable à la main. Sans ResizeObserver (Safari avant 13.1), le compte des copies est refait au redimensionnement de la fenêtre seulement. La barre de défilement fine du mode mouvement réduit (scrollbar-width) n'existe que dans Chrome 121+, Firefox 64+ et Safari 18.2+ ; ailleurs, la barre normale s'affiche.

Le code

Copiez les trois blocs dans votre page. Aucune dépendance.

HTML
<div class="fx-0187-root" data-speed="85" data-direction="left">
  <div class="bmq-band">
    <div class="bmq-viewport">
      <div class="bmq-track">
        <ul class="bmq-group" role="list">
          <li>Welcome to Effect.Labs</li>
          <li>Premium Web Effects</li>
          <li>Modern CSS Animations</li>
          <li>Infinite Scrolling</li>
        </ul>
      </div>
    </div>
    <button type="button" class="bmq-toggle" aria-pressed="false" aria-label="Pause"></button>
  </div>
</div>
CSS
.fx-0187-root,
.fx-0187-root *,
.fx-0187-root *::before,
.fx-0187-root *::after {
  box-sizing: border-box;
}

/* Réglages : variables ci-dessous ; vitesse (px/s) et sens dans les attributs data-speed et data-direction du HTML */
.fx-0187-root {
  --bmq-height: 250px;                        /* hauteur du cadre (auto : celle de la bande) */
  --bmq-bg: rgb(26, 26, 36);                  /* fond du cadre */
  --bmq-color: #6366f1;                       /* couleur du texte */
  --bmq-size: 1.5rem;                         /* taille du texte */
  --bmq-space: 4rem;                          /* espace entre deux éléments */
  --bmq-button: rgba(255, 255, 255, .8);      /* couleur du bouton Pause */
  --bmq-padding: 1rem 0;                      /* marges intérieures du cadre */
  --bmq-button-size: 2rem;                    /* diamètre du bouton Pause */
  display: flex;
  align-items: center;
  width: 100%;
  min-height: var(--bmq-height);
  padding: var(--bmq-padding);
  overflow: hidden;
  position: relative;
  background: var(--bmq-bg);
  color: #fff;
  font-family: sans-serif;
}

.bmq-band {
  display: flex;
  align-items: center;
  gap: .75rem;
  width: 100%;
  min-width: 0;
}

.bmq-viewport {
  flex: 1;
  min-width: 0;
  overflow: hidden;
}

.bmq-track {
  display: flex;
  width: max-content;
}

.bmq-group {
  display: flex;
  align-items: center;
  flex-shrink: 0;
  margin: 0;
  padding: 0;
  list-style: none;
  font-size: var(--bmq-size);
  font-weight: 600;
  color: var(--bmq-color);
}

.bmq-group > li {
  margin: 0 calc(var(--bmq-space) / 2);
  white-space: nowrap;
}

/* Défilement : lancé par le script une fois la piste remplie (classe bmq-on) */
.fx-0187-root.bmq-on .bmq-track {
  animation-name: bmq-scroll;
  animation-duration: var(--bmq-duration, 15s);
  animation-timing-function: linear;
  animation-iteration-count: infinite;
  will-change: transform;
}

.fx-0187-root[data-direction="right"] .bmq-track {
  animation-direction: reverse;
}

@keyframes bmq-scroll {
  from {
    transform: translateX(0);
  }

  to {
    transform: translateX(var(--bmq-shift));
  }
}

/* Pause : survol de la bande ou bouton Pause */
.bmq-viewport:hover .bmq-track,
.fx-0187-root.bmq-paused .bmq-track {
  animation-play-state: paused;
}

/* Focus clavier dans la bande (classe bmq-focus) : bande remise au début et immobile, le navigateur fait défiler jusqu'au lien */
.fx-0187-root.bmq-focus .bmq-track {
  animation: none;
}

/* Bouton Pause / Lecture (supprimez-le du HTML si la bande est courte et purement décorative) */
.bmq-toggle {
  flex: none;
  position: relative;
  width: var(--bmq-button-size);
  height: var(--bmq-button-size);
  margin: 0 1rem 0 0;
  padding: 0;
  border: 1px solid currentColor;
  border-radius: 50%;
  background: transparent;
  color: var(--bmq-button);
  cursor: pointer;
}

.bmq-toggle::before,
.bmq-toggle::after {
  content: "";
  position: absolute;
  top: 50%;
  width: 3px;
  height: 10px;
  margin-top: -5px;
  border-radius: 1px;
  background: currentColor;
}

.bmq-toggle::before {
  left: calc(50% - 4px);
}

.bmq-toggle::after {
  left: calc(50% + 1px);
}

.bmq-toggle[aria-pressed="true"]::before {
  left: calc(50% - 3px);
  width: 0;
  height: 0;
  margin-top: -6px;
  border-style: solid;
  border-width: 6px 0 6px 10px;
  border-color: transparent transparent transparent currentColor;
  border-radius: 0;
  background: none;
}

.bmq-toggle[aria-pressed="true"]::after {
  display: none;
}

.bmq-toggle:hover {
  background: rgba(127, 127, 127, .18);
}

.bmq-toggle:focus-visible {
  outline: 2px solid currentColor;
  outline-offset: 2px;
}

/* Sans script : bande immobile, défilable à la main */
.fx-0187-root:not(.bmq-on) .bmq-viewport {
  overflow-x: auto;
}

.fx-0187-root:not(.bmq-on) .bmq-toggle {
  display: none;
}

/* Mouvement réduit : rien ne bouge seul, le contenu reste lisible et défilable à la main */
@media (prefers-reduced-motion: reduce) {
  .fx-0187-root.bmq-on .bmq-track {
    animation: none;
  }

  .fx-0187-root .bmq-viewport {
    overflow-x: auto;
    scrollbar-width: thin;
  }

  .fx-0187-root .bmq-viewport:focus-visible {
    outline: 2px solid currentColor;
    outline-offset: -2px;
  }

  .fx-0187-root .bmq-clone,
  .fx-0187-root .bmq-toggle {
    display: none;
  }
}
JavaScript (fx-0187)
(function () {
  'use strict';
  // fx-0187 — Basic Marquee : le contenu est écrit une fois, le script le recopie pour remplir la bande
  var ROOT = '.fx-0187-root';
  var VERTICAL = false;
  var DEFAULT_SPEED = 85; // px par seconde
  var FOCUSABLE = 'a[href], button, input, select, textarea, [tabindex]';

  // Copie décorative : cachée aux lecteurs d'écran et hors du parcours clavier
  function copyOf(group) {
    var copy = group.cloneNode(true);
    copy.classList.add('bmq-clone');
    copy.setAttribute('aria-hidden', 'true');
    copy.removeAttribute('id');
    copy.querySelectorAll('[id]').forEach(function (el) { el.removeAttribute('id'); });
    copy.querySelectorAll(FOCUSABLE).forEach(function (el) { el.setAttribute('tabindex', '-1'); });
    return copy;
  }

  function setup(root) {
    if (root.bmqReady) return; // script collé deux fois
    root.bmqReady = true;
    var reduced = window.matchMedia('(prefers-reduced-motion: reduce)');

    var bands = [];
    root.querySelectorAll('.bmq-viewport').forEach(function (viewport) {
      var track = viewport.querySelector('.bmq-track');
      var group = track && track.querySelector('.bmq-group');
      if (!group) return;
      var holder = viewport.closest('[data-speed]');
      bands.push({ viewport: viewport, track: track, group: group, holder: holder && root.contains(holder) ? holder : root });
    });
    if (!bands.length) return;

    // Assez de copies pour couvrir la zone visible + une copie qui entre ; décalage = un groupe
    function fill(band) {
      var box = band.group.getBoundingClientRect();
      var size = VERTICAL ? box.height : box.width;
      var room = VERTICAL ? band.viewport.clientHeight : band.viewport.clientWidth;
      if (!size) return;
      var copies = band.track.children.length;
      var needed = Math.ceil(room / size) + 1;
      for (; copies < needed; copies++) band.track.appendChild(copyOf(band.group));
      var speed = parseFloat(band.holder.getAttribute('data-speed')) || DEFAULT_SPEED;
      band.track.style.setProperty('--bmq-shift', -size + 'px');
      band.track.style.setProperty('--bmq-duration', (size / speed) + 's');
      // Mouvement réduit : bande défilable à la main, donc aussi au clavier (tabindex) quand elle dépasse
      var over = VERTICAL ? band.viewport.scrollHeight > band.viewport.clientHeight + 1 : band.viewport.scrollWidth > band.viewport.clientWidth + 1;
      if (reduced.matches && over) band.viewport.setAttribute('tabindex', '0');
      else band.viewport.removeAttribute('tabindex');
      root.classList.add('bmq-on');
    }

    function fillAll() { bands.forEach(fill); }
    fillAll();

    // Contenu modifié après coup (texte traduit, nouvelle info) : les copies sont refaites
    if ('MutationObserver' in window) {
      bands.forEach(function (band) {
        new MutationObserver(function () {
          band.track.querySelectorAll('.bmq-clone').forEach(function (copy) { copy.remove(); });
          fill(band);
        }).observe(band.group, { childList: true, subtree: true, characterData: true });
      });
    }
    if ('ResizeObserver' in window) {
      var observer = new ResizeObserver(fillAll);
      bands.forEach(function (band) {
        observer.observe(band.viewport);
        observer.observe(band.group);
      });
    } else {
      window.addEventListener('resize', fillAll);
    }

    // Focus clavier sur un lien de la bande : bande immobile (bmq-focus) et lien amené en vue, remise en place à la sortie
    root.addEventListener('focusin', function (event) {
      var viewport = event.target.closest('.bmq-viewport');
      root.classList.toggle('bmq-focus', !!viewport);
      if (!viewport) return;
      var view = viewport.getBoundingClientRect();
      var item = event.target.getBoundingClientRect();
      if (VERTICAL) {
        if (item.bottom > view.bottom) viewport.scrollTop += item.bottom - view.bottom;
        if (item.top < view.top) viewport.scrollTop -= view.top - item.top;
      } else {
        if (item.right > view.right) viewport.scrollLeft += item.right - view.right;
        if (item.left < view.left) viewport.scrollLeft -= view.left - item.left;
      }
    });
    root.addEventListener('focusout', function () {
      root.classList.remove('bmq-focus');
      if (!reduced.matches) bands.forEach(function (band) { band.viewport.scrollLeft = 0; band.viewport.scrollTop = 0; });
    });

    var toggle = root.querySelector('.bmq-toggle');
    if (toggle) {
      toggle.addEventListener('click', function () {
        var paused = toggle.getAttribute('aria-pressed') !== 'true';
        toggle.setAttribute('aria-pressed', paused ? 'true' : 'false');
        root.classList.toggle('bmq-paused', paused);
      });
    }
  }

  function init() {
    document.querySelectorAll(ROOT).forEach(setup);
  }

  if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', init);
  else init();
})();

Personnaliser

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

Option / propriétéDéfautEffet
Contenu (HTML — les <li> de .bmq-group) 4 textes de démonstration Écrivez chaque message une seule fois : texte, liens, icônes ou images. Le script fabrique les copies, et les refait si le contenu change ensuite.
data-speed (sur la racine) 85 Vitesse en pixels par seconde, la même quelle que soit la longueur du texte. 30 à 50 pour des phrases à lire, 80 à 120 pour un décor.
data-direction left right fait défiler de gauche à droite.
--bmq-color / --bmq-size #6366f1 / 1.5rem Couleur et taille du texte de la bande.
--bmq-space 4rem Écart entre deux messages, le même au raccord entre deux copies.
--bmq-height / --bmq-padding / --bmq-bg 250px / 1rem 0 / rgb(26, 26, 36) Cadre de démonstration. Pour une bande posée dans une page : auto, 0 et transparent.
--bmq-button / --bmq-button-size rgba(255, 255, 255, .8) / 2rem Couleur et diamètre du bouton Pause. Retirez le <button> du HTML seulement pour une bande courte et décorative.

FAQ

Pourquoi n'y a-t-il plus de vide à droite sur un grand écran ?

Avant, le texte était écrit deux fois dans le HTML et l'animation le décalait de 50 % : si la zone faisait plus que la longueur du texte (environ 1 240 px ici), la fin de la deuxième copie arrivait avant le bord droit. Le script compte maintenant les copies d'après la largeur réelle, et refait le compte à chaque redimensionnement.

Comment ajouter un message sans recharger la page ?

Ajoutez un <li> à la liste .bmq-group (par exemple avec append()) : le script voit le changement, retire les anciennes copies, en refait de nouvelles et recalcule la durée pour garder la même vitesse.

La bande s'arrête-t-elle sur téléphone ?

Le bouton Pause est le moyen sûr. Mesuré dans Chromium en mode tactile : un toucher sur la bande l'arrête aussi, car le navigateur simule un survol jusqu'au toucher suivant ailleurs dans la page ; tous les navigateurs mobiles ne le font pas. Glisser le doigt verticalement sur la bande fait toujours défiler la page, et toucher un lien de la bande l'ouvre, comme ailleurs.