3DFree

Floating Shadows

Three badges (◆ ★ ♥) sitting above their contact shadows: the pointer acts as the lamp, the ground shadows slide away from it and stretch, the badges' drop shadow turns and they lift slightly — one lighting for all three, with the mouse, a pen or a finger; when the user reduces motion, the badges stay put and only the shadows follow the light.

JSMouseShadow

Updated

♦
★
♥
Hover or click the scene to interact

This effect is free — the 3D category contains 50 effects total, including 8 free. Effect.Labs has 840 vanilla effects. Explore the category →

Usage examples

Where should the hostas go, in the shade?

Jardins Lemaire · landscaper

Move the sun with the mouse: the shadows turn at the foot of the shrubs.

① Shadows of three shrubs along the sun's path — tool of a landscaper

WhenOn the "Where should the hostas go, in the shade?" page, three round shrubs stand on a lawn; the mouse plays the sun and the shadows turn at the foot of the shrubs, with an instruction at the bottom of the scene.
WhyA pointer that acts as the light makes a cast-shadow reasoning concrete, which a still plan cannot explain. One lighting for all three, like the sun's, gives parallel shadows: that is right for shrubs in an open garden.
SettingsRoot and container stretched to the whole scene; round green gradient badges (78 px, the middle shrub 96 px), symbols hidden (font-size: 0); dark green 74 × 16 px ground shadows; title and instruction placed absolutely inside the container.

Atelier Roussel architects

Shadow study, Les Tilleuls plot

HouseWorkshopBike shelter

② Architect's models under the lamp — shadow study of an architecture firm

WhenThe page for the Les Tilleuls plot shadow study shows three small white volumes, house, workshop and bike shelter, on a gray table; the mouse moves the lamp and the shadows slide under the volumes.
WhyContact shadows that stretch and move away from the pointer are enough to make volumes read as standing on a table, without any 3D rendering.
SettingsWhite badges with 2 px corners, in different sizes (84 × 64, 104 × 70, 52 × 44 px), border-triangle roof on the house (::before); light gray background; slate gray shadows; caption of the three volumes at the bottom.
Atelier Pinceau FinHand-painted figurines, made to order

Knight, tower, bishop: 28 mm seriesLead time 3 weeks

③ Painted figurines under the workshop lamp — shop of a figurine painter

WhenThe shop presents three round bases (knight, rook, bishop) on a wooden workbench; the lamp follows the mouse and the shadows turn under the bases.
WhyShowing a piece under a moving light is the gesture of a painter inspecting their work; here, the visitor holds the lamp.
Settings78 px round charcoal badges carrying the ♞ ♜ ♝ symbols at 40 px, each in its own paint color; very dark brown ground shadows; wood gradient workbench with planks; workshop name at the top, series and lead time at the bottom.

How it works

The root .fx-0029-root (250 px tall, rgb(26, 26, 36) background) holds .float-shadow-container, marked data-float-shadow: full width and 250 px tall, centering three .float-shadow-el spaced 30 px apart. Each carries a 60 × 60 px .float-shadow-shape badge (rounded indigo square, pink circle, rounded green square, all gradients) and, below it, a .float-shadow-shadow contact shadow: a 50 × 10 px black radial gradient ellipse at 35%, centered 15 px under the badge.

The script goes through every .fx-0029-root on the page and wires one instance per group. On each pointer move, it computes the light position relative to the container's center, from −1 to +1 on each axis, then applies the same lighting to all three elements: the ground shadow slides away from the pointer (up to 15 px) and stretches sideways when the light goes up or down (up to ×1.3) while fading; the badge gets a drop shadow blurred by 20 px and offset away from the light, shifts 3 px toward it and lifts up to 5 px. Measured in Chromium 151, Firefox 153 and WebKit 26.5 with the pointer at the right edge: 13.5 px of ground shadow slide. When the pointer leaves, everything returns to rest; the ground shadows follow with a 0.3 s transition.

The offset is computed once, from the container's center, and applies to all three badges: they react as under a distant sun, with parallel shadows, not as under a lamp placed between them, which would give each object its own shadow direction. The script listens to pointer events: with a finger, touch-action: pan-y keeps vertical page scrolling, and a sideways swipe moves the light; everything returns to rest when the finger lifts.

Accessibility

  • Reduced motion: under prefers-reduced-motion: reduce, a block scoped to .fx-0029-root forces transform: none on the badges: they no longer lift or shift. The shadows keep following the light, which keeps the "where the light comes from" cue without moving the objects. Measured in Chromium, Firefox and WebKit: badges still under the mouse, ground shadows still offset.
  • Decoration: there is nothing to activate and nothing to reach with the keyboard; the rest state, three badges sitting on their shadows, reads as is. If the badges stand for objects that matter (products, buildings), name them in the text, like the captions in the scenes.
  • Screen readers: the ◆ ★ ♥ symbols are read out ("black diamond suit", "black star", "black heart suit"): add aria-hidden="true" on the badges if they are decorative, as in the scenes.
  • Contrast: white symbols on indigo #6366f1 at 4.5:1 and on pink #ec4899 at 3.5:1; on the green #10b981, only 2.5:1: decorative, not for text.

Browser compatibility

CSS (transform, box-shadow, radial gradients, transition, touch-action) + JS with pointer events, no dependencies. Measured in Chromium 151, Firefox 153 and WebKit 26.5: same offsets, two independent containers on one page, still badges under reduced motion; finger swipe measured in Chromium in touch mode. No CSS 3D: the WebKit rendering is reliable, a real Safari and a real iPhone were not checked.

Chrome 55+✓ Full
Firefox 59+✓ Full
Safari 13+✓ Measured on WebKit
Edge 79+✓ Full
Mobile iOS 13+✓ Not checked on a real iPhone
Android Chrome✓ Full, sideways swipe

Without JavaScript, the three badges stay on their contact shadows, at rest. On a touch screen, a single tap does not move the light: the finger has to slide sideways.

The code

Copy the three blocks into your page. No dependencies.

HTML
<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">&#9830;</div>
      <div class="float-shadow-shadow"></div>
    </div>
    <div class="float-shadow-el">
      <div class="float-shadow-shape fs-shape-2">&#9733;</div>
      <div class="float-shadow-shadow"></div>
    </div>
    <div class="float-shadow-el">
      <div class="float-shadow-shape fs-shape-3">&#9829;</div>
      <div class="float-shadow-shadow"></div>
    </div>
  </div>
</div>
CSS
.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;
  }
}
JavaScript (fx-0029)
(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 = '';
      });
    });
  });
})();

Customize

Options passed to the API or data-* attributes:

Option / propertyDefaultEffect
Ground shadow slide (JS — -lightX * 15) 15 px at most 30 gives long late-afternoon shadows; 8, a light almost overhead.
Shadow stretch (JS — Math.abs(lightY) * 0.3) up to ×1.3 Lengthens the shadow when the light is low or high. 0.6 stretches it clearly; 0 keeps it round.
Badge drop shadow (JS — boxShadow, factor 8 and 20px blur) 8 px offset, 20 px blur, black at 30% A factor of 14 clearly lifts the badges off the background; a 10 px blur gives a harder light.
Lift (JS — lightX * 3 and Math.abs(lightY) * 5) 3 px sideways, 5 px up Set both to 0 for objects that sit still and never move: only the shadows react.
Badges (CSS — .float-shadow-shape, .fs-shape-1 to -3) 60 × 60 px, indigo, pink, green gradients Size, color and shape are free. Avoid clip-path on the badge: it would cut its drop shadow; draw the shape with border-radius or a ::before, like the roof in the second scene.
Ground shadows (CSS — .float-shadow-shadow) 50 × 10 px, black at 35%, 15 px under the badge Widen them along with the badges (74 × 16 px for 78 px badges) and tint them to match the ground (dark green on a lawn).

FAQ

The three shadows move together: can each one get its own direction?

Not in this code: the light is computed once, from the container's center, and the same offset applies to all three, as under a distant sun. For a nearby lamp, you would compute lightX and lightY inside the loop, from the center of each element (el.getBoundingClientRect()), instead of the container's center.

Why does my cut-out shape lose its shadow?

The turning shadow is a box-shadow on the badge; a clip-path or a mask cuts it along with the shape. Draw the shape with border-radius, borders or a pseudo-element, or replace the box-shadow with filter: drop-shadow(…) on a parent element.

Can I put several groups on one page?

Yes. Each group has its own .fx-0029-root and the script gives each one its own instance: each group only reacts to the pointer hovering it. That is the case on this page, where the demo at the top and the three examples run on a single script.