En général, non. Le CSS moderne couvre la plupart des galeries : scroll-snap pour des sliders que l'on fait glisser au doigt, CSS Grid pour des mises en page en mosaïque, des bandeaux défilants en @keyframes pour les logos et des lightbox simples. Quelques lignes de JavaScript natif ajoutent la lecture automatique, les vignettes, le zoom ou la comparaison avant/après, sans dépendance.

Comment choisir, performances et accessibilité
  • Choisissez CSS-Only Carousel ou Basic Slider pour des photos de produits ou de portfolio, une lightbox quand il faut voir la photo en grand, et Velocity Marquee pour un bandeau de logos ou de témoignages.
  • Chargez les images en différé avec une largeur et une hauteur explicites, rendez flèches et points accessibles au clavier, et arrêtez la lecture automatique dès que l'utilisateur interagit ou demande moins de mouvement.
  • Basic Slider, Basic Lightbox, Hover Zoom, Basic Marquee, CSS-Only Carousel et Velocity Marquee sont gratuits avec leur code ; les autres sont inclus dans Premium.

Basic Slider

Slider horizontal simple avec flèches de navigation.

CSS Transform Arrows
Voir le code source
HTML
<div class="demo-preview">
  <div class="carousel-container slider-basic" id="sliderBasic">
    <div class="carousel-track">
      <div class="carousel-slide">Slide 1</div>
      <div class="carousel-slide">Slide 2</div>
      <div class="carousel-slide">Slide 3</div>
      <div class="carousel-slide">Slide 4</div>
      <div class="carousel-slide">Slide 5</div>
    </div>
    <button class="carousel-arrow prev">
      <svg viewBox="0 0 24 24"><path d="M15 18l-6-6 6-6"/></svg>
    </button>
    <button class="carousel-arrow next">
      <svg viewBox="0 0 24 24"><path d="M9 18l6-6-6-6"/></svg>
    </button>
  </div>
</div>
CSS
.demo-preview,
.demo-preview *,
.demo-preview *::before,
.demo-preview *::after {
  box-sizing: border-box
}

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

/* Variables du design system du site, dont ce code dépend (valeurs de css/style.css — à adapter librement). */
.demo-preview {
  --bg-card: #12121a;
  --radius-xl: 1rem;
}

.carousel-container {
  width: 320px;
  height: 200px;
  position: relative;
  overflow: hidden;
  border-radius: var(--radius-xl);
  background: var(--bg-card);
}

.carousel-track {
  display: flex;
  height: 100%;
  transition: transform 0.5s cubic-bezier(0.25, 0.46, 0.45, 0.94);
}

.carousel-slide {
  min-width: 100%;
  height: 100%;
  display: flex;
  align-items: center;
  justify-content: center;
  font-weight: 600;
  font-size: 1.2rem;
  color: white;
}

.carousel-slide:nth-child(1) {
  background: linear-gradient(135deg, #6366f1, #8b5cf6);
}

.carousel-slide:nth-child(2) {
  background: linear-gradient(135deg, #8b5cf6, #d946ef);
}

.carousel-slide:nth-child(3) {
  background: linear-gradient(135deg, #d946ef, #f43f5e);
}

.carousel-slide:nth-child(4) {
  background: linear-gradient(135deg, #f43f5e, #f97316);
}

.carousel-slide:nth-child(5) {
  background: linear-gradient(135deg, #f97316, #eab308);
}

.carousel-arrow {
  position: absolute;
  top: 50%;
  transform: translateY(-50%);
  width: 36px;
  height: 36px;
  background: rgba(255, 255, 255, 0.2);
  backdrop-filter: blur(10px);
  border: 1px solid rgba(255, 255, 255, 0.3);
  border-radius: 50%;
  display: flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
  transition: all 0.3s ease;
  z-index: 10;
}

.carousel-arrow:hover {
  background: rgba(255, 255, 255, 0.4);
  transform: translateY(-50%) scale(1.1);
}

.carousel-arrow svg {
  width: 18px;
  height: 18px;
  stroke: white;
  stroke-width: 2;
  fill: none;
}

.carousel-arrow.prev {
  left: 10px;
}

.carousel-arrow.next {
  right: 10px;
}

.slider-basic .carousel-track {
  transition: transform 0.5s ease-out;
}
JavaScript
(function() {
  /* fx-0161-nav */
  /* Navigation du Basic Slider : les fleches etaient stylees mais
     cablees nulle part — ni ici, ni dans le code copie par le visiteur. */
  // Navigation du carrousel : le CSS anime .carousel-track, le JS pose la position.
  // querySelectorAll : plusieurs carrousels peuvent coexister sur une meme page.
  (function() {
    var roots = document.querySelectorAll('#sliderBasic, .slider-basic');
    Array.prototype.forEach.call(roots, function(root) {
      var track = root.querySelector('.carousel-track');
      var slides = root.querySelectorAll('.carousel-slide');
      var prev = root.querySelector('.carousel-arrow.prev');
      var next = root.querySelector('.carousel-arrow.next');
      if (!track || !slides.length) return;

      var index = 0;

      function aller(n) {
        index = (n + slides.length) % slides.length; // defilement circulaire
        track.style.transform = 'translateX(-' + (index * 100) + '%)';
      }
      if (prev) prev.addEventListener('click', function() {
        aller(index - 1);
      });
      if (next) next.addEventListener('click', function() {
        aller(index + 1);
      });

      // Fleches du clavier quand ce carrousel a le focus (sans gener la page hote)
      if (!root.hasAttribute('tabindex')) root.setAttribute('tabindex', '0');
      root.addEventListener('keydown', function(e) {
        if (e.key === 'ArrowLeft') {
          e.preventDefault();
          aller(index - 1);
        }
        if (e.key === 'ArrowRight') {
          e.preventDefault();
          aller(index + 1);
        }
      });

      aller(0);
    });
  })();
})();

Fade Slider

Transition en fondu entre les slides.

CSS Opacity Fade

Auto-play Slider

Défilement automatique avec barre de progression.

JS Timer Progress

Dots Navigation

Navigation par indicateurs circulaires.

CSS Dots Click

Thumbnail Slider

Navigation par miniatures sous le slider principal.

JS Thumbnails Gallery

Vertical Slider

Défilement vertical haut/bas.

CSS Vertical Y-axis

Parallax Slider

Effet parallaxe sur l'arrière-plan des slides.

CSS Parallax Depth

Infinite Loop

Défilement infini sans fin visible.

JS Infinite Touch

Basic Lightbox

Cliquez sur une image pour l'ouvrir dans un modal avec effet de fade.

JS Modal Fade
Voir le code source
HTML
<div class="demo-preview">
  <div class="gallery-grid" id="basicGallery">
    <div class="gallery-item gradient-1" data-index="0"></div>
    <div class="gallery-item gradient-2" data-index="1"></div>
    <div class="gallery-item gradient-3" data-index="2"></div>
    <div class="gallery-item gradient-4" data-index="3"></div>
  </div>
  <div class="lightbox-overlay" id="basicLightbox">
    <div class="lightbox-content">
      <button class="lightbox-close" aria-label="Fermer">
        <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
          <path d="M18 6L6 18M6 6l12 12"/>
        </svg>
      </button>
      <div class="lightbox-image gradient-1"></div>
    </div>
  </div>
</div>
CSS
.gallery-item  {
   width: 120px;
   height: 80px;
   border-radius: var(--radius-md);
   cursor: pointer;
   transition: transform 0.3s, box-shadow 0.3s;
   position: relative;
   overflow: hidden;
   }

.gallery-item:hover  {
   transform: scale(1.05);
   box-shadow: rgba(99, 102, 241, 0.3) 0px 10px 30px;
   }

.gradient-1  {
   background: linear-gradient(135deg, rgb(99, 102, 241), rgb(139, 92, 246));
   }

.gradient-2  {
   background: linear-gradient(135deg, rgb(217, 70, 239), rgb(244, 63, 94));
   }

.gradient-3  {
   background: linear-gradient(135deg, rgb(6, 182, 212), rgb(16, 185, 129));
   }

.gradient-4  {
   background: linear-gradient(135deg, rgb(245, 158, 11), rgb(239, 68, 68));
   }

.gallery-grid  {
   display: flex;
   flex-wrap: wrap;
   gap: 10px;
   justify-content: center;
   max-width: 280px;
   }

.masonry-grid .gallery-item  {
   width: 100%;
   break-inside: avoid;
   margin-bottom: 8px;
   }

.masonry-grid .gallery-item:nth-child(1)  {
   height: 60px;
   }

.masonry-grid .gallery-item:nth-child(2)  {
   height: 90px;
   }

.masonry-grid .gallery-item:nth-child(3)  {
   height: 70px;
   }

.masonry-grid .gallery-item:nth-child(4)  {
   height: 80px;
   }

.masonry-grid .gallery-item:nth-child(5)  {
   height: 50px;
   }

.masonry-grid .gallery-item:nth-child(6)  {
   height: 100px;
   }

/* Variables du design system du site, dont ce code depend. Sans elles,
   l'acheteur obtenait des coins carres et des couleurs par defaut (revue Owen).
   Valeurs reprises de css/style.css — a adapter librement. */
.gallery-grid {
  --radius-md: 0.5rem;
}

/* styles restaurés (1b523df9~1) */
.demo-preview {
  --radius-sm: 0.375rem;
  --radius-lg: 0.75rem;
  --radius-xl: 1rem;
  --text-muted: #8b8b93;
}

/* Lightbox Overlay Base */
.lightbox-overlay {
  position: fixed;
  inset: 0;
  background: rgba(0, 0, 0, 0.9);
  display: flex;
  align-items: center;
  justify-content: center;
  z-index: 1000;
  opacity: 0;
  visibility: hidden;
  transition: opacity 0.4s ease, visibility 0.4s ease;
  backdrop-filter: blur(10px);
}
.lightbox-overlay.active {
  opacity: 1;
  visibility: visible;
}

/* Lightbox Content */
.lightbox-content {
  max-width: 80vw;
  max-height: 80vh;
  border-radius: var(--radius-xl);
  transform: scale(0.8);
  transition: transform 0.4s cubic-bezier(0.175, 0.885, 0.32, 1.275);
  position: relative;
}
.lightbox-overlay.active .lightbox-content {
  transform: scale(1);
}

/* Lightbox Image Placeholder */
.lightbox-image {
  width: 600px;
  height: 400px;
  max-width: 80vw;
  max-height: 70vh;
  border-radius: var(--radius-xl);
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 1.5rem;
  font-weight: 600;
  color: white;
}

/* Close Button */
.lightbox-close {
  position: absolute;
  top: -40px;
  right: 0;
  width: 36px;
  height: 36px;
  background: rgba(255, 255, 255, 0.1);
  border: none;
  border-radius: 50%;
  color: white;
  cursor: pointer;
  display: flex;
  align-items: center;
  justify-content: center;
  transition: background 0.3s ease, transform 0.3s ease;
}
.lightbox-close:hover {
  background: rgba(255, 255, 255, 0.2);
  transform: rotate(90deg);
}
JavaScript
(function() {
  // ==========================================
  // 1. Basic Lightbox
  // ==========================================
  (function() {
  const basicGallery = document.getElementById('basicGallery');
  const basicLightbox = document.getElementById('basicLightbox');
  if (!basicGallery || !basicLightbox) return;
  const basicImage = basicLightbox.querySelector('.lightbox-image');

  basicGallery.querySelectorAll('.gallery-item').forEach(item => {
    item.addEventListener('click', () => {
      const gradient = Array.from(item.classList).find(c => c.startsWith('gradient-'));
      basicImage.className = 'lightbox-image ' + gradient;
      basicLightbox.classList.add('active');
    });
  });

  basicLightbox.querySelector('.lightbox-close').addEventListener('click', () => {
    basicLightbox.classList.remove('active');
  });
  basicLightbox.addEventListener('click', (e) => {
    if (e.target === basicLightbox) basicLightbox.classList.remove('active');
  });
  })();

  // ==========================================
  // Global Keyboard Navigation
  // ==========================================
  document.addEventListener('keydown', (e) => {
    // Find which lightbox is active
    const activeLightbox = document.querySelector('.lightbox-overlay.active');
    if (!activeLightbox) return;

    switch (e.key) {
      case 'Escape':
        activeLightbox.classList.remove('active');
        if (document.exitFullscreen && document.fullscreenElement) {
          document.exitFullscreen();
        }
        break;
      case 'ArrowLeft':
        const prevBtn = activeLightbox.querySelector('.lightbox-nav.prev');
        if (prevBtn) prevBtn.click();
        break;
      case 'ArrowRight':
        const nextBtn = activeLightbox.querySelector('.lightbox-nav.next');
        if (nextBtn) nextBtn.click();
        break;
    }
  });
})();

Lightbox with Navigation

Flèches précédent/suivant et navigation clavier.

JS Navigation Keyboard

Zoom Lightbox

L'image s'agrandit depuis sa position de miniature.

JS Zoom Transform

Lightbox with Thumbnails

Bandeau de miniatures en bas du lightbox.

JS Thumbnails Strip

Fullscreen Lightbox

Mode plein écran véritable, Echap pour fermer.

JS Fullscreen Immersive

Lightbox with Caption

Légende animée qui apparait sous l'image.

JS Caption Animated
Hover Me

Hover Zoom

L'image s'agrandit au survol dans son conteneur.

CSS Transform
Voir le code source
HTML
<div class="demo-preview">
  <div class="zoom-container hover-zoom">
    <div class="zoom-image grid-pattern">
      <span class="zoom-label">Hover Me</span>
    </div>
  </div>
</div>
CSS
.demo-preview,
.demo-preview *,
.demo-preview *::before,
.demo-preview *::after {
  box-sizing: border-box
}

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

/* Variables du design system du site, dont ce code dépend (valeurs de css/style.css — à adapter librement). */
.demo-preview {
  --bg-card: #12121a;
  --border: rgba(255, 255, 255, 0.08);
  --radius-lg: 0.75rem;
}

.zoom-container {
  width: 280px;
  height: 200px;
  border-radius: var(--radius-lg);
  overflow: hidden;
  position: relative;
  cursor: pointer;
  background: var(--bg-card);
  border: 1px solid var(--border);
}

.zoom-image {
  width: 100%;
  height: 100%;
  background:
    repeating-linear-gradient(45deg,
      transparent,
      transparent 10px,
      rgba(99, 102, 241, 0.1) 10px,
      rgba(99, 102, 241, 0.1) 20px),
    linear-gradient(135deg, #667eea 0%, #764ba2 50%, #f093fb 100%);
  background-size: 100% 100%;
  display: flex;
  align-items: center;
  justify-content: center;
  color: white;
  font-weight: 600;
  font-size: 1.2rem;
  text-shadow: 0 2px 4px rgba(0, 0, 0, 0.3);
  position: relative;
}

.zoom-image::before {
  content: '';
  position: absolute;
  inset: 0;
  background:
    radial-gradient(circle at 30% 30%, rgba(255, 255, 255, 0.3) 0%, transparent 50%),
    radial-gradient(circle at 70% 70%, rgba(0, 0, 0, 0.2) 0%, transparent 50%);
}

.grid-pattern {
  background-image:
    linear-gradient(rgba(255, 255, 255, 0.1) 1px, transparent 1px),
    linear-gradient(90deg, rgba(255, 255, 255, 0.1) 1px, transparent 1px),
    repeating-linear-gradient(45deg,
      transparent,
      transparent 10px,
      rgba(99, 102, 241, 0.1) 10px,
      rgba(99, 102, 241, 0.1) 20px),
    linear-gradient(135deg, #667eea 0%, #764ba2 50%, #f093fb 100%);
  background-size: 20px 20px, 20px 20px, 100% 100%, 100% 100%;
}

.hover-zoom .zoom-image {
  transition: transform 0.5s cubic-bezier(0.25, 0.46, 0.45, 0.94);
}

.hover-zoom:hover .zoom-image {
  transform: scale(1.3);
}

.demo-preview:has(.zoom-container) {
  min-height: 240px;
}

.zoom-label {
  position: relative;
  z-index: 2;
}
JavaScript
/* CSS pur — aucun JavaScript requis */

Magnifying Glass

Loupe circulaire qui suit le curseur.

JS Background

Lens Zoom

Loupe carrée qui agrandit la zone sous le curseur.

JS Clip-path

Pan Zoom

Déplacez la souris pour explorer l'image zoomee.

JS Transform-origin

Click to Zoom

Cliquez pour alterner entre vue normale et zoomee.

JS Toggle

Zoom with Overlay

Vue zoomee dans un panneau séparé.

JS Overlay

Product Zoom

Style e-commerce avec miniatures et survol.

JS E-commerce

Smooth Ken Burns

Animation lente de zoom et déplacement continu.

CSS Animation
Welcome to Effect.Labs Premium Web Effects Modern CSS Animations Infinite Scrolling
Welcome to Effect.Labs Premium Web Effects Modern CSS Animations Infinite Scrolling

Basic Marquee

Défilement horizontal simple et fluide.

CSS Minimal
Voir le code source
HTML
<div class="demo-preview">
  <div class="marquee-container marquee-basic">
    <div class="marquee-track">
      <div class="marquee-content">
        <span>Welcome to Effect.Labs</span>
        <span>Premium Web Effects</span>
        <span>Modern CSS Animations</span>
        <span>Infinite Scrolling</span>
      </div>
      <div class="marquee-content">
        <span>Welcome to Effect.Labs</span>
        <span>Premium Web Effects</span>
        <span>Modern CSS Animations</span>
        <span>Infinite Scrolling</span>
      </div>
    </div>
  </div>
</div>
CSS
.demo-preview,
.demo-preview *,
.demo-preview *::before,
.demo-preview *::after {
  box-sizing: border-box
}

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

.marquee-container {
  overflow: hidden;
  width: 100%;
  position: relative;
}

.marquee-track {
  display: flex;
  width: max-content;
  will-change: transform;
}

.marquee-content {
  display: flex;
  align-items: center;
  flex-shrink: 0;
}

.marquee-basic .marquee-track {
  animation: marqueeScroll 15s linear infinite;
}

.marquee-basic .marquee-content {
  padding-right: 4rem;
  font-size: 1.5rem;
  font-weight: 600;
  color: #6366f1;
}

.marquee-basic .marquee-content span {
  margin: 0 2rem;
  white-space: nowrap;
}

.demo-preview:has(.marquee-container),
.demo-preview:has(.marquee-vertical),
.demo-preview:has(.marquee-alternating),
.demo-preview:has(.marquee-speed-container) {
  min-height: 180px;
  padding: 1rem 0;
}

.demo-preview .marquee-container,
.demo-preview .marquee-speed-container,
.demo-preview .marquee-alternating,
.demo-preview .marquee-vertical {
  width: 100%;
}

@keyframes marqueeScroll {
  0% {
    transform: translateX(0);
  }

  100% {
    transform: translateX(-50%);
  }
}
JavaScript
/* CSS pur — aucun JavaScript requis */

Reverse Marquee

Texte défilant dans la direction opposée.

CSS Reverse

Vertical Marquee

Défilement vertical pour annonces.

CSS Vertical

Image Marquee

Images défilantes horizontalement.

CSS Images

Speed Variation

Différentes vitesses de défilement.

CSS Speed

Hover Pause

Pause au survol de la souris.

CSS Interactive

Gradient Fade

Fondu en gradient sur les bords.

CSS Gradient

Alternating Rows

Rangées alternant les directions.

CSS Multi-row

Logo Ticker

Bandeau de logos de partenaires.

CSS Logos

Marquee with Icons

Texte mélangé avec icônes et symboles.

CSS Icons

WebGL Displacement Slideshow

Diaporama dont les transitions sont calculées par un shader de displacement GPU : les visuels se déforment et fondent l'un dans l'autre. Repli crossfade CSS si WebGL absent.

💡 Idéal pour : hero visuel, galerie produit premium, showreel agence, portfolio photo, page d'accueil immersive.

WebGL Shader Slideshow Premium
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

Velocity Marquee

Bande infinie sans raccord visible : le contenu est dupliqué autant que nécessaire, la position est accumulée en JS (rAF) et repliée modulo la largeur d'un jeu — jamais de saut. Le scroll de la page et le mouvement horizontal du curseur accélèrent la bande avec inertie et inclinent le texte (skewX clampé). En mode prefers-reduced-motion, défilement lent et constant sans inclinaison.

💡 Idéal pour : bande logo partenaires, section « Ils nous font confiance », ticker presse, héro agence créa.

Scroll Physics Seamless Loop Pointer Velocity rAF + lerp skewX
Voir le code source
HTML
<div class="demo-preview">
  <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"
           aria-label="Seamless · Infinite · Velocity · Marquee"
           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" aria-label="Marquee Velocity Effect : NEW DROP · EFFECT.LABS · VANILLA JS · NO FRAMEWORK · OPEN SOURCE"
           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"
           aria-label="Inertia · skewX · mask-image · rAF · lerp"
           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" id="vmqPauseBtn"
            aria-label="Mettre en pause le défilement"
            aria-pressed="false"
            type="button">
      <span aria-hidden="true">⏸</span>
      <span>Pause</span>
    </button>
  </div>
</div>
CSS
.demo-preview,
.demo-preview *,
.demo-preview *::before,
.demo-preview *::after {
  box-sizing: border-box
}

.demo-preview {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 100%;
  height: 250px;
  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;
  pointer-events: 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;
}

@media (prefers-reduced-motion: reduce) {
  .vmq-track {

    transition: none !important;
  }

  .vmq-pause-btn {
    transition: none;
  }
}
JavaScript
(function() {
  /* fx-0831 */
  (function() {
    'use strict';

    /* --- constantes --- */
    var BASE_SPEED = 0.85;
    var SCROLL_K = 0.06;
    var MOUSE_K = 0.028;
    var BREATHE_K = 0.10;
    var LERP_VEL = 0.07;
    var LERP_SKEW = 0.09;
    var SKEW_K = 0.28;
    var SKEW_MAX = 7;

    /* --- état global --- */
    var vel = BASE_SPEED;
    var skew = 0;
    var tSkew = 0;
    var scrollBonus = 0;
    var mouseBonus = 0;
    var hoverCount = 0;
    var paused = false;
    var rafId = null;
    var time = 0;
    var lastScrollY = 0;
    var lastMouseX = null;
    var reduced = false;
    var roots = [];
    var resizeTimer = null;

    /* --- initialisation --- */
    function init() {
      lastScrollY = window.scrollY || 0;
      var mq = window.matchMedia('(prefers-reduced-motion: reduce)');
      reduced = mq.matches;
      mq.addEventListener('change', function(e) {
        reduced = e.matches;
        if (reduced) {
          skew = 0;
          tSkew = 0;
        }
      });

      roots = Array.prototype.slice.call(document.querySelectorAll('.vmq-root'));
      roots.forEach(setup);

      window.addEventListener('scroll', onScroll, {
        passive: true
      });
      window.addEventListener('mousemove', onMouse, {
        passive: true
      });
      window.addEventListener('resize', onResize);

      rafId = requestAnimationFrame(tick);
    }

    /* --- setup d'une racine --- */
    function setup(root) {
      var track = root.querySelector('.vmq-track');
      if (!track) return;

      var origItems = Array.prototype.slice.call(track.children);
      var origCount = origItems.length;

      function dup() {
        while (track.children.length > origCount) {
          track.removeChild(track.lastChild);
        }
        var cw = root.getBoundingClientRect().width || 480;
        var sw = track.scrollWidth || 200;
        var copies = Math.ceil(2 * cw / sw) + 2;
        for (var i = 0; i < copies; i++) {
          origItems.forEach(function(item) {
            track.appendChild(item.cloneNode(true));
          });
        }
        root._vmq = root._vmq || {};
        root._vmq.track = track;
        root._vmq.setW = sw;
        root._vmq.pos = root._vmq.pos || 0;
        root._vmq.rtl = root.dataset.dir === 'rtl';
        root._vmq.dup = dup;
      }
      dup();

      root.addEventListener('mouseenter', function() {
        hoverCount++;
      });
      root.addEventListener('mouseleave', function() {
        hoverCount = Math.max(0, hoverCount - 1);
      });
      root.addEventListener('focusin', function() {
        hoverCount++;
      });
      root.addEventListener('focusout', function() {
        hoverCount = Math.max(0, hoverCount - 1);
      });

      if (window.IntersectionObserver) {
        new IntersectionObserver(function(entries) {
          root._vmq.visible = entries[0].isIntersecting;
        }, {
          threshold: 0
        }).observe(root);
      }
      root._vmq.visible = true;
    }

    /* --- scroll / mouse --- */
    function onScroll() {
      var dy = (window.scrollY || 0) - lastScrollY;
      lastScrollY = window.scrollY || 0;
      scrollBonus = dy * SCROLL_K;
    }

    function onMouse(e) {
      if (lastMouseX !== null) {
        mouseBonus = (e.clientX - lastMouseX) * MOUSE_K;
      }
      lastMouseX = e.clientX;
    }

    function onResize() {
      clearTimeout(resizeTimer);
      resizeTimer = setTimeout(function() {
        roots.forEach(function(root) {
          if (root._vmq && root._vmq.dup) root._vmq.dup();
        });
      }, 140);
    }

    /* --- boucle principale --- */
    function tick() {
      rafId = requestAnimationFrame(tick);
      if (paused) return;

      time += 0.009;
      var breathe = reduced ? 0 : Math.sin(time) * BREATHE_K;

      var tVel;
      if (reduced) {
        tVel = BASE_SPEED * 0.22;
      } else if (hoverCount > 0) {
        tVel = BASE_SPEED * 0.25;
      } else {
        tVel = BASE_SPEED + scrollBonus + mouseBonus + breathe;
      }

      vel += (tVel - vel) * LERP_VEL;
      scrollBonus *= 0.80;
      mouseBonus *= 0.84;

      if (reduced || hoverCount > 0) {
        tSkew = 0;
      } else {
        tSkew = -(vel - BASE_SPEED) * SKEW_K;
        if (tSkew > SKEW_MAX) tSkew = SKEW_MAX;
        if (tSkew < -SKEW_MAX) tSkew = -SKEW_MAX;
      }
      skew += (tSkew - skew) * LERP_SKEW;

      roots.forEach(function(root) {
        if (!root._vmq || !root._vmq.visible) return;
        var sw = root._vmq.setW;
        if (!sw || sw < 1) return;
        var step = root._vmq.rtl ? -vel : vel;
        root._vmq.pos = ((root._vmq.pos + step) % sw + sw) % sw;
        var sk = root._vmq.rtl ? -skew : skew;
        root._vmq.track.style.transform =
          'translateX(-' + root._vmq.pos.toFixed(2) + 'px) skewX(' + sk.toFixed(3) + 'deg)';
      });
    }

    /* --- API publique --- */
    window.VelocityMarquee = {
      pause: function() {
        paused = true;
      },
      play: function() {
        paused = false;
      },
      toggle: function() {
        paused = !paused;
        return paused;
      },
      setSpeed: function(v) {
        BASE_SPEED = Math.max(0.1, Math.min(5, Number(v) || BASE_SPEED));
      },
      setSkewMax: function(v) {
        SKEW_MAX = Math.max(0, Math.min(20, Number(v) || SKEW_MAX));
      }
    };

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

  })();
})();

Cylinder Marquee 3D

Un cylindre 3D CSS pur : N facettes positionnées en rotateY + translateZ, rayon calculé automatiquement. Rotation continue par rAF, drag inertiel à la souris/tactile, atténuation cosinus des faces arrière. Repli élégant si le navigateur ne supporte pas transform-style: preserve-3d.

💡 Idéal pour : hall of fame de témoignages, défilé de technologies maîtrisées, showcase de clients/partenaires, section « Skills » de portfolio.

CSS 3D preserve-3d Drag inertiel rAF + lerp Pointer Capture Premium

Text-Path Marquee

Texte défilant sans raccord sur 3 chemins sinusoïdaux SVG natifs — pas de canvas, aucun framework. L'onde pulse en amplitude et dérive en phase ; la souris crée une bosse gaussienne locale qui déforme le chemin en temps réel (lerp). Alternance plein / contour pour la profondeur. Repli prefers-reduced-motion : chemins figés, lecture statique.

💡 Idéal pour : hero animé de landing créative, titres de section « wavy », ticker de lancement produit, badge de scène de concert ou de festival.

SVG textPath Wave Path rAF + lerp Pointer Deform WCAG 2.2.2 Premium

Live Ticker

Bandeau de cotations en défilement continu avec recyclage DOM pur : aucune duplication de nœuds, aucun saut. L'API LiveTicker.push() injecte de nouvelles données à la volée avec flash vert/rouge, le drag inertiel et le bouton pause rendent le composant entièrement interactif. Repli prefers-reduced-motion : liste statique défilable au doigt ou à la molette.

💡 Idéal pour : barre de cotations boursières, fil d'annonces SaaS (changelog, alertes système, nouveautés produit), notifications live, breaking news d'un portail média.

DOM Recycling rAF + Inertie Pointer Events Live API push Flash delta Premium

Lens Marquee

Deux couches de texte défilent à l'identique : une couche outline monochrome toujours visible, et une couche dégradée révélée uniquement à travers une lentille circulaire lumineuse qui suit le curseur (CSS mask-image + custom props JS). Sans pointeur, la lentille balaie automatiquement les trois rangées. Repli immédiat si mask-image non supporté.

💡 Idéal pour : hero d'agence créative, section stack technique, landing premium, bannière de lancement produit.

CSS Mask Dual Layer rAF + lerp Pointer Tracking Auto-oscillation WCAG 2.2.2 Premium

Questions fréquentes

Comment créer un carousel infini en CSS ? +

Dupliquez la liste d'items. Appliquez animation: scroll Xs linear infinite. Quand l'animation atteint la fin de la première liste, elle recommence imperceptiblement car la seconde liste est identique. Utilisez overflow:hidden sur le conteneur.

Comment faire un slider responsive sans librairie ? +

Utilisez CSS scroll-snap : overflow-x:auto, scroll-snap-type:x mandatory sur le conteneur, scroll-snap-align:start sur chaque slide. Les boutons flèches utilisent scrollBy() en JavaScript. Zéro dépendance.

Quel est le meilleur carousel pour des images ? +

Pour des images plein écran, utilisez un slider avec transition transform. Pour des miniatures, un carousel avec scroll-snap + navigation par vignettes. Pour un portfolio, une grille masonry avec lightbox au clic.

Comment optimiser les performances d'un carousel ? +

Chargez les images en lazy loading (loading='lazy'). Pré-chargez seulement la slide suivante. Utilisez des images au bon format (WebP) et à la bonne taille. Pour les marquées, utilisez will-change:transform.

Comment rendre un carousel accessible ? +

Ajoutez role='region' et aria-label sur le conteneur. Les boutons flèches ont aria-label='Précédent'/'Suivant'. Chaque slide a role='group' et aria-roledescription='slide'. Supportez la navigation clavier avec les flèches.