Où planter les hostas à l'ombre ?
Jardins Lemaire · paysagisteDéplacez le soleil avec la souris : les ombres tournent au pied des arbustes.
Trois pastilles (◆ ★ ♥) posées au-dessus de leurs ombres de contact : le pointeur joue la lampe, les ombres au sol glissent à l'opposé et s'étirent, l'ombre portée des pastilles tourne et elles se soulèvent un peu — un même éclairage pour les trois, à la souris, au stylet ou au doigt ; si l'utilisateur réduit les animations, les pastilles ne bougent plus et seules les ombres suivent la lumière.
Mis à jour le
Cet effet est gratuit — la catégorie 3D en contient 50 au total, dont 8 gratuits. Effect.Labs regroupe 840 effets vanilla. Découvrir la catégorie →
Déplacez le soleil avec la souris : les ombres tournent au pied des arbustes.
Atelier Roussel architectes
MaisonAtelierAbri vélos
Cavalier, tour, fou : série 28 mmDélai 3 semaines
La racine .fx-0029-root (250 px de haut, fond rgb(26, 26, 36)) contient .float-shadow-container, marqué data-float-shadow : toute la largeur et 250 px de haut, qui aligne au centre trois .float-shadow-el espacés de 30 px. Chacun porte une pastille .float-shadow-shape de 60 × 60 px (carré arrondi indigo, rond rose, carré arrondi vert, en dégradés) et, dessous, une ombre de contact .float-shadow-shadow : une ellipse de 50 × 10 px en dégradé radial noir à 35 %, centrée 15 px sous la pastille.
Le script parcourt toutes les racines .fx-0029-root de la page et branche une instance par groupe. À chaque mouvement du pointeur, il calcule la position de la lumière par rapport au centre du conteneur, de −1 à +1 sur chaque axe, puis applique le même éclairage aux trois éléments : l'ombre au sol glisse à l'opposé du pointeur (jusqu'à 15 px) et s'étire en largeur quand la lumière monte ou descend (jusqu'à ×1,3) en pâlissant ; la pastille reçoit une ombre portée de 20 px de flou décalée à l'opposé de la lumière, se décale de 3 px vers elle et se soulève jusqu'à 5 px. Mesuré dans Chromium 151, Firefox 153 et WebKit 26.5 avec le pointeur au bord droit : 13,5 px de glissement des ombres au sol. Quand le pointeur sort, tout revient à l'état de repos ; les ombres au sol suivent avec une transition de 0,3 s.
Le décalage est calculé une fois, depuis le centre du conteneur, et vaut pour les trois pastilles : elles réagissent comme sous un soleil lointain, toutes leurs ombres parallèles, et non comme sous une lampe placée entre elles, qui donnerait une direction d'ombre par objet. Le script écoute les événements pointer : au doigt, touch-action: pan-y garde le défilement vertical de la page, et un glissé de côté déplace la lumière ; tout revient au repos quand le doigt se lève.
prefers-reduced-motion: reduce, un bloc limité à .fx-0029-root impose transform: none aux pastilles : elles ne se soulèvent plus et ne se décalent plus. Les ombres continuent de suivre la lumière, ce qui garde l'information « d'où vient la lumière » sans déplacer les objets. Mesuré dans Chromium, Firefox et WebKit : pastilles immobiles sous la souris, ombres au sol toujours décalées.aria-hidden="true" sur les pastilles si elles sont décoratives, comme dans les scènes.#6366f1 à 4,5:1 et sur rose #ec4899 à 3,5:1 ; sur le vert #10b981, seulement 2,5:1 : décoratif, pas pour du texte.CSS (transform, box-shadow, dégradés radiaux, transition, touch-action) + JS d'événements pointer, aucune dépendance. Mesuré dans Chromium 151, Firefox 153 et WebKit 26.5 : mêmes décalages, deux conteneurs indépendants sur une page, pastilles immobiles sous mouvement réduit ; glissé au doigt mesuré dans Chromium en mode tactile. Aucune 3D CSS : le rendu WebKit est fiable, un vrai Safari et un vrai iPhone n'ont pas été vérifiés.
Sans JavaScript, les trois pastilles restent posées sur leurs ombres de contact, à l'état de repos. Sur un écran tactile, un simple appui ne déplace pas la lumière : il faut glisser le doigt de côté.
Copiez les trois blocs dans votre page. Aucune dépendance.
<div class="fx-0029-root">
<div class="float-shadow-container" data-float-shadow>
<div class="float-shadow-el">
<div class="float-shadow-shape fs-shape-1">♦</div>
<div class="float-shadow-shadow"></div>
</div>
<div class="float-shadow-el">
<div class="float-shadow-shape fs-shape-2">★</div>
<div class="float-shadow-shadow"></div>
</div>
<div class="float-shadow-el">
<div class="float-shadow-shape fs-shape-3">♥</div>
<div class="float-shadow-shadow"></div>
</div>
</div>
</div>
.fx-0029-root,
.fx-0029-root *,
.fx-0029-root *::before,
.fx-0029-root *::after {
box-sizing: border-box
}
.fx-0029-root {
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;
}
.float-shadow-container {
width: 100%;
height: 250px;
position: relative;
display: flex;
align-items: center;
justify-content: center;
gap: 30px;
cursor: crosshair;
/* Touch: a sideways drag moves the light, a vertical swipe still scrolls the page */
touch-action: pan-y;
}
.float-shadow-el {
position: relative;
transition: transform 0.3s ease;
}
.float-shadow-shape {
width: 60px;
height: 60px;
border-radius: 16px;
display: flex;
align-items: center;
justify-content: center;
font-size: 1.5rem;
position: relative;
z-index: 2;
}
.fs-shape-1 {
background: linear-gradient(135deg, #6366f1, #818cf8);
}
.fs-shape-2 {
background: linear-gradient(135deg, #ec4899, #f472b6);
border-radius: 50%;
}
.fs-shape-3 {
background: linear-gradient(135deg, #10b981, #34d399);
}
.float-shadow-shadow {
position: absolute;
bottom: -15px;
left: 50%;
transform: translateX(-50%);
width: 50px;
height: 10px;
background: radial-gradient(ellipse, rgba(0, 0, 0, 0.35) 0%, transparent 70%);
border-radius: 50%;
transition: all 0.3s ease;
z-index: 1;
}
/* Reduced motion: the shapes no longer lift; only the shadows follow the light */
@media (prefers-reduced-motion: reduce) {
.fx-0029-root .float-shadow-shape {
transform: none !important;
}
}
(function() {
// ==========================================
// 9. Floating Shadows - Dynamic light source (pointer events: mouse, pen and touch)
// One instance per .fx-0029-root: several groups on one page each follow their own light.
// ==========================================
document.querySelectorAll('.fx-0029-root').forEach(function(root) {
var container = root.querySelector('[data-float-shadow]');
if (!container) return;
var elements = container.querySelectorAll('.float-shadow-el');
container.addEventListener('pointermove', function(e) {
var rect = container.getBoundingClientRect();
var mx = e.clientX - rect.left;
var my = e.clientY - rect.top;
var centerX = rect.width / 2;
var centerY = rect.height / 2;
var lightX = (mx - centerX) / centerX;
var lightY = (my - centerY) / centerY;
elements.forEach(function(el) {
var shape = el.querySelector('.float-shadow-shape');
var shadow = el.querySelector('.float-shadow-shadow');
// Move shadow opposite to light direction
var shadowX = -lightX * 15;
var shadowY = lightY * 5 + 5;
var shadowScale = 1 + Math.abs(lightY) * 0.3;
shadow.style.transform = 'translateX(calc(-50% + ' + shadowX + 'px)) scaleX(' + shadowScale + ')';
shadow.style.opacity = 0.6 - Math.abs(lightY) * 0.2;
// Lift shape slightly toward light
shape.style.transform = 'translate(' + (lightX * 3) + 'px, ' + (-Math.abs(lightY) * 5) + 'px)';
// Dynamic box shadow on shape
shape.style.boxShadow = ((-lightX * 8)) + 'px ' + ((-lightY * 8 + 4)) + 'px 20px rgba(0,0,0,0.3)';
});
});
container.addEventListener('pointerleave', function() {
elements.forEach(function(el) {
var shape = el.querySelector('.float-shadow-shape');
var shadow = el.querySelector('.float-shadow-shadow');
shadow.style.transform = 'translateX(-50%)';
shadow.style.opacity = '0.35';
shape.style.transform = 'translate(0, 0)';
shape.style.boxShadow = '';
});
});
});
})();
Options passées à l'API ou attributs data-* :
| Option / propriété | Défaut | Effet |
|---|---|---|
Glissement des ombres au sol (JS — -lightX * 15) |
15 px au plus | 30 donne des ombres longues de fin de journée ; 8, une lumière presque au zénith. |
Étirement des ombres (JS — Math.abs(lightY) * 0.3) |
jusqu'à ×1,3 | Allonge l'ombre quand la lumière est basse ou haute. 0.6 l'étire nettement ; 0 la garde ronde. |
Ombre portée des pastilles (JS — boxShadow, facteur 8 et flou 20px) |
décalage de 8 px, flou de 20 px, noir à 30 % | Un facteur de 14 détache franchement les pastilles du fond ; un flou de 10 px donne une lumière plus dure. |
Soulèvement (JS — lightX * 3 et Math.abs(lightY) * 5) |
3 px de côté, 5 px vers le haut | Mettez 0 et 0 pour des objets posés, qui ne bougent jamais : seules les ombres réagissent. |
Pastilles (CSS — .float-shadow-shape, .fs-shape-1 à -3) |
60 × 60 px, dégradés indigo, rose, vert | Taille, couleur et forme libres. Évitez clip-path sur la pastille : il couperait son ombre portée ; dessinez la forme avec border-radius ou un ::before, comme le toit de la deuxième scène. |
Ombres au sol (CSS — .float-shadow-shadow) |
50 × 10 px, noir à 35 %, 15 px sous la pastille | Élargissez-les avec les pastilles (74 × 16 px pour des pastilles de 78 px) et teintez-les selon le sol (vert sombre sur une pelouse). |
Dans ce code, non : la lumière est calculée une fois, depuis le centre du conteneur, et le même décalage vaut pour les trois, comme sous un soleil lointain. Pour une lampe proche, il faudrait calculer lightX et lightY dans la boucle, depuis le centre de chaque élément (el.getBoundingClientRect()), à la place du centre du conteneur.
L'ombre qui tourne est un box-shadow sur la pastille ; un clip-path ou un mask la coupe avec la forme. Dessinez la forme avec border-radius, des bordures ou un pseudo-élément, ou remplacez le box-shadow par filter: drop-shadow(…) sur un élément parent.
Oui. Chaque groupe a sa racine .fx-0029-root et le script donne à chacun son instance : chaque groupe ne réagit qu'au pointeur qui le survole. C'est le cas sur cette page, où la démo du haut et les trois exemples tournent avec un seul script.