Galeries, Carousels & Sliders
Carousel, lightbox, zoom, comparaison avant/après et marquee.
Mis à jour le
Faut-il une bibliothèque JavaScript pour un carrousel ou une galerie ?
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.
Voir le code source
<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>.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;
}(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.
Auto-play Slider
Défilement automatique avec barre de progression.
Thumbnail Slider
Navigation par miniatures sous le slider principal.
Card Carousel
Plusieurs cartes visibles, défilement horizontal.
3D Carousel
Carrousel en perspective 3D : cliquez pour faire tourner les faces.
Vertical Slider
Défilement vertical haut/bas.
Parallax Slider
Effet parallaxe sur l'arrière-plan des slides.
Infinite Loop
Défilement infini sans fin visible.
Basic Lightbox
Cliquez sur une image pour l'ouvrir dans un modal avec effet de fade.
Voir le code source
<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>.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);
}(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;
}
});
})();Zoom Lightbox
L'image s'agrandit depuis sa position de miniature.
Gallery Grid
Grille style masonry qui ouvre un lightbox.
Lightbox with Thumbnails
Bandeau de miniatures en bas du lightbox.
Fullscreen Lightbox
Mode plein écran véritable, Echap pour fermer.
Lightbox with Caption
Légende animée qui apparait sous l'image.
Swipe Gallery
Galerie tactile avec geste de balayage.
Hover Zoom
L'image s'agrandit au survol dans son conteneur.
Voir le code source
<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>.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;
}/* CSS pur — aucun JavaScript requis */Magnifying Glass
Loupe circulaire qui suit le curseur.
Lens Zoom
Loupe carrée qui agrandit la zone sous le curseur.
Pan Zoom
Déplacez la souris pour explorer l'image zoomee.
Click to Zoom
Cliquez pour alterner entre vue normale et zoomee.
Zoom with Overlay
Vue zoomee dans un panneau séparé.
Product Zoom
Style e-commerce avec miniatures et survol.
Smooth Ken Burns
Animation lente de zoom et déplacement continu.
Basic Marquee
Défilement horizontal simple et fluide.
Voir le code source
<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>.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%);
}
}/* CSS pur — aucun JavaScript requis */Reverse Marquee
Texte défilant dans la direction opposée.
Vertical Marquee
Défilement vertical pour annonces.
Image Marquee
Images défilantes horizontalement.
Speed Variation
Différentes vitesses de défilement.
Hover Pause
Pause au survol de la souris.
Gradient Fade
Fondu en gradient sur les bords.
Alternating Rows
Rangées alternant les directions.
Logo Ticker
Bandeau de logos de partenaires.
Marquee with Icons
Texte mélangé avec icônes et symboles.
CSS-Only Carousel
Carrousel natif sans une ligne de JavaScript : flèches et pastilles générées par le navigateur (::scroll-button / ::scroll-marker), scroll-snap fluide. Léger, accessible, robuste.
💡 Idéal pour : galerie produit, témoignages, logos partenaires, onboarding, hero slider — partout où on mettait 30 lignes de JS.
Voir le code source
<div class="demo-preview">
<div class="coc-wrap">
<div class="coc-carousel" tabindex="0" aria-label="Carrousel">
<div class="coc-slide" style="--g:linear-gradient(135deg,#6d8bff,#5cffd8)">01</div>
<div class="coc-slide" style="--g:linear-gradient(135deg,#ff7eb6,#a777ff)">02</div>
<div class="coc-slide" style="--g:linear-gradient(135deg,#ffb86b,#ff5e8a)">03</div>
<div class="coc-slide" style="--g:linear-gradient(135deg,#4dd2ff,#9b8cff)">04</div>
<div class="coc-slide" style="--g:linear-gradient(135deg,#5cffd8,#3ad6b3)">05</div>
</div>
</div>
</div>.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;
}
.coc-wrap {
position: relative;
}
.coc-carousel {
display: flex;
gap: 14px;
overflow-x: auto;
scroll-snap-type: x mandatory;
scroll-behavior: smooth;
padding: 6px;
border-radius: 14px;
scrollbar-width: none;
scroll-marker-group: after;
}
.coc-carousel::-webkit-scrollbar {
height: 0;
}
.coc-slide {
flex: 0 0 78%;
scroll-snap-align: center;
aspect-ratio: 16 / 9;
border-radius: 14px;
display: grid;
place-items: center;
color: #fff;
font-weight: 800;
font-size: 20px;
letter-spacing: -0.01em;
box-shadow: 0 12px 30px rgba(0, 0, 0, 0.3);
background: var(--g, linear-gradient(135deg, #6d8bff, #5cffd8));
}
.coc-slide::scroll-marker {
content: "";
width: 9px;
height: 9px;
margin: 0 4px;
border: 0;
border-radius: 50%;
background: rgba(255, 255, 255, 0.28);
cursor: pointer;
transition: background .2s, transform .2s;
}
.coc-slide::scroll-marker:target-current {
background: #5cffd8;
transform: scale(1.3);
}
.coc-slide::scroll-marker:hover {
background: rgba(255, 255, 255, 0.6);
}
.coc-carousel::scroll-marker-group {
display: flex;
justify-content: center;
align-items: center;
padding-top: 12px;
}
.coc-carousel::scroll-button(left),
.coc-carousel::scroll-button(right) {
position: absolute;
top: calc(50% - 28px);
transform: translateY(-50%);
z-index: 3;
width: 38px;
height: 38px;
border: 0;
border-radius: 50%;
cursor: pointer;
font-size: 18px;
background: rgba(10, 14, 31, 0.65);
color: #fff;
-webkit-backdrop-filter: blur(6px);
backdrop-filter: blur(6px);
}
.coc-carousel::scroll-button(left) {
left: 10px;
content: "‹";
}
.coc-carousel::scroll-button(right) {
right: 10px;
content: "›";
}
.coc-carousel::scroll-button(left):disabled,
.coc-carousel::scroll-button(right):disabled {
opacity: .35;
cursor: default;
}
@media (prefers-reduced-motion: reduce) {
.coc-carousel {
scroll-behavior: auto;
}
}/* CSS pur — aucun JavaScript requis */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.
Gallery Flow Morph
Galerie éparpillée en dérive parallaxe : au clic, la vignette se morphe en plein cadre via la View Transitions API, titre révélé lettre à lettre. Repli fondu, fermeture clavier.
💡 Idéal pour : portfolios, galeries photo, pages projets d'agence, moodboards interactifs.
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.
Voir le code source
<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>.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;
}
}(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.
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.
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.
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.