GODRICH
#css-animation#button#loader#transition#scroll#dashboard#ui#design-trends

CSS Shadow Effects: 9 Demos That Compute the Cast

With css shadow effects from one box-shadow line, the shadow stays put as the light circles, light leaks behind walls and two lights leave one shadow — scenes

See All Nine Shadow Scenes Running on This Page

Auto-plays · click a tile to jump to its section · all nine in one zip

CSS Shadow Effects: 9 Demos Where the Cast Is Computed From the Light

Every scene draws on one 480 by 300 canvas (W and H), and the only input is a single light target. Until you press the canvas, a built-in path steers that light along a loop of about four seconds. After the press, the pointer takes over, and hovering alone changes nothing. The arrow keys nudge the target 12 pixels at a time. The scene eases toward the target by LIGHT_LERP and recomputes every shadow from scratch on each 16 ms step (FIXED_DT).

Most box shadow effects you copy from a gallery hide a fixed answer. An offset such as 8px 12px 20px was decided once, so the shadow keeps pointing the same way while the light goes around it. Here the offset is never typed. It is derived from where the light sits and how high it hangs.

The backbone is one line. For a light at (lx, ly) with height lh and a point of the object at (x, y) with height h, the floor shadow lands at the light plus (point minus light) times lh divided by (lh minus h). A higher point shrinks the denominator and is pushed farther from the light, and the denominator is clamped to at least 8 so it never reaches zero. Scenes 01, 02, 08 and 09 apply it to the four top corners of a box and wrap the results in a convex hull, while 03, 04, 06 and 07 turn the same idea into formulas that read blur, length or opacity off a height. Scene 05 skips projection and blocks light with rays.

If you want depth built on lettering rather than on boxes, CSS Text Shadow Effects — 9 Patterns covers stacked layers on type. This page stays on the shape of the shadow itself. The same input always paints the same picture, and no Date, fetch, storage, WebGL or library appears in any of the nine files.

Code for Each Shadow Technique

Each snippet is a run of consecutive lines cut from one scene's index.html, and the full files open from the zip. The scenes differ in makeScene, while main, which owns input, time and the button, is the same skeleton in all nine.

01Shadow Projected onto the Floor as the Light Circles

castPoint takes a light height of 220 and a box height of 70, gets the factor 220 / (220 − 70) = 1.47 and pushes the four top corners away from the light onto the floor, and boxShadow then joins the four base points and the four pushed points into one hull and fills it. As the light runs its elliptical orbit (ORBIT_RADIUS 110), the shadow stays on the far side and swings along with it. Measured at 63.4% accumulated area, 40.4 average intensity and 23/23 moved frames.

Shadow Projected onto the Floor as the Light Circles
Open in a new tab

01 · Shadow Projected onto the Floor as the Light Circles — Measured area 63.387% · moving frames 23/23Original by GODRICH · SCSS and React ship in the zip · See the password

LIGHT_HEIGHTORBIT_RADIUSLIGHT_LERPBOX_HEIGHT
    function castPoint(lx, ly, lh, x, y, h) {
        const k = lh / Math.max(lh - h, 8);
        return [lx + (x - lx) * k, ly + (y - ly) * k];
    }
    function boxShadow(lx, ly, lh, x0, y0, x1, y1, h) {
        const base = [[x0, y0], [x1, y0], [x1, y1], [x0, y1]];
        const pts = base.slice();
        for (let i = 0; i < base.length; i += 1) {
            pts.push(castPoint(lx, ly, lh, base[i][0], base[i][1], h));
        }
        return hull(pts);
    }
            path: function (t) {
                const a = PI2 * t / PERIOD_MS;
                return [CENTER_X + ORBIT_RADIUS * Math.cos(a), CENTER_Y + ORBIT_RADIUS * 0.72 * Math.sin(a)];
            },
            pos: function () { return [lx, ly]; },
            update: function (tx, ty) {
                lx = lerp(lx, tx, LIGHT_LERP);
                ly = lerp(ly, ty, LIGHT_LERP);
            },

02A Row of Pillars Lit by the Pointer

One light follows the target at a LIGHT_LERP of 0.12, and six pillars of heights 67, 101, 88, 59, 64 and 74 (fixed by the seed 444) each get their own projection. The demo path drives the light until you press, and from then on the pointer does. Measured at 35.0% accumulated area, 44.0 average intensity and 23/23 moved frames.

A Row of Pillars Lit by the Pointer
Open in a new tab

02 · A Row of Pillars Lit by the Pointer — Measured area 34.99% · moving frames 23/23Original by GODRICH · SCSS and React ship in the zip · See the password

LIGHT_LERPPILLAR_COUNTPILLAR_WIDTHLIGHT_HEIGHT
    function makeScene(ctx) {
        let lx = 240;
        let ly = 200;
        const rnd = mulberry32(SEED);
        const heights = [];
        for (let i = 0; i < PILLAR_COUNT; i += 1) {
            heights.push(58 + Math.floor(rnd() * 44));
        }
        function pillarX(i) { return 58 + i * (364 / (PILLAR_COUNT - 1)); }
                for (let i = 0; i < PILLAR_COUNT; i += 1) {
                    const x = pillarX(i);
                    const poly = boxShadow(lx, fy, LIGHT_HEIGHT, x - PILLAR_WIDTH / 2, BASE_Y - PILLAR_DEPTH / 2, x + PILLAR_WIDTH / 2, BASE_Y + PILLAR_DEPTH / 2, heights[i]);
                    fillShadow(ctx, poly, SHADOW_RGB, 0.44);
                }

03A Shadow That Blurs With Height

The plank is cut into 64 strips that each read their own height above the floor, with blur at 2 plus height times BLUR_PER_HEIGHT, opacity at CONTACT_ALPHA divided by (1 + height × 0.03), and every strip filled with a vertical gradient. The strip touching the floor (height 0.86) gets 2.17 px and 0.604, the raised end (height 109.14) gets 23.83 px and 0.145, so sharpness changes inside a single board. Measured at 32.8% accumulated area, 44.9 average intensity and 23/23 moved frames.

A Shadow That Blurs With Height
Open in a new tab

03 · A Shadow That Blurs With Height — Measured area 32.814% · moving frames 23/23Original by GODRICH · SCSS and React ship in the zip · See the password

BLUR_PER_HEIGHTCONTACT_ALPHALIFT_MAX
                for (let i = 0; i < STRIPS; i += 1) {
                    const h0 = heightAt(i / STRIPS, total);
                    const h1 = heightAt((i + 1) / STRIPS, total);
                    const hm = (h0 + h1) / 2;
                    const xa = Math.round(PLANK_LEFT + (PLANK_LEN * i) / STRIPS + h0 * slide * 0.9);
                    const xb = Math.round(PLANK_LEFT + (PLANK_LEN * (i + 1)) / STRIPS + h1 * slide * 0.9);
                    const blur = 2 + hm * BLUR_PER_HEIGHT;
                    const alpha = CONTACT_ALPHA / (1 + hm * 0.03);
                    const yTop = BASE_Y - 2;
                    const yBot = BASE_Y + 16 + hm * 0.5;
                    const g = ctx.createLinearGradient(0, yTop - blur, 0, yBot + blur);
                    const span = yBot - yTop + 2 * blur;
                    g.addColorStop(0, rgba(SHADOW_RGB, 0));
                    g.addColorStop(clamp(blur * 1.2 / span, 0, 0.45), rgba(SHADOW_RGB, alpha));
                    g.addColorStop(clamp(1 - blur * 1.2 / span, 0.55, 1), rgba(SHADOW_RGB, alpha * 0.85));
                    g.addColorStop(1, rgba(SHADOW_RGB, 0));
                    ctx.fillStyle = g;
                    ctx.fillRect(xa, yTop - blur, xb - xa, span);
                }

04A Sundial Under a Virtual Sun on an Arc

One sun progress value sets elevation as 14° + 56° × sin(π × progress), and the shadow length is GNOMON_HEIGHT divided by tan(elevation), cut at SHADOW_LEN_MAX 140. Because the direction is the sun angle turned around, the shadow lengthens and shortens while it sweeps the opposite way along the 160° arc. Measured at 28.5% accumulated area, 37.1 average intensity and 23/23 moved frames.

A Sundial Under a Virtual Sun on an Arc
Open in a new tab

04 · A Sundial Under a Virtual Sun on an Arc — Measured area 28.476% · moving frames 23/23Original by GODRICH · SCSS and React ship in the zip · See the password

SUN_ARC_DEGSHADOW_LEN_MAXGNOMON_HEIGHT
        // turn sun progress u (0 just after sunrise, 1 just before sunset) into a screen angle
        function sunAngle(u) {
            const mid = 270;
            return (mid - SUN_ARC_DEG / 2 + u * SUN_ARC_DEG) * Math.PI / 180;
        }
        function elevation(u) { return (ELEV_LOW_DEG + ELEV_SPAN_DEG * Math.sin(Math.PI * u)) * Math.PI / 180; }
                const phi = sunAngle(ux);
                const e = elevation(ux);
                const len = Math.min(SHADOW_LEN_MAX, GNOMON_HEIGHT / Math.tan(e));

05The Area the Light Reaches Around Walls

visibilityPolygon gathers 180 evenly spaced rays (RAY_COUNT) plus three rays per wall endpoint, offset by −ANGLE_EPS, 0 and +ANGLE_EPS, and sorts them by angle, then castRay finds the nearest hit for each and the points are joined into the lit polygon. Only the inside of that polygon is clipped and brightened, so the space behind a wall stays dark. Measured at 65.6% accumulated area, 74.3 average intensity and 23/23 moved frames.

The Area the Light Reaches Around Walls
Open in a new tab

05 · The Area the Light Reaches Around Walls — Measured area 65.603% · moving frames 23/23Original by GODRICH · SCSS and React ship in the zip · See the password

RAY_COUNTWALL_SEGMENTSANGLE_EPSLIGHT_RADIUS
    function castRay(ox, oy, dx, dy) {
        let best = 4000;
        for (let i = 0; i < WALL_SEGMENTS.length; i += 1) {
            const s = WALL_SEGMENTS[i];
            const ex = s[2] - s[0];
            const ey = s[3] - s[1];
            const den = dx * ey - dy * ex;
            if (Math.abs(den) < 1e-9) {
                continue;
            }
            const t = ((s[0] - ox) * ey - (s[1] - oy) * ex) / den;
            const u = ((s[0] - ox) * dy - (s[1] - oy) * dx) / den;
            if (t > 0 && u >= 0 && u <= 1 && t < best) {
                best = t;
            }
        }
        return best;
    }
    function visibilityPolygon(ox, oy) {
        const angles = [];
        for (let i = 0; i < RAY_COUNT; i += 1) {
            angles.push(PI2 * i / RAY_COUNT);
        }
        for (let i = 0; i < WALL_SEGMENTS.length; i += 1) {
            const s = WALL_SEGMENTS[i];
            const a = Math.atan2(s[1] - oy, s[0] - ox);
            angles.push(a - ANGLE_EPS, a, a + ANGLE_EPS);
        }
        angles.sort(function (p, q) { return p - q; });
        const out = [];
        for (let i = 0; i < angles.length; i += 1) {
            const dx = Math.cos(angles[i]);
            const dy = Math.sin(angles[i]);
            const d = castRay(ox, oy, dx, dy);
            out.push([ox + dx * d, oy + dy * d]);
        }
        return out;
    }

06Card Shadow Values Computed From the Elevation Level

shadowOf turns one level number into an offset of level × ELEV_STEP × (LIGHT_HEIGHT / 180), a blur of 2 + level × ELEV_STEP × 0.9 and an opacity of min(0.12 + 0.045·level, 0.6), then paints two layers through the canvas shadowBlur. Level 1 gives 3.33 offset, 5.6 blur and 0.165 opacity, level 5 gives 16.67, 20.0 and 0.345, and all three numbers move straight into a box-shadow as offset, blur and alpha. Measured at 41.6% accumulated area, 28.5 average intensity and 23/23 moved frames.

Card Shadow Values Computed From the Elevation Level
Open in a new tab

06 · Card Shadow Values Computed From the Elevation Level — Measured area 41.603% · moving frames 23/23Original by GODRICH · SCSS and React ship in the zip · See the password

ELEV_STEPSHADOW_ALPHA_BASEHOVER_BONUS
    function shadowOf(level, dx, dy) {
        const dist = Math.max(Math.sqrt(dx * dx + dy * dy), 1);
        const reach = level * ELEV_STEP * (LIGHT_HEIGHT / 180);
        return {
            ox: (dx / dist) * reach,
            oy: (dy / dist) * reach + level * 0.5,
            blur: 2 + level * ELEV_STEP * 0.9,
            alpha: Math.min(SHADOW_ALPHA_BASE + level * 0.045, 0.6)
        };
    }
                    const s = shadowOf(level, x + CARD_W / 2 - lx, y + CARD_H / 2 - ly);
                    // layer a wide ambient blur under a short key shadow
                    ctx.shadowColor = rgba(INK_RGB, s.alpha);
                    ctx.shadowBlur = s.blur;
                    ctx.shadowOffsetX = s.ox;
                    ctx.shadowOffsetY = s.oy;
                    ctx.fillStyle = rgba(CARD_RGBS[i], 1);
                    ctx.fillRect(x, y, CARD_W, CARD_H);

07A Contact Shadow That Darkens as the Object Settles

contactAlpha sets opacity from the height h over HEIGHT_MAX 96 as AO_STRENGTH × (1 − h / 96)² + 0.05, and contactRadius widens the radius to (AO_RADIUS + halfWidth × 0.4) × (1 + 1.2·h / 96). A sphere of half-width 26 resting on the floor gets opacity 0.71 at radius 36.4, and at the top height of 96 it relaxes to 0.05 at radius 80.1, painted as a canvas ellipse and not as a DOM box-shadow. Measured at 16.8% accumulated area, 41.9 average intensity and 23/23 moved frames.

A Contact Shadow That Darkens as the Object Settles
Open in a new tab

07 · A Contact Shadow That Darkens as the Object Settles — Measured area 16.829% · moving frames 23/23Original by GODRICH · SCSS and React ship in the zip · See the password

AO_RADIUSAO_STRENGTHHEIGHT_MAX
    function contactAlpha(h) {
        const near = 1 - clamp(h / HEIGHT_MAX, 0, 1);
        return AO_STRENGTH * near * near + 0.05;
    }
    function contactRadius(h, half) {
        return (AO_RADIUS + half * 0.4) * (1 + 1.2 * clamp(h / HEIGHT_MAX, 0, 1));
    }

08The Double Shadow of Two Colored Lights

Each light paints a pool of color on its own offscreen layer, erases only its own shadow polygon with destination-out, and adds the layer to the main canvas with lighter. Where one light is blocked the other light's color remains, and only where both are blocked does the bare floor show, the darkest spot. Measured at 66.3% accumulated area, 34.1 average intensity and 23/23 moved frames.

The Double Shadow of Two Colored Lights
Open in a new tab

08 · The Double Shadow of Two Colored Lights — Measured area 66.294% · moving frames 23/23Original by GODRICH · SCSS and React ship in the zip · See the password

LIGHT_A_HUELIGHT_B_HUELIGHT_GAPLIGHT_HEIGHT
        function lights(t) {
            const a = t * AXIS_SPEED;
            const dx = Math.cos(a) * LIGHT_GAP / 2;
            const dy = Math.sin(a) * LIGHT_GAP / 2;
            return [
                [clamp(cx + dx, 10, W - 10), clamp(cy + dy, 10, H - 10)],
                [clamp(cx - dx, 10, W - 10), clamp(cy - dy, 10, H - 10)]
            ];
        }
                    lctx.globalCompositeOperation = 'source-over';
                    lctx.clearRect(0, 0, W, H);
                    const g = lctx.createRadialGradient(ls[i][0], ls[i][1], 0, ls[i][0], ls[i][1], POOL_RADIUS);
                    g.addColorStop(0, lightColor(hues[i], 0.62));
                    g.addColorStop(1, lightColor(hues[i], 0.05));
                    lctx.fillStyle = g;
                    lctx.fillRect(0, 0, W, H);
                    lctx.globalCompositeOperation = 'destination-out';
                    lctx.fillStyle = 'rgba(0,0,0,1)';
                    for (let b = 0; b < BLOCKS.length; b += 1) {
                        const r = BLOCKS[b];
                        const poly = boxShadow(ls[i][0], ls[i][1], LIGHT_HEIGHT, r[0], r[1], r[2], r[3], r[4]);
                        polyPath(lctx, poly);
                        lctx.fill();
                    }
                    ctx.globalCompositeOperation = 'lighter';
                    ctx.drawImage(layer, 0, 0);
                }

09A Desk Hero Where the Sun Sets and a Lamp Turns On

One progress value lerps the sun height from 420 down to 70, switches the lamp on by smooth((p − 0.35) / 0.4) and fades the sun's opacity by 1 − 0.9·smooth((p − 0.55) / 0.45), so the same three boxes carry a sun shadow and a lamp shadow layered separately. At progress 0.75 the sun height is 124.7, the lamp is fully on and the sun opacity is 0.625, so both shadows show, and at 1 only the sun opacity of 0.1 remains. Measured at 70.1% accumulated area, 21.4 average intensity and 18/23 moved frames.

A Desk Hero Where the Sun Sets and a Lamp Turns On
Open in a new tab

09 · A Desk Hero Where the Sun Sets and a Lamp Turns On — Measured area 70.11% · moving frames 18/23Original by GODRICH · SCSS and React ship in the zip · See the password

DUSK_FRAMESLAMP_HEIGHTSUN_HEIGHT_DUSK
                const night = smooth(p);
                const lampOn = smooth((p - 0.35) / 0.4);
                const sunAlpha = 1 - 0.9 * smooth((p - 0.55) / 0.45);
                const desk = [lerp(DESK_DAY[0], DESK_NIGHT[0], night), lerp(DESK_DAY[1], DESK_NIGHT[1], night), lerp(DESK_DAY[2], DESK_NIGHT[2], night)];
                ctx.fillStyle = rgba(desk, 1);
                ctx.fillRect(0, 0, W, H);
                glow(ctx, SUN_SPOT[0] + 20, SUN_SPOT[1], 380, SUN_RGB, 0.7 * sunAlpha);
                glow(ctx, LAMP_SPOT[0], LAMP_SPOT[1], 280, LAMP_RGB, 0.9 * lampOn);
                const sunH = lerp(SUN_HEIGHT_NOON, SUN_HEIGHT_DUSK, smooth(p));
                for (let i = 0; i < ITEMS.length; i += 1) {
                    const r = ITEMS[i];
                    fillShadow(ctx, boxShadow(SUN_SPOT[0], SUN_SPOT[1], sunH, r[0], r[1], r[2], r[3], r[4]), INK_RGB, 0.4 * sunAlpha);
                    fillShadow(ctx, boxShadow(LAMP_SPOT[0], LAMP_SPOT[1], LAMP_HEIGHT, r[0], r[1], r[2], r[3], r[4]), INK_RGB, 0.5 * lampOn);
                }

Where the Values Change the Scene — Light Height, Ray Count, Gap Between Lights

Three constants reshape a scene: LIGHT_HEIGHT in 01, RAY_COUNT in 05 and LIGHT_GAP in 08. Softness is owned by BLUR_PER_HEIGHT in 03, and the opacity a designer would call css shadow transparency comes from SHADOW_ALPHA_BASE and ELEV_STEP in 06.

Lowering the Light Stretches the Shadow

At LIGHT_HEIGHT 220 the box top (height 70) has a factor of 220 / 150 = 1.47. Dropping it to 120 gives 120 / 50 = 2.4 and the shadow reaches far across the floor, while raising it to 400 gives 400 / 330 = 1.21 and the shadow hugs the box.

Doubling the Rays Leaves the Lit Shape Alone

Raising RAY_COUNT in 05 from 180 to 360 grows the polygon from 264 vertices (180 + 3 × 28) to 444, yet the lit shape is the same. The rays aimed at wall corners decide the outline, and the evenly spaced rays only split straight edges, so the one thing that grows is the ray-by-segment intersection cost.

Pulling the Lights Apart Shrinks the Overlap

Widening LIGHT_GAP from 160 to 260 spreads each box's two colored shadows (light hues 330 and 170) farther apart and shrinks the dark region where both lights are blocked. The two lights sit on an axis that turns around their midpoint at AXIS_SPEED 0.0008 radians per ms, and the gap opens along that axis.

All nine scenes were rendered and measured, and the table collects the numbers. Seven scenes pass 30% accumulated area, 01, 05, 08 and 09 pass 60%, and even 07, a single contact shadow, holds 16.8%, so no scene falls under 10%. Only 09 skips frames, at 18/23, and the other eight move on all 23.

Scene Accumulated area Average intensity Moved frames
01 Light orbit projection 63.4% 40.4 23/23
02 Pointer pillar row 35.0% 44.0 23/23
03 Blur by height 32.8% 44.9 23/23
04 Sundial 28.5% 37.1 23/23
05 Wall visibility polygon 65.6% 74.3 23/23
06 Elevation cards 41.6% 28.5 23/23
07 Contact shadow 16.8% 41.9 23/23
08 Two colored lights 66.3% 34.1 23/23
09 Lamp hero 70.1% 21.4 18/23

Where It Breaks — Narrow Screens, a Light Inside an Object, Low Contrast

A Light Lower Than the Object Makes the Factor Jump

In 01, once the light height falls to the box height of 70 or less, the denominator lh − h is clamped to 8 and the factor leaps to 70 / 8 = 8.75. The formula itself no longer holds for a light below the object, so the clamp is only a safety net, and the light should stay above the object. Scene 02 hits the same clamp first if you lower LIGHT_HEIGHT toward its tallest pillar of 101.

A Low Sun Gets Its Shadow Cut Off

In 04, a sun progress of 0.04 gives an elevation of 21.0°, where the natural length of 156.2 is cut to SHADOW_LEN_MAX 140. At progress 0.25 the elevation is 53.6° and the length 44.2, and at 0.5 it is 70° and 21.8, which makes midday the shortest shadow.

Narrow Wall Gaps Keep the Light Out

In 05, pushOut grows each wall rectangle by WALL_MARGIN (4) on every side and moves a light that lands inside to the nearest edge of the grown rectangle. A gap narrower than twice that margin (4 times 2 = 8) therefore cannot hold the light, and the light is kept out of the walls.

Near the Edge the Two Lights Draw Closer

Each light in 08 is clamped 10 px inside the canvas edge. When the midpoint nears an edge the real gap is smaller than LIGHT_GAP, so the same value gives a bigger overlap.

On a Dark Floor the Shadow Color Disappears

Scene 01 lays the shadow color [74, 56, 120] at 0.46 opacity over a light floor [243, 238, 250]. Move the same values onto a dark purple floor and the shadow sinks into it. The floor in 08 is a dark purple [58, 50, 86] only as a stage exception to show colored light overlapping, and since it adds light instead of painting a shadow, the overlap still reads there.

Narrow Screens and the Moved-Frame Count

The canvas keeps aspect-ratio 480 / 300 and only its width shrinks, and the overflow and phone-width overflow checks came out negative for all nine. The 18/23 in 09 comes from the demo path, which swings across the full canvas width and slows near both ends, so some captures differ very little from the one before.

Editing Constants to Find the Boundary

Change the box height in 01, the light height in 02, the cap in 04 and the margin in 05 one at a time to find the values where each break above starts. To try this on real files, open the zip below with the password 6bvjpvkj and edit these constants one by one.

Three Variants and Accessibility

Name Changed value Feel
Change the light height 01 LIGHT_HEIGHT 220 → 120 The projection factor of the box top rises from 1.47 (220 / 150) to 2.4 (120 / 50), so the shadow stretches much longer, while 400 drops the factor to 1.21 and shortens it.
Raise the ray count 05 RAY_COUNT 180 → 360 The polygon grows from 264 to 444 vertices but the lit shape stays identical, and the only thing that grows is the ray-by-segment intersection cost.
Change the gap between lights 08 LIGHT_GAP 160 → 260 Each box's two colored shadows (hues 330 and 170) separate by a wider margin and the dark overlap where both lights are blocked shrinks.

With prefers-reduced-motion set to reduce, none of the nine scenes starts its requestAnimationFrame loop. Each paints one still frame taken 1000 ms into the path, the button reads "Next scene", and each press jumps the path ahead 900 ms and draws it at once. Pointer moves, presses and arrow-key input are drawn immediately in the same way, without easing, and in this mode the light follows the pointer even without a press. All nine canvases carry tabindex 0, so they take focus, the arrow keys move the light 12 px per press, and Enter or Space acts like the button. One aria-live="polite" line reports the state, and a single <button> switches between pause and play.

Is box-shadow Enough for a CSS Shadow Effect on Image Cards?

It is enough when the light is fixed and the object is a rectangle. box-shadow needs only an offset, a blur and a color, and for a static grid of cards, adding light math just adds cost. Once the values must follow a light, calculation starts to pay, and that holds for a css shadow effect on div elements as much as for canvas art.

The scenes fit different places. Scene 01 suits a product box, 02 and 09 a landing hero background, 03 floating cards and floating buttons, 04 a section about times of day, 05 a dungeon map or room layout intro, 06 dashboard panels and stacked cards, 07 desk props and product photo compositing, and 08 an event banner lit like a stage. The three values of 06 carry over to CSS as they are, while 07 is an elliptical gradient, so take its opacity and radius formulas rather than a box-shadow value.

For objects drawn by rotation math, Dice Roll Animation: 9 Scenes with Top-Face Math and Spinning Top Animation: 9 Canvas Physics Scenes continue from here, and this page stops at one and two lights.

Frequently Asked Questions Before Building CSS Shadow Effects

Can a Plain Box Shadow Effect CSS Rule Fade Out Like a Gradient?

Scene 03 is that calculation. Each strip raises its blur and lowers its opacity in proportion to height and is filled with a vertical gradient, so the touching end stays dark and the raised end spreads wide and faint. When you move the values, start with BLUR_PER_HEIGHT and CONTACT_ALPHA.

Which Part Do I Edit to Fit My Own Page?

Open one scene's index.html from the zip and change the top constants, LIGHT_HEIGHT first and then the color arrays. The canvas keeps its 480 by 300 ratio, so only the CSS width needs adjusting for your slot.

Do I Need a Library or an Image?

No. Every box, label and shadow is painted through the canvas 2d context, and no library, image or outside address is called, so a single file is enough to run it.

Enter the archive password

The password is inside this article. You will find it as you read.