01Extruded Letters Whose Layer Value Stacks Into Depth
Shadows are stacked one small offset after another in the same direction, so as the layer-count value grows, the letters read as a thicker 3D extrude.
Best forLanding page hero title
SHADOW_LAYER_COUNTbuildExtrudeShadowrenderExtrude

<main class="stage">
<div class="ex is-demo" id="ex">
<p class="ex__badge"><span class="ex__badgeLabel">Extrude depth</span> <span class="ex__badgeValue" id="exValue">1.0px</span></p>
<h1 class="ex__word" id="exWord">Extrude</h1>
</div>
</main>@use "../../../../site/static/demo-tokens" as *;
// 핵심: step 값 하나로 SHADOW_LAYER_COUNT 층의 그림자를 조립한다. step 이 작을 때는
// 얇고 납작한 입체, 클 때는 층이 겹겹이 쌓여 두꺼운 입체로 보인다.
$duration: $dur-loop;
$easing: linear;
$color: $subject-cream;
@include stage($stage-ink);
.ex {
display: grid;
gap: $sp-4;
justify-items: center;
text-align: center;
}
.ex__badge {
margin: 0;
padding: $sp-1 $sp-3;
border-radius: $r-pill;
background: rgba(255, 247, 230, .1);
color: $subject-cream;
font-weight: 700;
font-size: 11px;
font-variant-numeric: tabular-nums;
}
.ex__word {
margin: 0;
font-weight: 800;
font-size: clamp(64px, 22vw, 132px);
line-height: 1;
white-space: nowrap;
color: $color;
will-change: text-shadow;
}
@media (max-width: 340px) {
.ex__word { font-size: clamp(48px, 24vw, 88px); }
}
@media (prefers-reduced-motion: reduce) {
.ex__word { transition: none; }
}html, body {
margin: 0;
overflow: hidden;
}
.stage {
box-sizing: border-box;
min-height: 100vh;
display: grid;
place-items: center;
margin: 0;
background: #17141a;
font-family: -apple-system, "Segoe UI", "Noto Sans KR", sans-serif;
padding: clamp(8px, 4vw, 24px);
}
.ex {
display: grid;
gap: 16px;
justify-items: center;
text-align: center;
}
.ex__badge {
margin: 0;
padding: 4px 12px;
border-radius: 999px;
background: rgba(255, 247, 230, 0.1);
color: #fff7e6;
font-weight: 700;
font-size: 11px;
font-variant-numeric: tabular-nums;
}
.ex__word {
margin: 0;
font-weight: 800;
font-size: clamp(64px, 22vw, 132px);
line-height: 1;
white-space: nowrap;
color: #fff7e6;
will-change: text-shadow;
}
@media (max-width: 340px) {
.ex__word {
font-size: clamp(48px, 24vw, 88px);
}
}
@media (prefers-reduced-motion: reduce) {
.ex__word {
transition: none;
}
}(function () {
var SHADOW_LAYER_COUNT = 6;
var STEP_MIN_PX = 1;
var STEP_MAX_PX = 15;
var PERIOD_MS = 1000;
var word = document.getElementById('exWord');
var value = document.getElementById('exValue');
var reduce = window.matchMedia('(prefers-reduced-motion: reduce)').matches;
// 계산 레이어: 층 번호(i)가 깊어질수록 명도를 낮춰 아래로 갈수록 어두워지는 입체감을 만든다
function extrudeColor(i, n) {
var t = i / n;
var l = Math.max(6, Math.round(24 - t * 16));
return 'hsl(232 14% ' + l + '%)';
}
// 값(step) 하나로 SHADOW_LAYER_COUNT 개의 그림자를 문자열로 조립한다
function buildExtrudeShadow(step) {
var parts = [];
for (var i = 1; i <= SHADOW_LAYER_COUNT; i++) {
var d = (i * step).toFixed(2);
parts.push(d + 'px ' + d + 'px 0 ' + extrudeColor(i, SHADOW_LAYER_COUNT));
}
return parts.join(', ');
}
function renderExtrude(step) {
word.style.textShadow = buildExtrudeShadow(step);
value.textContent = step.toFixed(1) + 'px';
}
var startTime = null;
function loop(now) {
if (startTime === null) startTime = now;
var t = (now - startTime) % PERIOD_MS;
var phase = (Math.sin((t / PERIOD_MS) * Math.PI * 2 - Math.PI / 2) + 1) / 2;
renderExtrude(STEP_MIN_PX + (STEP_MAX_PX - STEP_MIN_PX) * phase);
requestAnimationFrame(loop);
}
if (reduce) {
renderExtrude(STEP_MAX_PX);
} else {
requestAnimationFrame(loop);
}
})();// TextShadowStackExtrude3D — vanilla 판(run/412/demo/01-text-shadow-stack-extrude-3d/index.html) 의 React 포트.
// 값(step) 하나로 SHADOW_LAYER_COUNT 층의 그림자를 조립한다. step 이 작을 때는 얇은 입체,
// 클 때는 층이 겹겹이 쌓여 두꺼운 입체로 보인다.
import { useEffect, useRef, useState } from "react";
import styles from "./TextShadowStackExtrude3D.module.scss";
type Box<T> = { current: T };
const SHADOW_LAYER_COUNT = 6;
const STEP_MIN_PX = 1;
const STEP_MAX_PX = 15;
const PERIOD_MS = 1000;
function extrudeColor(i: number, n: number): string {
const t = i / n;
const l = Math.max(6, Math.round(24 - t * 16));
return "hsl(232 14% " + l + "%)";
}
function buildExtrudeShadow(step: number): string {
const parts: string[] = [];
for (let i = 1; i <= SHADOW_LAYER_COUNT; i++) {
const d = (i * step).toFixed(2);
parts.push(d + "px " + d + "px 0 " + extrudeColor(i, SHADOW_LAYER_COUNT));
}
return parts.join(", ");
}
export default function TextShadowStackExtrude3D() {
const wordRef = useRef<HTMLHeadingElement | null>(null);
const valueRef = useRef<HTMLSpanElement | null>(null);
const t0Ref = useRef<number>(0) as Box<number>;
const frameRef = useRef<number>(0) as Box<number>;
const [demo] = useState(true);
const [reduce, setReduce] = useState(false);
function render(step: number): void {
const word = wordRef.current;
const value = valueRef.current;
if (!word || !value) return;
word.style.textShadow = buildExtrudeShadow(step);
value.textContent = step.toFixed(1) + "px";
}
useEffect(() => {
setReduce(window.matchMedia("(prefers-reduced-motion: reduce)").matches);
}, []);
useEffect(() => {
if (reduce) {
render(STEP_MAX_PX);
return;
}
function loop(now: number): void {
if (t0Ref.current === 0) t0Ref.current = now;
const t = (now - t0Ref.current) % PERIOD_MS;
const phase = (Math.sin((t / PERIOD_MS) * Math.PI * 2 - Math.PI / 2) + 1) / 2;
render(STEP_MIN_PX + (STEP_MAX_PX - STEP_MIN_PX) * phase);
frameRef.current = requestAnimationFrame(loop);
}
frameRef.current = requestAnimationFrame(loop);
return () => cancelAnimationFrame(frameRef.current);
}, [reduce]);
const cls = [styles.ex, demo ? styles.isDemo : ""].filter(Boolean).join(" ");
return (
<main className={styles.stage}>
<div className={cls}>
<p className={styles.badge}><span>돌출 깊이</span> <span ref={valueRef}>1.0px</span></p>
<h1 className={styles.word} ref={wordRef}>돌출</h1>
</div>
</main>
);
}01 Extruded Letters Whose Layer Value Stacks Into Depth — Measured area 10.384% · moving frames 22/23Original by GODRICH · SCSS and React ship in the zip · CSS Text Shadow Effects — 9 Patterns
The post this demo lives in
CSS Text Shadow Effects — 9 Patternscss text shadow effects: A shadow is where light goes missing, yet these nine letters shine by shadow alone — item 1 sta See all demos in this post


