Scroll✨ Premium

3D Depth Gallery

Cinq photos posées tous les 5 unités devant une caméra Three.js qui plonge de 20 unités en 2 000 crans de molette (ou 909 px de glisser) ; chaque image a son décalage x et sa couleur de fond, fondus à 8 % et 14 % par image. Three.js r128 est chargé depuis un CDN par le HTML vendu ; les cinq photos sont des exemples Unsplash, à remplacer par les vôtres.

Vous aurez accès à la démo interactive avec un compte gratuit.

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

3 exemples d'usage

Galerie de collection haute couture — chaque pièce dans sa lumière — 3D Depth Gallery exemple 1

① Galerie de collection haute couture — chaque pièce dans sa lumière

QuandPage collection d'une maison de couture : les pièces de la saison, une par écran, que l'on traverse à la molette ; le nom de la pièce et sa matière en légende.
PourquoiLe tableau bg[] donne à chaque photo son atmosphère — ivoire pour une soie, prune pour un velours, chartreuse pour un crêpe — sans retouche ni photomontage : le fond de scène se fond d'une teinte à l'autre pendant la plongée. Le décalage x propre à chaque pièce (−0,9 à +1,0) la fait glisser à gauche ou à droite selon sa composition. fx-0795 (Pinned Image Sequence) fait tourner un objet 3D ; ici c'est une photo 2D éditoriale que la caméra approche.
RéglagesCinq entrées GALLERIES.couture avec bg = teinte dominante de la pièce ; data-gallery="couture" sur la zone ; compteur et légende sur une pastille sombre (le blanc ne se lit pas sur l'ivoire) ; SCROLL_SMOOTH abaissé à 0,05 pour une plongée plus lente.
Portefeuille de reportages photographiques — la profondeur par le décalage — 3D Depth Gallery exemple 2

② Portefeuille de reportages photographiques — la profondeur par le décalage

QuandPage d'accueil d'une agence de presse : les reportages phares en plein cadre, un titre et le nombre d'images par sujet, chaque reportage cadré légèrement à gauche ou à droite.
PourquoiLe décalage x négatif ou positif de chaque plan, projeté en x / distance, fait glisser la photo latéralement quand la caméra approche — une profondeur de champ simulée, sans calque ni masque, sur de vraies photos. fx-0592 (Layered Parallax) empile des formes abstraites en CSS ; fx-0782 déplace des photographies avec un fond de scène qui prend leur dominante.
RéglagesPhotos 3:2 à la place des carrés du code vendu (le plan prend le ratio de l'image : 4,5 × 3 unités), SIZE 3,4 pour remplir davantage la hauteur, x alternés −1,1 / 0,9 / −0,8 / 1,0 / −0,9, bg = dominante du reportage, VEL_MAX 2 pour un éclair de fond plus franc au défilement rapide.
Vitrine de recherche en astrophysique — le fond prend la couleur du ciel — 3D Depth Gallery exemple 3

③ Vitrine de recherche en astrophysique — le fond prend la couleur du ciel

QuandPage « images » d'un observatoire : nébuleuses et galaxies, un objet par écran, sa désignation et le mode de prise de vue en légende.
PourquoiLe fond de scène reprend la dominante de chaque cliché — magenta d'une nébuleuse en Hα, bleu d'une galaxie — et le visiteur est plongé dans l'atmosphère chromatique de l'objet avant même que la photo remplisse le cadre. fx-0566 (Scroll Fade Gallery) fait défiler des photos en fondu CSS dans une colonne, sans fond adaptatif ni décalage latéral, et ne fonctionne pas sur Firefox.
Réglagesbg[] sombres et saturés (luminance basse : HUD blanc au-delà de 9:1), GAP 6 pour un ciel plus profond entre deux objets, PARALLAX_X 0,22 pour accentuer le flottement sous le pointeur, FADE_SMOOTH 0,1 pour un fondu plus long.

Comment ça marche

Le HTML charge d'abord Three.js r128 depuis cdn.jsdelivr.net (build classique three.min.js, 600 Ko, qui expose la variable globale THREE), puis pose une scène .dg-stage (fond #0a0a0f, min-height: 100vh, contenu centré — à remplacer par votre mise en page) qui contient la zone .dg-scene (aspect-ratio: 16/10, overflow: hidden, touch-action: none, fond #0b1024, police Inter ou système) contenant un canvas.dg-canvas plein cadre, trois blocs HUD en pointer-events: none — compteur 1 / 5 (12 px, en haut à droite), légende h3 18 px + p 12 px (en bas à gauche), indication « Molette / glisser pour explorer » dans une pastille rgba(0,0,0,.45) — et un texte de repli. Le script vérifie d'abord typeof THREE : si la bibliothèque n'est pas arrivée (CDN bloqué, hors ligne, fichier chargé après l'effet), ou si WebGLRenderer échoue, la classe dg-nogl masque canvas et HUD et affiche le repli. Sinon : rendu antialias, pixelRatio plafonné à 2, setSize(W, H, false) (le canvas garde sa taille CSS), caméra perspective 45° en z = 5, et cinq PlaneGeometry(3, 3) en MeshBasicMaterial gris 0x888888, transparents, depthWrite: false, posés en (item.x, 0, −idx × GAP) : x = −0,9 / 0,8 / −0,7 / 1,0 / −0,7, z = 0 à −20. Un TextureLoader (crossOrigin: 'anonymous') remplace le gris par la photo à l'arrivée et le plan prend le ratio de l'image ; seul le premier est opaque, le fond de scène prend bg[0]. Les cinq src sont des photos Unsplash carrées de 1 200 px (images.unsplash.com, servies avec Access-Control-Allow-Origin: *) — des exemples à remplacer par vos images. Le gris 0x888888 est l'état d'attente de chaque plan : il persiste si une image n'arrive pas, et notamment si elle est hébergée sur un autre domaine que la page sans autoriser le CORS, condition que WebGL impose à toute texture.

Le défilement ne bouge pas la page : wheel sur la zone (preventDefault, deltaMode 1 multiplié par 16) et glisser (pointerdown/pointermove, 2,2 unités par pixel) alimentent scrollTarget, borné de 0 à maxScroll = (5 − (−20 + 5)) / 0,01 = 2 000. À chaque image, scrollCur comble 8 % de l'écart (SCROLL_SMOOTH, 95 % en 36 images), la caméra descend en z = 5 − scrollCur × 0,01, de 5 à −15, et l'index continu nd = (5 − camZ) / 5 va de 0 à 4 : 500 crans par photo. L'image ci reçoit l'opacité 1 − blend, la suivante blend, les autres 0, chaque opacité lissée à 14 % (FADE_SMOOTH) ; le fond three.background est l'interpolation linéaire de bg[ci] vers bg[ni]. Compteur et légende basculent à blend ≥ 0,5, l'indication disparaît dès scrollCur > 30. Mesuré par pas de 100 : légende 2 / 5 à 300, 3 / 5 à 800, 4 / 5 à 1 300, 5 / 5 à 1 800, rendu identique au-delà de 2 000 ; un glisser de 250 px (550 unités) mène à 2 / 5 ; le coin de la zone vaut exactement bg[] à chaque palier — (255, 250, 240) puis (60, 70, 120), (95, 129, 171), (40, 80, 150), (70, 95, 70). À 5 unités de la caméra, un plan de 3 unités couvre 72 % de la hauteur (hauteur visible 2 × 5 × tan 22,5° = 4,14) ; il n'en faisait que 36 % à 10 unités quand il est apparu, et grossit jusqu'à sortir du cadre pendant qu'il s'efface — c'est la plongée. Son décalage x se projette en x / distance : 0,9 unité vaut 101 px de désaxement dans une zone de 744 × 465 px à 5 unités, 202 px à 2,5 unités — la photo glisse latéralement en approchant, d'un côté ou de l'autre selon le signe.

Deux couches par-dessus. Le pointeur : pointermove normalise la position en ±1 (ptrTarget), lissée à 8 % ; chaque plan est déplacé de ptr.x × 0,16 × opacité × (1 + idx × 0,05) unités en x et ptr.y × 0,08 × … en y — ±18 px et ±9 px à 5 unités dans 744 × 465. Mesuré au repos, en 744 × 465 : la photo se décale de 35 px entre pointeur à gauche et pointeur à droite (36 px calculés). La vitesse : vel suit scrollCur − prevScroll à 12 %, bornée à ±1,5 unité par image ; velNorm éclaircit les trois canaux du fond de + velNorm × 0,08 et nourrit breath (lissé à 14 %) qui incline chaque plan de ±0,045 rad (2,6°) selon le pointeur et le gonfle de 3 % — un « souffle » visible seulement pendant le mouvement. La boucle requestAnimationFrame ne s'arrête jamais : 120 images par seconde mesurées au repos, sans IntersectionObserver, sans cancelAnimationFrame ; resize et pointerup sont posés sur window et jamais retirés, aucun dispose(). Le JS tient dans une seule IIFE sans variable globale : il initialise chaque .dg-scene de la page et lit sa galerie dans data-gallery ; la police du HUD est déclarée sur .dg-scene, aucune règle ne sort de l'effet.

Accessibilité

  • prefers-reduced-motion : géré à moitié. Lu une fois au chargement, il met à 0 PARALLAX_X/Y, BREATH_TILT et BREATH_SCALE — mesuré : 0 px de décalage de la photo entre pointeur à gauche et à droite, contre 35 px sans la préférence. Mais la plongée, le fondu et le morphing du fond restent (2 / 5 atteint après 600 crans sous la préférence), avec leur inertie de 36 images. Le geste est volontaire, donc admissible ; pour aller plus loin, passez SCROLL_SMOOTH et FADE_SMOOTH à 1 sous la préférence : la photo change sans traînée.
  • Contraste du HUD sur la première photo : 1,04:1. Compteur et légende sont blancs avec une ombre de 6 à 8 px ; sur bg[0] = (255, 250, 240) (ivoire), ils sont invisibles — vérifié sur la capture, « Golden / PMS 135 C » et « 1 / 5 » ne se lisent pas. La pastille d'indication (rgba(0,0,0,.45)) tient à 3,45:1. Sur les quatre autres fonds : 9,0:1, 4,0:1, 7,8:1, 7,0:1. Posez compteur et légende sur une pastille sombre comme l'indication, ou n'utilisez que des bg[] de luminance moyenne ou basse.
  • Aucune commande clavier : pas d'écouteur keydown, rien de focalisable, les blocs HUD sont en pointer-events: none. Ajoutez tabindex="0" et role="group" à .dg-scene avec un aria-label, et sur keydown ajoutez ±500 (une photo) à scrollTarget pour Flèche bas/haut, 0 et 2 000 pour Début/Fin. Ne mettez pas aria-live sur le compteur tel quel : son textContent est réécrit à chaque image, même inchangé — comparez avant d'écrire.
  • La zone confisque le défilement de la page : wheel est toujours annulé, même en fin de course (mesuré : defaultPrevented vrai à 0 et à 2 000, scrollY figé pendant 2 400 crans), et touch-action: none fait de même au doigt. Sur une page longue, un visiteur dont le pointeur est sur la galerie ne peut plus descendre. Le canvas n'a ni aria-hidden ni role ; les photos ne portent aucun texte alternatif (seuls titre et sous-titre sont dans le DOM). Relâchez l'événement aux bornes (voir FAQ) et décrivez chaque photo dans la légende ou un aria-label mis à jour.

Compatibilité navigateur

Requiert la variable globale THREE : le HTML vendu charge Three.js r128 (build classique three.min.js, 600 Ko) depuis cdn.jsdelivr.net avant le code de l'effet — gardez cette version, l'effet est écrit pour son API et les versions récentes ne sont plus publiées qu'en modules ES. Requiert aussi un contexte WebGL 1, les pointer events, aspect-ratio et inset en CSS ; JavaScript ES5. Piloté par wheel et pointermove, pas par animation-timeline : il fonctionne sur Firefox, contrairement à Scroll Fade Gallery (fx-0566, animation-timeline: view()) et Layered Parallax (fx-0592, animation-timeline: scroll()) que Firefox ne rend dans aucune version — le code convertit d'ailleurs le deltaMode 1 (molette en lignes, Firefox) par ×16.

Chrome 88+✓ Complet
Firefox 89+✓ Complet
Safari 15+✓ Complet
Edge 88+✓ Complet
Mobile iOS 15+✓ Complet (glisser ; la zone bloque le défilement de la page)
Android Chrome✓ Complet (glisser ; la zone bloque le défilement de la page)

Si Three.js n'a pas pu se charger (CDN bloqué, hors ligne) ou sans WebGL, la classe dg-nogl masque canvas et HUD et affiche « Galerie 3D — WebGL requis. Voici la première image. » sur un dégradé radial — mais aucune image n'est affichée (mesuré : canvas en display: none, pas d'img) ; le texte promet ce qu'il ne montre pas, ajoutez un img de la première photo dans .dg-fallback. Sans aspect-ratio (Safari 14, Firefox 88), la zone n'a pas de hauteur. Une photo qui n'arrive pas (URL morte, domaine sans CORS) laisse son plan gris ; les autres s'affichent.

Le code

Structure HTML à coller dans votre page (CSS + JS disponibles avec un compte premium) :

index.html — structure
<!-- Three.js r128 (build classique, variable globale THREE), chargé avant le code de l'effet -->
<script src="https://cdn.jsdelivr.net/npm/three@0.128.0/build/three.min.js" integrity="sha384-CI3ELBVUz9XQO+97x6nwMDPosPR5XvsxW2ua7N1Xeygeh1IxtgqtCkGfQY9WWdHu" crossorigin="anonymous"></script>
<div class="dg-stage">
  <div class="dg-scene" id="dgScene" data-gallery="main">
    <canvas class="dg-canvas" width="0" height="0"></canvas>
    <div class="dg-hud dg-counter">1 / 5</div>
    <div class="dg-hud dg-caption">
      <h3>Golden</h3>
      <p>PMS 135 C</p>
    </div>
    <div class="dg-hud dg-hint">Molette / glisser pour explorer</div>
    <div class="dg-fallback">Galerie 3D — WebGL requis. Voici la première image.</div>
  </div>
</div>
🔒 Débloquer le code complet — à partir de 2,99 € le 1er mois

HTML + CSS + JS complet, prêt à coller — avec des centaines d'effets premium.

Personnaliser

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

Option / propriétéDéfautEffet
GALLERIES (JS — clé lue dans data-gallery) main : 5 entrées { src, x, title, sub, bg } Une galerie par clé, choisie par l'attribut data-gallery de la zone (repli sur main). src = URL de la photo (cinq exemples Unsplash dans le code, à remplacer par les vôtres ; hébergée sur un autre domaine, l'image doit autoriser le CORS), x = désaxement en unités (−0,9 à 1,0 dans le code), title/sub = légende, bg = fond en RVB 0-255, écrit à la main — rien n'est calculé depuis l'image.
GAP (JS) 5 Distance entre deux plans, en unités. La course totale suit : (5 − (−(n − 1) × GAP + 5)) / S2W. 8 : plongée plus longue entre deux photos, 800 crans chacune.
SIZE (JS) 3 Côté du plan, en unités ; le plan couvre SIZE / 4,14 de la hauteur à 5 unités (72 %). 3.6 : 87 % ; au-delà de 4,14, la photo déborde déjà à sa distance de netteté.
S2W (JS) 0.01 Unités de caméra par cran de molette : course de 2 000 crans, 500 par photo. 0.02 : deux fois plus court, 250 crans par photo — plus nerveux à la molette, 114 px de glisser par photo.
SCROLL_SMOOTH / FADE_SMOOTH (JS) 0.08 / 0.14 Part de l'écart comblée à chaque image, pour la caméra et pour les opacités : 95 % en 36 et 20 images. Comptées en images, pas en millisecondes — deux fois plus lentes à 60 Hz qu'à 120 Hz. 1 = sans inertie.
PARALLAX_X / PARALLAX_Y (JS) 0.16 / 0.08 (0 sous prefers-reduced-motion) Amplitude du flottement sous le pointeur, en unités, pondérée par l'opacité et 1 + idx × 0,05. À 5 unités dans 744 × 465 px : ±18 et ±9 px. 0 pour une galerie fixe.
BREATH_TILT / BREATH_SCALE / VEL_MAX (JS) 0.045 / 0.03 / 1.5 Inclinaison (radians) et gonflement des plans proportionnels à la vitesse de défilement, bornée à VEL_MAX unités par image ; la même vitesse éclaircit le fond de 8 % au maximum. 0 / 0 supprime le souffle, le fond continue de s'éclaircir.
Glisser (JS — (dragY − e.clientY) * 2.2) 2,2 crans par pixel Sensibilité du glisser : 909 px pour la course entière, 227 px par photo. 4 : 125 px par photo, une chiquenaude au doigt suffit.

FAQ

Le script teste typeof THREE === 'undefined' et bascule en repli si la variable globale n'existe pas : c'est le build classique de Three.js r128 (three.min.js, 600 Ko, chargé depuis cdn.jsdelivr.net en tête du HTML vendu) qui la crée, et il doit être exécuté avant le code de l'effet. Vous pouvez héberger ce même fichier chez vous ; gardez la r128, l'effet est écrit pour son API et les versions récentes ne sont publiées qu'en modules ES (il faudrait alors import * as THREE from 'three'; window.THREE = THREE; avant l'IIFE). À remplacer ensuite : les cinq src de GALLERIES.main, des photos Unsplash d'exemple. Si vos images sont sur un autre domaine que la page, le serveur doit envoyer Access-Control-Allow-Origin — le chargeur est en crossOrigin: 'anonymous', et WebGL refuse toute texture d'origine étrangère sans cet en-tête : le plan reste gris 0x888888. Mettez aussi à jour bg, la dominante de chaque photo, écrite à la main.
C'est le code : l'écouteur wheel est en passive: false et appelle e.preventDefault() à chaque événement, y compris quand scrollTarget est déjà à 0 ou à 2 000 — mesuré : defaultPrevented vrai aux deux bornes, scrollY de la page inchangé après 2 400 crans. Sur mobile, touch-action: none a le même effet au doigt. Sur une galerie qui remplit l'écran, le visiteur est bloqué. Correctif de trois lignes au début de l'écouteur : calculez d, puis n'appelez preventDefault() que si (d > 0 && scrollTarget < maxScroll) || (d < 0 && scrollTarget > minScroll) — la page reprend la main en fin de course. Pour le tactile, remplacez touch-action: none par pan-x pinch-zoom et n'appliquez le glisser vertical que dans les mêmes conditions.
fx-0566 est du CSS pur : une colonne défilante où chaque photo s'estompe selon sa position, par animation-timeline: view() — aucun fond, aucun déplacement latéral, aucune profondeur, et Firefox ne le rend pas. fx-0782 déplace une caméra à travers une pile : la photo apparaît à 36 % de la hauteur, atteint 72 % à sa distance de netteté, puis grossit jusqu'à déborder pendant qu'elle s'efface ; son désaxement x la fait glisser de côté en approchant ; le fond de scène passe d'un bg[] à l'autre et s'éclaircit avec la vitesse ; le pointeur fait flotter l'ensemble. Le prix : 600 Ko de Three.js, un contexte WebGL, une boucle permanente et la molette confisquée à la page. Choisissez fx-0782 quand la photo est le sujet et que le visiteur doit s'y arrêter ; fx-0566 pour une galerie qui reste un flux de page, hors Firefox.