GalerieGratuit

Velocity Marquee

Bandes de texte sans fin que le défilement de la page pousse et incline : elles s'arrêtent au survol, au focus ou d'un clic sur Pause.

MarqueeScroll VelocitySeamlessrAF

Mis à jour le

SEAMLESS INFINITE VELOCITY MARQUEE NO GAP SCROLL PHYSICS
NEW DROP EFFECT.LABS VANILLA JS NO FRAMEWORK OPEN SOURCE CSS + JS
INERTIA SKEWX MASK-IMAGE RAF + LERP A11Y WCAG 2.2.2
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

Panier (0)
DROP 07 HIVER BRUT 300 PIÈCES FAIT À ROUBAIX
LAINE BOUILLIE DENIM 14 OZ COTON CIRÉ ENVOI LE 14 NOV.
Drop 07 · 14 nov.

Hiver brut

Douze pièces coupées dans des tissus lourds. Faites défiler : la bande accélère avec vous.

Voir le drop
Manteau laine289 €
Veste coton ciré214 €
Jean large 14 oz139 €

① Manifeste d'une collection de mode qui accélère au défilement

QuandLe haut de la page d'une collection limitée : sous la navigation, une grande bande « DROP 07 ✦ HIVER BRUT ✦ 300 PIÈCES » et une fine bande inverse qui liste les matières, au-dessus des trois premiers produits.
PourquoiLa bande réagit au geste le plus courant de la page, le défilement : elle donne du rythme à l'arrivée sur la collection sans vidéo ni image lourde, et s'arrête dès que la souris s'y pose pour qu'on puisse lire.
Réglagesdata-vmq-speed="70", data-vmq-skew="9" ; grande bande --vmq-size:58px, petite bande data-dir="rtl" à 15 px ; couleurs --vmq-color:#f5f5f4, --vmq-accent:#f97316 sur la racine ; hauteur de racine 124 px, fond transparent.
Le Ferlanc EnquêtesPortraitsArchives S'abonner

À deux heures du matin, les presses se sont tues. Seul l'atelier de coupe reste éclairé, où trois personnes terminent une commande attendue avant l'aube.

CHAPITRE II LES ATELIERS DE NUIT 23 H – 5 H LE GESTE

II.Ce que la nuit fabrique

Personne ici ne parle de travail de nuit. On dit « la deuxième équipe », comme si la journée continuait simplement sous les néons.

② Bande de transition entre deux chapitres d'un long article

QuandDans une enquête longue d'un magazine en ligne, entre la fin du chapitre I et l'intertitre du chapitre II : une bande sombre pleine largeur annonce « CHAPITRE II ✦ LES ATELIERS DE NUIT ✦ 23 H – 5 H ».
PourquoiLe lecteur fait défiler en continu : la bande s'anime au moment précis où il passe d'une partie à l'autre, marque la coupure comme un intertitre de presse et reprend son calme dès qu'il s'arrête de lire en défilant.
Réglagesdata-vmq-speed="42", data-vmq-skew="6" ; une seule bande de 56 px de haut, texte 22 px ; fond #1c1917, texte --vmq-color:#f4efe6, accent #c2410c ; bouton Pause centré verticalement à droite.
BRISOLDE Billets
Aujourd'hui
18:30Scène Nord · Ouverture des portes 19:15Chapiteau · Atelier percussions 20:30Grande scène · Concert d'ouverture 22:00Cour · DJ set 23:40Bord du lac · Feu d'artifice
4 – 6 juin · Au bord du lac

Brisolde 2027

Trois jours de musique, quarante concerts. Survolez les horaires du jour pour les arrêter et les lire.

Prendre un billetTout le programme

③ Horaires du jour dans l'en-tête d'un festival — la bande s'arrête sous la souris

QuandSous l'en-tête d'un site de festival, une fine ligne de 32 px précédée de « Aujourd'hui » fait passer les horaires de la journée (heure en jaune, scène et rendez-vous).
PourquoiLes horaires restent visibles sans prendre de place dans l'en-tête ; comme la bande s'arrête au survol et au focus, chacun peut lire tranquillement l'heure qui l'intéresse.
Réglagesdata-vmq-speed="38", data-vmq-skew="3" ; bande de 32 px, texte 12,5 px, heure dans un <b> à l'intérieur de chaque .vmq-item ; racine en flex: 1 à côté de l'étiquette « Aujourd'hui ».

Comment ça marche

Chaque .vmq-root placé dans une racine .fx-0831-root est une bande. Le script garde vos éléments (.vmq-item, .vmq-sep) puis ajoute derrière eux juste assez de copies pour couvrir la largeur de la bande ; la position avance en pixels par seconde, calculée sur le temps écoulé (même vitesse sur un écran 60 Hz ou 120 Hz), et revient à zéro après exactement un jeu d'éléments, d'où l'absence de raccord visible.

La vitesse de base (50 px/s) respire doucement (±12 %). Faire défiler la page ajoute une impulsion proportionnelle au nombre de pixels défilés : vers le bas la bande accélère, vers le haut elle ralentit et peut repartir à l'envers. Un mouvement horizontal de la souris dans la racine, entre les bandes, fait de même (sur une bande, c'est l'arrêt qui l'emporte). L'écart à la vitesse de base incline le texte (skewX, 7° au maximum) puis tout revient au calme en douceur.

Les bandes d'une même racine partagent leur état : data-dir="rtl" inverse le sens d'une bande, le survol ou le focus d'une bande les arrête toutes en 0,4 s environ, et le bouton .vmq-pause-btn les fige. Deux racines dans la page restent indépendantes. Hors de l'écran, la boucle s'arrête ; elle se reconstruit quand la largeur de la racine change.

Accessibilité

  • Pause : survoler une bande ou y placer le focus clavier (un lien dans la bande, par exemple) l'arrête ; le bouton Pause porte aria-pressed et fige toutes les bandes de la racine jusqu'au clic suivant (WCAG 2.2.2).
  • Copies masquées : seuls vos éléments d'origine sont lus par les lecteurs d'écran ; les copies ajoutées pour la boucle portent aria-hidden="true" et leurs liens tabindex="-1", donc rien n'est annoncé deux fois ni atteint deux fois au clavier.
  • Mouvement réduit : sous prefers-reduced-motion: reduce, aucune copie n'est créée et rien ne bouge ; chaque bande devient une ligne défilable au doigt, à la molette ou au clavier (elle reçoit tabindex="0" quand son contenu dépasse). Le bouton Pause et la consigne de démonstration sont alors masqués.
  • Séparateurs décoratifs : les « ✦ » et « · » portent aria-hidden="true".

Compatibilité navigateur

Pointer Events, IntersectionObserver et ResizeObserver (ces deux derniers testés avant usage), mask-image avec préfixe -webkit-. Mesuré le 08/11/2026 dans Chromium, Firefox et le WebKit de Playwright : vitesse, pause au survol, au focus et au bouton, deux copies indépendantes, mouvement réduit, toucher à 390 px.

Chrome 64+✓ Complet (mesuré)
Firefox 69+✓ Complet (mesuré)
Safari 13.1+✓ Mesuré dans WebKit, pas dans un vrai Safari
Edge 79+✓ Complet (même moteur que Chrome)
Safari iOS 13.4+✓ Non vérifié sur appareil
Chrome Android 64+✓ Toucher mesuré en émulation (390 px)

Sans JavaScript, chaque bande affiche vos éléments d'origine, immobiles, coupés au bord droit. Sans masque CSS, les bords de la bande sont nets au lieu de s'estomper.

Le code

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

HTML
<div class="fx-0831-root" data-vmq-speed="50" data-vmq-skew="7">
  <div class="vmq-demo-wrapper">
    <span class="vmq-demo-label" aria-hidden="true">déplacez le curseur · scrollez la page</span>

    <!-- bande secondaire haute (RTL, petite) -->
    <div class="vmq-stripe vmq-stripe--sm">
      <div class="vmq-root" data-dir="rtl"
           style="--vmq-size:clamp(13px,2.4vw,20px);--vmq-gap:18px;--vmq-sep-gap:10px;">
        <div class="vmq-track">
          <span class="vmq-item" data-style="outline">SEAMLESS</span>
          <span class="vmq-sep" aria-hidden="true">·</span>
          <span class="vmq-item" data-style="outline">INFINITE</span>
          <span class="vmq-sep" aria-hidden="true">·</span>
          <span class="vmq-item" data-style="outline">VELOCITY</span>
          <span class="vmq-sep" aria-hidden="true">·</span>
          <span class="vmq-item" data-style="outline">MARQUEE</span>
          <span class="vmq-sep" aria-hidden="true">·</span>
          <span class="vmq-item" data-style="outline">NO GAP</span>
          <span class="vmq-sep" aria-hidden="true">·</span>
          <span class="vmq-item" data-style="outline">SCROLL PHYSICS</span>
          <span class="vmq-sep" aria-hidden="true">·</span>
        </div>
      </div>
    </div>

    <!-- bande principale (grande typo, LTR) -->
    <div class="vmq-stripe vmq-stripe--lg">
      <div class="vmq-root" style="--vmq-size:clamp(30px,6vw,56px);--vmq-gap:24px;">
        <div class="vmq-track">
          <span class="vmq-item">NEW DROP</span>
          <span class="vmq-sep" aria-hidden="true">✦</span>
          <span class="vmq-item" data-style="outline">EFFECT.LABS</span>
          <span class="vmq-sep" aria-hidden="true">✦</span>
          <span class="vmq-item">VANILLA JS</span>
          <span class="vmq-sep" aria-hidden="true">✦</span>
          <span class="vmq-item" data-style="outline">NO FRAMEWORK</span>
          <span class="vmq-sep" aria-hidden="true">✦</span>
          <span class="vmq-item">OPEN SOURCE</span>
          <span class="vmq-sep" aria-hidden="true">✦</span>
          <span class="vmq-item" data-style="outline">CSS + JS</span>
          <span class="vmq-sep" aria-hidden="true">✦</span>
        </div>
      </div>
    </div>

    <!-- bande secondaire basse (RTL, petite) -->
    <div class="vmq-stripe vmq-stripe--sm">
      <div class="vmq-root" data-dir="rtl"
           style="--vmq-size:clamp(13px,2.4vw,20px);--vmq-gap:18px;--vmq-sep-gap:10px;">
        <div class="vmq-track">
          <span class="vmq-item" data-style="outline">INERTIA</span>
          <span class="vmq-sep" aria-hidden="true">·</span>
          <span class="vmq-item" data-style="outline">SKEWX</span>
          <span class="vmq-sep" aria-hidden="true">·</span>
          <span class="vmq-item" data-style="outline">MASK-IMAGE</span>
          <span class="vmq-sep" aria-hidden="true">·</span>
          <span class="vmq-item" data-style="outline">RAF + LERP</span>
          <span class="vmq-sep" aria-hidden="true">·</span>
          <span class="vmq-item" data-style="outline">A11Y</span>
          <span class="vmq-sep" aria-hidden="true">·</span>
          <span class="vmq-item" data-style="outline">WCAG 2.2.2</span>
          <span class="vmq-sep" aria-hidden="true">·</span>
        </div>
      </div>
    </div>

    <button class="vmq-pause-btn" type="button" aria-pressed="false">
      <span aria-hidden="true">⏸</span>
      <span>Pause</span>
    </button>
  </div>
</div>
CSS
.fx-0831-root,
.fx-0831-root *,
.fx-0831-root *::before,
.fx-0831-root *::after {
  box-sizing: border-box
}

.fx-0831-root {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 100%;
  min-height: 300px;
  overflow: hidden;
  position: relative;
  background: rgb(26, 26, 36);
  color: rgb(255, 255, 255);
  font-family: sans-serif;
}

.vmq-root {
  position: relative;
  overflow: hidden;
  width: 100%;
  cursor: default;
  -webkit-mask-image: linear-gradient(to right,
      transparent 0%,
      #000 9%,
      #000 91%,
      transparent 100%);
  mask-image: linear-gradient(to right,
      transparent 0%,
      #000 9%,
      #000 91%,
      transparent 100%);
}

@supports not ((mask-image: linear-gradient(to right, #000, #000)) or (-webkit-mask-image: linear-gradient(to right, #000, #000))) {
  .vmq-root {
    border-radius: 0;
  }
}

.vmq-track {
  display: flex;
  align-items: center;
  white-space: nowrap;
  will-change: transform;
  transform-origin: left center;
  user-select: none;
  -webkit-user-select: none;
}

.vmq-item {
  flex-shrink: 0;
  display: inline-flex;
  align-items: center;
  padding: 0 var(--vmq-gap, 28px);
  font-size: var(--vmq-size, clamp(24px, 5vw, 48px));
  font-weight: 800;
  letter-spacing: -0.02em;
  line-height: 1;
  color: var(--vmq-color, #f1f5f9);
  white-space: nowrap;
  text-decoration: none;
}

.vmq-item[data-style="outline"] {
  color: transparent;
  -webkit-text-stroke: 1.5px var(--vmq-stroke, rgba(241, 245, 249, 0.45));
  text-stroke: 1.5px var(--vmq-stroke, rgba(241, 245, 249, 0.45));
}

.vmq-sep {
  flex-shrink: 0;
  padding: 0 var(--vmq-sep-gap, 16px);
  font-size: calc(var(--vmq-size, clamp(24px, 5vw, 48px)) * 0.60);
  color: var(--vmq-accent, #6366f1);
  line-height: 1;
  pointer-events: none;
  white-space: nowrap;
}

.vmq-demo-wrapper {
  position: relative;
  width: 100%;
  min-height: 300px;
  display: flex;
  flex-direction: column;
  align-items: stretch;
  justify-content: center;
  background: #06060e;
  border-radius: 14px;
  overflow: hidden;
  gap: 0;
}

.vmq-demo-label {
  position: absolute;
  top: 16px;
  left: 50%;
  transform: translateX(-50%);
  font-size: 11px;
  color: rgba(148, 163, 184, 0.30);
  letter-spacing: 0.10em;
  text-transform: uppercase;
  font-weight: 500;
  pointer-events: none;
  white-space: nowrap;
  z-index: 2;
}

.vmq-stripe {
  display: flex;
  align-items: center;
  flex: 1;
  min-height: 60px;
}

.vmq-stripe--lg {
  flex: 2;
  min-height: 100px;
}

.vmq-stripe--sm {
  flex: 1;
  opacity: 0.6;
  min-height: 56px;
}

.vmq-pause-btn {
  position: absolute;
  bottom: 14px;
  right: 14px;
  z-index: 10;
  display: inline-flex;
  align-items: center;
  gap: 6px;
  background: rgba(15, 15, 26, 0.80);
  border: 1px solid rgba(99, 102, 241, 0.35);
  color: #c7d2fe;
  padding: 6px 12px 6px 10px;
  border-radius: 20px;
  font-size: 12px;
  font-weight: 600;
  cursor: pointer;
  letter-spacing: 0.01em;
  transition: background 0.15s, border-color 0.15s;
  outline: 2px solid transparent;
  outline-offset: 2px;
}

.vmq-pause-btn:hover {
  background: rgba(30, 30, 50, 0.90);
  border-color: rgba(99, 102, 241, 0.65);
}

.vmq-pause-btn:focus-visible {
  outline-color: #818cf8;
}

.vmq-pause-btn[aria-pressed="true"] {
  background: rgba(49, 46, 129, 0.85);
  border-color: #6366f1;
  color: #e0e7ff;
}

/* mouvement réduit : bande immobile, défilable à la main (doigt, molette, clavier) */
.vmq-root.vmq-static {
  overflow-x: auto;
  -webkit-mask-image: none;
  mask-image: none;
  scrollbar-width: thin;
  scrollbar-color: rgba(148, 163, 184, 0.35) transparent;
}

.vmq-root.vmq-static .vmq-track {
  transform: none;
  user-select: text;
  -webkit-user-select: text;
}

.vmq-root:focus-visible {
  outline: 2px solid #818cf8;
  outline-offset: -2px;
}

@media (prefers-reduced-motion: reduce) {
  .vmq-pause-btn,
  .vmq-demo-label {
    display: none;
  }
}
JavaScript (fx-0831)
(function () {
  /* fx-0831 — Velocity Marquee : une instance par .fx-0831-root */
  'use strict';

  var RM = window.matchMedia('(prefers-reduced-motion: reduce)');
  var FOCUSABLE = 'a[href], button, input, select, textarea, [tabindex]';

  function num(v, d, min, max) {
    var n = parseFloat(v);
    if (isNaN(n)) n = d;
    return Math.max(min, Math.min(max, n));
  }

  /* lissage indépendant de la fréquence d'écran (k = facteur prévu pour 60 im/s) */
  function ease(k, dt) { return 1 - Math.pow(1 - k, dt * 60); }

  function setupRoot(root) {
    if (root.getAttribute('data-vmq-ready')) return;
    root.setAttribute('data-vmq-ready', '1');

    var BASE = num(root.getAttribute('data-vmq-speed'), 50, 5, 400);   /* px/s */
    var SKEW_MAX = num(root.getAttribute('data-vmq-skew'), 7, 0, 20);  /* degrés */
    var SCROLL_K = 3.6;   /* px/s ajoutés par px de défilement de la page */
    var MOUSE_K = 1.7;    /* px/s ajoutés par px de mouvement horizontal de la souris */
    var SKEW_K = 0.0047;  /* degrés d'inclinaison par px/s d'écart à la vitesse de base */
    var BREATHE = 0.12;   /* respiration lente : ±12 % de la vitesse */

    var btn = root.querySelector('.vmq-pause-btn');
    var bands = [];
    var vel = BASE, skew = 0, sBonus = 0, mBonus = 0, time = 0;
    var paused = false, visible = true, reduced = RM.matches;
    var raf = 0, lastT = 0, lastY = window.scrollY || 0, lastX = null;

    Array.prototype.forEach.call(root.querySelectorAll('.vmq-root'), function (el) {
      var track = el.querySelector('.vmq-track');
      if (!track) return;
      var b = {
        el: el, track: track, orig: Array.prototype.slice.call(track.children),
        setW: 0, pos: 0, rtl: el.getAttribute('data-dir') === 'rtl', hover: false, focus: false
      };
      el.addEventListener('pointerenter', function (e) { if (e.pointerType === 'mouse') b.hover = true; });
      el.addEventListener('pointerleave', function () { b.hover = false; });
      el.addEventListener('focusin', function () { b.focus = true; });
      el.addEventListener('focusout', function () { b.focus = false; });
      bands.push(b);
    });
    if (!bands.length) return;

    function held() {
      for (var i = 0; i < bands.length; i++) if (bands[i].hover || bands[i].focus) return true;
      return false;
    }

    function clone(node) {
      var c = node.cloneNode(true);
      if (c.nodeType !== 1) return c;
      c.setAttribute('aria-hidden', 'true');
      c.removeAttribute('id');
      if (c.matches(FOCUSABLE)) c.setAttribute('tabindex', '-1');
      Array.prototype.forEach.call(c.querySelectorAll('[id]'), function (n) { n.removeAttribute('id'); });
      Array.prototype.forEach.call(c.querySelectorAll(FOCUSABLE), function (n) { n.setAttribute('tabindex', '-1'); });
      return c;
    }

    /* (re)construit chaque bande : jeu d'origine + copies cachées aux lecteurs d'écran */
    function build() {
      bands.forEach(function (b) {
        var t = b.track;
        while (t.lastChild && b.orig.indexOf(t.lastChild) < 0) t.removeChild(t.lastChild);
        t.style.transform = '';
        b.el.classList.toggle('vmq-static', reduced);
        b.el.removeAttribute('tabindex');
        if (reduced) {
          b.setW = 0;
          if (b.el.scrollWidth > b.el.clientWidth + 1) b.el.setAttribute('tabindex', '0');
          return;
        }
        var cw = b.el.clientWidth || 480;
        var sw = t.scrollWidth || 200;
        var n = Math.ceil(cw / sw) + 1;
        for (var i = 0; i < n; i++) b.orig.forEach(function (o) { t.appendChild(clone(o)); });
        var first = b.orig[0], twin = t.children[b.orig.length];
        var w = (first && twin) ? twin.getBoundingClientRect().left - first.getBoundingClientRect().left : 0;
        b.setW = w > 1 ? w : sw;
        b.pos = b.pos % b.setW;
      });
      if (!reduced) draw();
      run();
    }

    function draw() {
      bands.forEach(function (b) {
        if (!b.setW) return;
        var sk = b.rtl ? -skew : skew;
        b.track.style.transform = 'translateX(' + (-b.pos).toFixed(2) + 'px) skewX(' + sk.toFixed(3) + 'deg)';
      });
    }

    function frame(t) {
      raf = 0;
      if (reduced || paused || !visible) { lastT = 0; return; }
      raf = requestAnimationFrame(frame);
      var dt = lastT ? Math.min((t - lastT) / 1000, 0.05) : 0;
      lastT = t;
      if (!dt) return;

      var hold = held();
      time += 0.54 * dt;
      var target = hold ? 0 : BASE * (1 + Math.sin(time) * BREATHE) + sBonus + mBonus;
      sBonus *= Math.pow(0.80, dt * 60);
      mBonus *= Math.pow(0.84, dt * 60);
      /* survol ou focus : arrêt rapide (WCAG 2.2.2), sinon retour en douceur */
      vel += (target - vel) * ease(hold ? 0.16 : 0.07, dt);
      if (hold && Math.abs(vel) < 0.5) vel = 0;

      var tSkew = hold ? 0 : Math.max(-SKEW_MAX, Math.min(SKEW_MAX, -(vel - BASE) * SKEW_K));
      skew += (tSkew - skew) * ease(0.09, dt);

      bands.forEach(function (b) {
        if (!b.setW) return;
        var step = (b.rtl ? -vel : vel) * dt;
        b.pos = ((b.pos + step) % b.setW + b.setW) % b.setW;
      });
      draw();
    }

    function run() {
      if (!raf && !reduced && !paused && visible) { lastT = 0; raf = requestAnimationFrame(frame); }
    }

    window.addEventListener('scroll', function () {
      var y = window.scrollY || 0;
      if (visible) sBonus = (y - lastY) * SCROLL_K;
      lastY = y;
    }, { passive: true });

    root.addEventListener('pointermove', function (e) {
      if (e.pointerType !== 'mouse') return;
      if (lastX !== null) mBonus = (e.clientX - lastX) * MOUSE_K;
      lastX = e.clientX;
    });
    root.addEventListener('pointerleave', function () { lastX = null; });

    if (btn) {
      btn.addEventListener('click', function () {
        paused = !paused;
        btn.setAttribute('aria-pressed', paused ? 'true' : 'false');
        var icon = btn.querySelector('[aria-hidden="true"]');
        if (icon) icon.textContent = paused ? '▶' : '⏸';
        if (paused) { vel = 0; skew = 0; draw(); } else { vel = 0; run(); }
      });
    }

    if (RM.addEventListener) {
      RM.addEventListener('change', function (e) { reduced = e.matches; skew = 0; build(); });
    }

    if ('IntersectionObserver' in window) {
      new IntersectionObserver(function (en) {
        visible = en[0].isIntersecting;
        run();
      }).observe(root);
    }

    var lastW = root.clientWidth, pending = 0;
    if ('ResizeObserver' in window) {
      new ResizeObserver(function () {
        if (!root.clientWidth || root.clientWidth === lastW || pending) return;
        pending = requestAnimationFrame(function () { pending = 0; lastW = root.clientWidth; build(); });
      }).observe(root);
    }

    build();
    if (document.fonts && document.fonts.ready) document.fonts.ready.then(build);
  }

  function init() {
    Array.prototype.forEach.call(document.querySelectorAll('.fx-0831-root'), setupRoot);
  }

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

Personnaliser

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

Option / propriétéDéfautEffet
Vitesse (HTML — data-vmq-speed sur .fx-0831-root) 50 (px/s) Vitesse de croisière de toutes les bandes de la racine, de 5 à 400 px/s. Le défilement de la page s'ajoute à cette valeur.
Inclinaison maximale (HTML — data-vmq-skew) 7 (degrés) Angle maximal du skewX pris quand la bande accélère ou freine. 0 supprime l'inclinaison.
Sens d'une bande (HTML — data-dir="rtl" sur .vmq-root) Absent : défilement vers la gauche La bande part vers la droite ; l'impulsion du défilement est inversée elle aussi.
Contenu (HTML — enfants de .vmq-track) Six mots et six séparateurs par bande Mettez vos propres .vmq-item : texte, <a class="vmq-item" href="…">, logo <img> dans un élément. data-style="outline" affiche un mot en contour.
Taille et espacements (CSS — --vmq-size, --vmq-gap, --vmq-sep-gap sur .vmq-root) clamp(24px, 5vw, 48px), 28 px, 16 px Taille du texte, marge de part et d'autre de chaque mot et autour des séparateurs.
Couleurs (CSS — --vmq-color, --vmq-stroke, --vmq-accent) #f1f5f9, rgba(241,245,249,.45), #6366f1 Texte plein, contour des mots outline et séparateurs ; à poser sur la racine ou sur une bande.
Bouton Pause (HTML — .vmq-pause-btn) Présent Vous pouvez le retirer, mais gardez alors un autre moyen d'arrêter le mouvement pour les personnes au clavier (WCAG 2.2.2).

FAQ

Pourquoi la bande va-t-elle parfois à l'envers ?

Faire défiler la page vers le haut retire de la vitesse : un geste rapide dépasse la vitesse de base et la bande recule un instant, puis l'impulsion s'éteint (elle perd environ 20 % par image) et la bande reprend son sens normal.

Puis-je mettre des logos ou des liens dans la bande ?

Oui : tout enfant de .vmq-track est repris. Les liens restent cliquables, le focus clavier arrête la bande, et les copies créées pour la boucle sont retirées de l'ordre du clavier et des lecteurs d'écran. Pour des images, donnez-leur une hauteur fixe pour que la largeur mesurée au départ soit juste.

Mes polices se chargent tard : la boucle saute-t-elle ?

Non. Le script reconstruit les bandes quand document.fonts.ready est résolu et à chaque changement de largeur de la racine, pour que la longueur d'un tour corresponde aux mots réellement affichés.