소개
학생들이 제일 어려워하는 position에 대해서 어떻게 강의를 하면 좋을지 고민하던 중 만들게 되었습니다.
진행 방법
어떤 도구를 사용했고, 어떻게 활용하셨나요?
claude를 사용했습니다. 컴퓨터를 이리저리 옮겨 다니면서 자료를 만들다보니 claude code를 사용하지 못 했습니다.
Tip: 사용한 프롬프트 전문을 꼭 포함하고, 내용을 짧게 소개해 주세요.
한국어로 된 문자 메시지의 스크린샷
Tip: 활용 이미지나 캡처 화면을 꼭 남겨주세요.
위치
Tip: 코드 전문은 코드블록에 감싸서 작성해주세요. ( / 을 눌러 '코드 블록'을 선택)
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8"/>
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
<title>CSS Position 완전 정복</title>
<link href="https://fonts.googleapis.com/css2?family=Noto+Sans+KR:wght@300;400;500;700;900&family=JetBrains+Mono:wght@400;500;700&display=swap" rel="stylesheet"/>
<style>
:root {
--bg: #111318;
--surface: #1a1d24;
--surface2: #20242e;
--border: #2c3040;
--text: #e2e6f0;
--muted: #6b728a;
--static-c: #94a3b8;
--relative-c:#60a5fa;
--absolute-c:#f472b6;
--fixed-c: #34d399;
--sticky-c: #fbbf24;
--static-bg: rgba(148,163,184,0.08);
--relative-bg:rgba(96,165,250,0.08);
--absolute-bg:rgba(244,114,182,0.08);
--fixed-bg: rgba(52,211,153,0.08);
--sticky-bg: rgba(251,191,36,0.08);
--static-bd: rgba(148,163,184,0.25);
--relative-bd:rgba(96,165,250,0.25);
--absolute-bd:rgba(244,114,182,0.25);
--fixed-bd: rgba(52,211,153,0.25);
--sticky-bd: rgba(251,191,36,0.25);
}
*{box-sizing:border-box;margin:0;padding:0;}
html { scroll-behavior: smooth; }
body {
background: var(--bg);
color: var(--text);
font-family: 'Noto Sans KR', sans-serif;
line-height: 1.7;
overflow-x: hidden;
}
/* 배경 노이즈 */
body::before {
content:'';
position:fixed;
inset:0;
background-image:
radial-gradient(circle at 15% 20%, rgba(96,165,250,0.06) 0%, transparent 40%),
radial-gradient(circle at 85% 70%, rgba(244,114,182,0.06) 0%, transparent 40%),
radial-gradient(circle at 50% 50%, rgba(52,211,153,0.04) 0%, transparent 50%);
pointer-events:none;
z-index:0;
}
/* ── 히어로 ── */
.hero {
position: relative;
z-index: 1;
padding: 80px 24px 72px;
text-align: center;
border-bottom: 1px solid var(--border);
}
.hero-kicker {
font-family: 'JetBrains Mono', monospace;
font-size: 11px;
letter-spacing: .25em;
text-transform: uppercase;
color: var(--muted);
margin-bottom: 20px;
}
.hero h1 {
font-size: clamp(2.4rem,6vw,4rem);
font-weight: 900;
letter-spacing: -.04em;
line-height: 1.05;
margin-bottom: 20px;
}
.hero h1 .word {
display: inline-block;
transition: color .2s;
}
.hero h1 .w1 { color: var(--relative-c); }
.hero h1 .w2 { color: var(--absolute-c); }
.hero h1 .w3 { color: var(--fixed-c); }
.hero h1 .w4 { color: var(--sticky-c); }
.hero-sub {
font-size: 15px;
color: var(--muted);
max-width: 520px;
margin: 0 auto 32px;
line-height: 1.85;
}
/* 빠른 이동 네비 */
.quick-nav {
display: flex;
justify-content: center;
gap: 8px;
flex-wrap: wrap;
}
.qn-btn {
padding: 7px 16px;
border-radius: 20px;
border: 1px solid;
background: transparent;
font-family: 'JetBrains Mono', monospace;
font-size: 12px;
cursor: pointer;
transition: all .2s;
text-decoration: none;
}
.qn-static { border-color: var(--static-bd); color: var(--static-c); }
.qn-relative { border-color: var(--relative-bd); color: var(--relative-c); }
.qn-absolute { border-color: var(--absolute-bd); color: var(--absolute-c); }
.qn-fixed { border-color: var(--fixed-bd); color: var(--fixed-c); }
.qn-sticky { border-color: var(--sticky-bd); color: var(--sticky-c); }
.qn-btn:hover { filter: brightness(1.3); transform: translateY(-2px); }
/* ── 래퍼 ── */
.wrapper {
position: relative;
z-index: 1;
max-width: 920px;
margin: 0 auto;
padding: 0 24px 100px;
}
/* ── 섹션 헤더 ── */
.section { margin-top: 72px; }
.section-header {
display: flex;
align-items: center;
gap: 16px;
margin-bottom: 24px;
padding-bottom: 20px;
border-bottom: 1px solid var(--border);
}
.pos-badge {
font-family: 'JetBrains Mono', monospace;
font-size: 13px;
font-weight: 700;
padding: 6px 18px;
border-radius: 8px;
border: 1.5px solid;
letter-spacing: .05em;
}
.badge-static { background:var(--static-bg); border-color:var(--static-bd); color:var(--static-c); }
.badge-relative { background:var(--relative-bg); border-color:var(--relative-bd); color:var(--relative-c); }
.badge-absolute { background:var(--absolute-bg); border-color:var(--absolute-bd); color:var(--absolute-c); }
.badge-fixed { background:var(--fixed-bg); border-color:var(--fixed-bd); color:var(--fixed-c); }
.badge-sticky { background:var(--sticky-bg); border-color:var(--sticky-bd); color:var(--sticky-c); }
.section-title-wrap h2 {
font-size: 1.5rem;
font-weight: 800;
letter-spacing: -.02em;
margin-bottom: 2px;
}
.section-title-wrap p {
font-size: 13px;
color: var(--muted);
}
/* ── 설명 카드 ── */
.desc-card {
background: var(--surface);
border: 1px solid var(--border);
border-radius: 14px;
padding: 22px 26px;
margin-bottom: 18px;
font-size: 14.5px;
line-height: 1.9;
color: #c5cce0;
}
.desc-card strong { color: var(--text); }
.key-point {
display: inline-flex;
align-items: center;
gap: 8px;
margin-top: 14px;
padding: 10px 16px;
border-radius: 8px;
font-size: 13.5px;
font-weight: 600;
}
.kp-static { background:var(--static-bg); border:1px solid var(--static-bd); color:var(--static-c); }
.kp-relative { background:var(--relative-bg); border:1px solid var(--relative-bd); color:var(--relative-c); }
.kp-absolute { background:var(--absolute-bg); border:1px solid var(--absolute-bd); color:var(--absolute-c); }
.kp-fixed { background:var(--fixed-bg); border:1px solid var(--fixed-bd); color:var(--fixed-c); }
.kp-sticky { background:var(--sticky-bg); border:1px solid var(--sticky-bd); color:var(--sticky-c); }
/* ── 코드 블록 ── */
.code-block {
background: #0d0f14;
border: 1px solid var(--border);
border-radius: 12px;
overflow: hidden;
margin-bottom: 18px;
}
.cb-bar {
display: flex;
align-items: center;
gap: 7px;
padding: 10px 16px;
background: rgba(255,255,255,0.03);
border-bottom: 1px solid var(--border);
}
.dot{width:10px;height:10px;border-radius:50%;}
.dr{background:#f38ba8;}.dy{background:#f9e2af;}.dg{background:#a6e3a1;}
.cb-lang {
font-family:'JetBrains Mono',monospace;
font-size:11px;color:var(--muted);
margin-left:auto;letter-spacing:.1em;
}
pre {
padding: 20px 22px;
font-family: 'JetBrains Mono', monospace;
font-size: 13px;
line-height: 2;
overflow-x: auto;
color: #cdd6f4;
}
.kw { color: #cba6f7; }
.pr { color: #89b4fa; }
.vl { color: #a6e3a1; }
.vl2 { color: #fab387; }
.cm { color: #45475a; font-style: italic; }
.sel { color: #89dceb; }
.hl-static { color: var(--static-c); font-weight:600; }
.hl-relative { color: var(--relative-c); font-weight:600; }
.hl-absolute { color: var(--absolute-c); font-weight:600; }
.hl-fixed { color: var(--fixed-c); font-weight:600; }
.hl-sticky { color: var(--sticky-c); font-weight:600; }
/* ── 인터랙티브 데모 ── */
.demo-wrap {
background: var(--surface);
border: 1px solid var(--border);
border-radius: 14px;
overflow: hidden;
margin-bottom: 18px;
}
.demo-header {
padding: 12px 18px;
background: rgba(255,255,255,0.03);
border-bottom: 1px solid var(--border);
font-family: 'JetBrains Mono', monospace;
font-size: 11px;
color: var(--muted);
letter-spacing: .12em;
text-transform: uppercase;
}
.demo-body { padding: 24px; }
/* static 데모 */
.demo-static-flow {
display: flex;
flex-direction: column;
gap: 6px;
}
.flow-box {
padding: 12px 18px;
border-radius: 8px;
font-family: 'JetBrains Mono', monospace;
font-size: 13px;
background: var(--static-bg);
border: 1px solid var(--static-bd);
color: var(--static-c);
}
/* relative 데모 */
.demo-relative-area {
position: relative;
height: 120px;
background: var(--surface2);
border-radius: 10px;
display: flex;
align-items: center;
padding: 0 20px;
gap: 10px;
}
.rel-box-normal {
padding: 10px 16px;
border-radius: 6px;
background: rgba(100,116,139,0.2);
border: 1px dashed #475569;
color: #64748b;
font-family: 'JetBrains Mono', monospace;
font-size: 12px;
}
.rel-box-moved {
position: relative;
top: -20px;
left: 10px;
padding: 10px 16px;
border-radius: 6px;
background: var(--relative-bg);
border: 2px solid var(--relative-bd);
color: var(--relative-c);
font-family: 'JetBrains Mono', monospace;
font-size: 12px;
font-weight: 600;
}
.rel-ghost {
position: relative;
padding: 10px 16px;
border-radius: 6px;
border: 1px dashed var(--relative-bd);
color: rgba(96,165,250,0.3);
font-family: 'JetBrains Mono', monospace;
font-size: 12px;
}
/* absolute 데모 */
.demo-absolute-area {
position: relative;
height: 160px;
background: var(--surface2);
border-radius: 10px;
border: 2px dashed #475569;
}
.abs-parent-label {
position: absolute;
top: 8px; left: 12px;
font-family: 'JetBrains Mono', monospace;
font-size: 10px;
color: #475569;
letter-spacing: .1em;
}
.abs-box {
position: absolute;
padding: 10px 16px;
border-radius: 6px;
background: var(--absolute-bg);
border: 2px solid var(--absolute-bd);
color: var(--absolute-c);
font-family: 'JetBrains Mono', monospace;
font-size: 12px;
font-weight: 600;
}
/* fixed 데모 — 미니 뷰포트 시뮬레이션 */
.demo-fixed-viewport {
position: relative;
height: 180px;
background: var(--surface2);
border-radius: 10px;
border: 2px solid var(--border);
overflow: hidden;
}
.vp-label {
position: absolute;
top: 8px; left: 12px;
font-family: 'JetBrains Mono', monospace;
font-size: 10px;
color: var(--muted);
letter-spacing: .1em;
}
.vp-scroll-content {
padding: 40px 20px 20px;
overflow-y: auto;
height: 100%;
}
.vp-text-line {
height: 14px;
border-radius: 4px;
background: rgba(255,255,255,0.06);
margin-bottom: 10px;
}
.fixed-sim-box {
position: absolute;
bottom: 14px; right: 14px;
padding: 8px 14px;
border-radius: 8px;
background: var(--fixed-bg);
border: 2px solid var(--fixed-bd);
color: var(--fixed-c);
font-family: 'JetBrains Mono', monospace;
font-size: 11px;
font-weight: 700;
animation: fixedPulse 2s ease-in-out infinite;
}
@keyframes fixedPulse {
0%,100% { box-shadow: 0 0 0 0 rgba(52,211,153,0.2); }
50% { box-shadow: 0 0 0 8px rgba(52,211,153,0); }
}
/* sticky 데모 */
.demo-sticky-area {
height: 200px;
overflow-y: auto;
background: var(--surface2);
border-radius: 10px;
border: 1px solid var(--border);
}
.sticky-header {
position: sticky;
top: 0;
background: var(--sticky-bg);
border-bottom: 2px solid var(--sticky-bd);
color: var(--sticky-c);
font-family: 'JetBrains Mono', monospace;
font-size: 13px;
font-weight: 700;
padding: 12px 18px;
z-index: 10;
}
.sticky-content-item {
padding: 14px 18px;
border-bottom: 1px solid rgba(255,255,255,0.04);
font-size: 13px;
color: var(--muted);
}
/* ── 비교표 ── */
.compare-table {
width: 100%;
border-collapse: collapse;
font-size: 13.5px;
background: var(--surface);
border: 1px solid var(--border);
border-radius: 14px;
overflow: hidden;
}
.compare-table th {
padding: 14px 16px;
text-align: left;
font-family: 'JetBrains Mono', monospace;
font-size: 11px;
letter-spacing: .12em;
text-transform: uppercase;
color: var(--muted);
background: var(--surface2);
border-bottom: 1px solid var(--border);
}
.compare-table td {
padding: 14px 16px;
border-bottom: 1px solid rgba(255,255,255,0.04);
color: #b0b8d0;
vertical-align: middle;
}
.compare-table tr:last-child td { border-bottom: none; }
.compare-table tr:hover td { background: rgba(255,255,255,0.02); }
.compare-table td:first-child {
font-family: 'JetBrains Mono', monospace;
font-weight: 700;
font-size: 13px;
}
.ct-static td:first-child { color: var(--static-c); }
.ct-relative td:first-child { color: var(--relative-c); }
.ct-absolute td:first-child { color: var(--absolute-c); }
.ct-fixed td:first-child { color: var(--fixed-c); }
.ct-sticky td:first-child { color: var(--sticky-c); }
.tc-yes { color: #86efac; font-family:'JetBrains Mono',monospace;font-size:12px; }
.tc-no { color: #fca5a5; font-family:'JetBrains Mono',monospace;font-size:12px; }
.tc-cond{ color: #fde68a; font-family:'JetBrains Mono',monospace;font-size:12px; }
/* ── 핵심 정리 카드 ── */
.summary-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(160px, 1fr));
gap: 14px;
margin-top: 56px;
}
.sum-card {
background: var(--surface);
border: 1px solid var(--border);
border-radius: 14px;
padding: 20px;
transition: transform .2s;
}
.sum-card:hover { transform: translateY(-4px); }
.sum-card .sc-dot {
width: 10px; height: 10px;
border-radius: 50%;
margin-bottom: 12px;
}
.sc-static .sc-dot { background: var(--static-c); }
.sc-relative .sc-dot { background: var(--relative-c); }
.sc-absolute .sc-dot { background: var(--absolute-c); }
.sc-fixed .sc-dot { background: var(--fixed-c); }
.sc-sticky .sc-dot { background: var(--sticky-c); }
.sum-card .sc-name {
font-family: 'JetBrains Mono', monospace;
font-size: 14px;
font-weight: 700;
margin-bottom: 8px;
}
.sc-static .sc-name { color: var(--static-c); }
.sc-relative .sc-name { color: var(--relative-c); }
.sc-absolute .sc-name { color: var(--absolute-c); }
.sc-fixed .sc-name { color: var(--fixed-c); }
.sc-sticky .sc-name { color: var(--sticky-c); }
.sum-card .sc-desc {
font-size: 12.5px;
color: var(--muted);
line-height: 1.7;
}
.sum-card .sc-when {
margin-top: 10px;
font-size: 11.5px;
color: var(--muted);
border-top: 1px solid var(--border);
padding-top: 10px;
}
/* ── 애니메이션 ── */
@keyframes fadeUp {
from { opacity:0; transform:translateY(24px); }
to { opacity:1; transform:translateY(0); }
}
.section { animation: fadeUp .55s ease both; }
.section:nth-child(1) { animation-delay:.05s; }
.section:nth-child(2) { animation-delay:.1s; }
.section:nth-child(3) { animation-delay:.15s; }
.section:nth-child(4) { animation-delay:.2s; }
.section:nth-child(5) { animation-delay:.25s; }
.section:nth-child(6) { animation-delay:.3s; }
.section:nth-child(7) { animation-delay:.35s; }
/* 반응형 */
@media (max-width:600px) {
.compare-table th:nth-child(4),
.compare-table td:nth-child(4) { display:none; }
}
/* 데모 토글 버튼 */
.demo-toggle-row {
display: flex;
gap: 8px;
flex-wrap: wrap;
margin-bottom: 14px;
}
.demo-toggle {
padding: 6px 14px;
border-radius: 6px;
border: 1px solid var(--border);
background: transparent;
color: var(--muted);
font-family: 'JetBrains Mono', monospace;
font-size: 12px;
cursor: pointer;
transition: all .2s;
}
.demo-toggle.on {
background: var(--relative-bg);
border-color: var(--relative-bd);
color: var(--relative-c);
}
/* absolute 위치 컨트롤 */
.abs-controls {
display: flex;
gap: 10px;
flex-wrap: wrap;
margin-top: 12px;
}
.abs-ctrl-btn {
padding: 6px 14px;
border-radius: 6px;
border: 1px solid var(--absolute-bd);
background: var(--absolute-bg);
color: var(--absolute-c);
font-family: 'JetBrains Mono', monospace;
font-size: 11px;
cursor: pointer;
transition: all .15s;
}
.abs-ctrl-btn:hover { filter: brightness(1.3); }
.abs-ctrl-btn.active {
background: rgba(244,114,182,0.25);
font-weight: 700;
}
</style>
</head>
<body>
<!-- 히어로 -->
<div class="hero">
<div class="hero-kicker">CSS · Layout · Positioning</div>
<h1>
<span class="word w1">position</span>
<span style="color:var(--muted);font-weight:300;"> 완전 정복</span>
</h1>
<p class="hero-sub">
static · relative · absolute · fixed · sticky<br/>
5가지 포지션을 인터랙티브 예제와 함께 완벽히 이해해요!
</p>
<div class="quick-nav">
<a href="#sec-static" class="qn-btn qn-static">static</a>
<a href="#sec-relative" class="qn-btn qn-relative">relative</a>
<a href="#sec-absolute" class="qn-btn qn-absolute">absolute</a>
<a href="#sec-fixed" class="qn-btn qn-fixed">fixed</a>
<a href="#sec-sticky" class="qn-btn qn-sticky">sticky</a>
</div>
</div>
<div class="wrapper">
<!-- ─── STATIC ─── -->
<div class="section" id="sec-static">
<div class="section-header">
<div class="pos-badge badge-static">static</div>
<div class="section-title-wrap">
<h2>기본값 — 흐름대로</h2>
<p>position을 지정하지 않으면 기본 적용되는 값</p>
</div>
</div>
<div class="desc-card">
<strong>position: static</strong>은 모든 요소의 기본값이에요.<br/>
HTML 문서의 <strong>일반 흐름(Normal Flow)</strong>에 따라 위에서 아래, 왼쪽에서 오른쪽으로 차례로 배치됩니다.<br/>
<code style="font-family:'JetBrains Mono',monospace;background:var(--surface2);padding:2px 7px;border-radius:4px;font-size:13px;">top · right · bottom · left · z-index</code> 속성이 <strong>적용되지 않아요.</strong>
<br/>
<div class="key-point kp-static">📌 top/left 효과 없음 · z-index 무시 · 일반 흐름 유지</div>
</div>
<div class="code-block">
<div class="cb-bar"><div class="dot dr"></div><div class="dot dy"></div><div class="dot dg"></div><div class="cb-lang">CSS</div></div>
<pre><span class="sel">.box</span> {
<span class="pr">position</span>: <span class="hl-static">static</span>; <span class="cm">/* 기본값 — 생략 가능 */</span>
<span class="cm">/* top: 20px; ← 아무 효과 없음! */</span>
<span class="cm">/* z-index: 10; ← 아무 효과 없음! */</span>
}</pre>
</div>
<div class="demo-wrap">
<div class="demo-header">🖥 데모 — 일반 흐름으로 순서대로 쌓임</div>
<div class="demo-body">
<div class="demo-static-flow">
<div class="flow-box">Box 1 — position: static (기본)</div>
<div class="flow-box">Box 2 — position: static (기본)</div>
<div class="flow-box">Box 3 — position: static (기본)</div>
</div>
<p style="font-size:12px;color:var(--muted);margin-top:12px;">↑ 위에서 아래로, 선언 순서대로 배치됩니다.</p>
</div>
</div>
</div>
<!-- ─── RELATIVE ─── -->
<div class="section" id="sec-relative">
<div class="section-header">
<div class="pos-badge badge-relative">relative</div>
<div class="section-title-wrap">
<h2>자기 자신 기준으로 이동</h2>
<p>원래 있던 자리를 유지하며 상대적으로 이동</p>
</div>
</div>
<div class="desc-card">
<strong>position: relative</strong>는 요소가 원래 있던 자리(static 위치)를 <strong>기준</strong>으로 이동해요.<br/>
중요한 점은 <strong>원래 자리가 그대로 유지</strong>된다는 것! 빈 공간이 남아요.<br/>
<code style="font-family:'JetBrains Mono',monospace;background:var(--surface2);padding:2px 7px;border-radius:4px;font-size:13px;">top · right · bottom · left</code>로 이동 거리를 지정해요.
<br/>
<div class="key-point kp-relative">📌 원래 자리 유지 · 자신의 위치 기준 이동 · absolute의 기준점이 됨</div>
</div>
<div class="code-block">
<div class="cb-bar"><div class="dot dr"></div><div class="dot dy"></div><div class="dot dg"></div><div class="cb-lang">CSS</div></div>
<pre><span class="sel">.box</span> {
<span class="pr">position</span>: <span class="hl-relative">relative</span>;
<span class="pr">top</span>: <span class="vl">-20px</span>; <span class="cm">/* 원래 자리에서 위로 20px */</span>
<span class="pr">left</span>: <span class="vl">10px</span>; <span class="cm">/* 원래 자리에서 오른쪽 10px */</span>
}</pre>
</div>
<div class="demo-wrap">
<div class="demo-header">🖥 데모 — 가운데 박스가 relative로 이동</div>
<div class="demo-body">
<div class="demo-relative-area">
<div class="rel-box-normal">Box 1</div>
<div style="position:relative;display:inline-flex;flex-direction:column;">
<div class="rel-ghost">Box 2 (원래 자리)</div>
<div class="rel-box-moved">Box 2 ↖ relative</div>
</div>
<div class="rel-box-normal">Box 3</div>
</div>
<p style="font-size:12px;color:var(--muted);margin-top:12px;">↑ Box 2가 이동해도 원래 자리(점선)는 그대로 유지돼요.</p>
</div>
</div>
</div>
<!-- ─── ABSOLUTE ─── -->
<div class="section" id="sec-absolute">
<div class="section-header">
<div class="pos-badge badge-absolute">absolute</div>
<div class="section-title-wrap">
<h2>부모 기준으로 자유롭게 배치</h2>
<p>가장 가까운 positioned 부모 기준으로 위치 결정</p>
</div>
</div>
<div class="desc-card">
<strong>position: absolute</strong>는 <strong>일반 흐름에서 완전히 벗어나요.</strong> 빈 자리가 생기지 않아요.<br/>
<strong>가장 가까운 position이 static이 아닌 부모</strong>를 기준으로 배치돼요.<br/>
부모 중 해당하는 게 없으면 <strong>브라우저 전체(html)</strong>가 기준이 됩니다.<br/>
<div class="key-point kp-absolute">📌 흐름 이탈 · relative 부모 기준 · top/left로 정확한 위치 지정</div>
</div>
<div class="code-block">
<div class="cb-bar"><div class="dot dr"></div><div class="dot dy"></div><div class="dot dg"></div><div class="cb-lang">CSS</div></div>
<pre><span class="cm">/* 부모에 relative 필수! */</span>
<span class="sel">.parent</span> {
<span class="pr">position</span>: <span class="hl-relative">relative</span>;
}
<span class="sel">.child</span> {
<span class="pr">position</span>: <span class="hl-absolute">absolute</span>;
<span class="pr">top</span>: <span class="vl">20px</span>; <span class="cm">/* 부모 위쪽 끝에서 20px */</span>
<span class="pr">right</span>: <span class="vl">20px</span>; <span class="cm">/* 부모 오른쪽 끝에서 20px */</span>
}</pre>
</div>
<div class="demo-wrap">
<div class="demo-header">🖥 데모 — 부모(점선 박스) 안에서 자유 배치</div>
<div class="demo-body">
<div class="abs-controls">
<button class="abs-ctrl-btn active" id="abs-btn-tr" onclick="moveAbs('tr')">우상단</button>
<button class="abs-ctrl-btn" id="abs-btn-tl" onclick="moveAbs('tl')">좌상단</button>
<button class="abs-ctrl-btn" id="abs-btn-br" onclick="moveAbs('br')">우하단</button>
<button class="abs-ctrl-btn" id="abs-btn-center" onclick="moveAbs('center')">가운데</button>
</div>
<div class="demo-absolute-area" style="margin-top:10px;">
<span class="abs-parent-label">PARENT (position: relative)</span>
<div class="abs-box" id="abs-box" style="top:20px;right:20px;">
absolute
</div>
</div>
<p style="font-size:12px;color:var(--muted);margin-top:10px;">↑ 버튼을 눌러 absolute 요소의 위치를 바꿔보세요!</p>
</div>
</div>
</div>
<!-- ─── FIXED ─── -->
<div class="section" id="sec-fixed">
<div class="section-header">
<div class="pos-badge badge-fixed">fixed</div>
<div class="section-title-wrap">
<h2>화면(뷰포트)에 고정</h2>
<p>스크롤해도 항상 같은 자리에 고정</p>
</div>
</div>
<div class="desc-card">
<strong>position: fixed</strong>는 <strong>브라우저 뷰포트(화면)</strong>를 기준으로 위치를 고정해요.<br/>
페이지를 아무리 스크롤해도 <strong>화면에서 움직이지 않아요.</strong><br/>
내비게이션 바, 플로팅 버튼, 쿠키 배너 같은 요소에 자주 사용해요.<br/>
<div class="key-point kp-fixed">📌 뷰포트 기준 · 스크롤 무관 고정 · 흐름 이탈 · z-index 필요</div>
</div>
<div class="code-block">
<div class="cb-bar"><div class="dot dr"></div><div class="dot dy"></div><div class="dot dg"></div><div class="cb-lang">CSS</div></div>
<pre><span class="cm">/* 화면 우하단에 고정 버튼 */</span>
<span class="sel">.float-btn</span> {
<span class="pr">position</span>: <span class="hl-fixed">fixed</span>;
<span class="pr">bottom</span>: <span class="vl">20px</span>;
<span class="pr">right</span>: <span class="vl">20px</span>;
<span class="pr">z-index</span>: <span class="vl2">999</span>; <span class="cm">/* 항상 위에 표시 */</span>
}
<span class="cm">/* 화면 상단 고정 헤더 */</span>
<span class="sel">.navbar</span> {
<span class="pr">position</span>: <span class="hl-fixed">fixed</span>;
<span class="pr">top</span>: <span class="vl">0</span>;
<span class="pr">left</span>: <span class="vl">0</span>;
<span class="pr">right</span>: <span class="vl">0</span>;
}</pre>
</div>
<div class="demo-wrap">
<div class="demo-header">🖥 데모 — 초록 박스는 스크롤해도 항상 고정</div>
<div class="demo-body">
<div class="demo-fixed-viewport">
<span class="vp-label">VIEWPORT (브라우저 화면)</span>
<div class="vp-scroll-content">
<div class="vp-text-line" style="width:80%"></div>
<div class="vp-text-line" style="width:95%"></div>
<div class="vp-text-line" style="width:70%"></div>
<div class="vp-text-line" style="width:85%"></div>
<div class="vp-text-line" style="width:60%"></div>
<div class="vp-text-line" style="width:90%"></div>
</div>
<div class="fixed-sim-box">FIXED ✦</div>
</div>
<p style="font-size:12px;color:var(--muted);margin-top:10px;">↑ 스크롤이 발생해도 초록 박스는 항상 같은 위치에 고정돼요.</p>
</div>
</div>
</div>
<!-- ─── STICKY ─── -->
<div class="section" id="sec-sticky">
<div class="section-header">
<div class="pos-badge badge-sticky">sticky</div>
<div class="section-title-wrap">
<h2>스크롤하다 걸리면 고정</h2>
<p>relative + fixed의 혼합! 임계점에 도달하면 고정</p>
</div>
</div>
<div class="desc-card">
<strong>position: sticky</strong>는 <strong>relative와 fixed의 혼합</strong>이에요.<br/>
평소엔 relative처럼 일반 흐름을 따르다가, <strong>스크롤이 지정한 임계점에 도달하면 fixed처럼 고정</strong>돼요.<br/>
테이블 헤더, 섹션 헤더 등에 자주 사용해요.<br/>
<strong>주의:</strong> 부모 요소가 <code style="font-family:'JetBrains Mono',monospace;background:var(--surface2);padding:2px 7px;border-radius:4px;font-size:13px;">overflow: hidden</code>이면 작동하지 않아요!
<br/>
<div class="key-point kp-sticky">📌 스크롤 임계점 도달 시 고정 · top 값 필수 · 부모 영역 안에서만</div>
</div>
<div class="code-block">
<div class="cb-bar"><div class="dot dr"></div><div class="dot dy"></div><div class="dot dg"></div><div class="cb-lang">CSS</div></div>
<pre><span class="sel">.sticky-header</span> {
<span class="pr">position</span>: <span class="hl-sticky">sticky</span>;
<span class="pr">top</span>: <span class="vl">0</span>; <span class="cm">/* ← 이 값이 임계점! 필수! */</span>
<span class="cm">/* top: 0 → 화면 최상단에 닿으면 고정 */</span>
<span class="cm">/* top: 60px → 위에서 60px 지점에 닿으면 고정 */</span>
<span class="pr">z-index</span>: <span class="vl2">10</span>;
}</pre>
</div>
<div class="demo-wrap">
<div class="demo-header">🖥 데모 — 아래로 스크롤하면 헤더가 상단에 고정!</div>
<div class="demo-body">
<div class="demo-sticky-area">
<div class="sticky-content-item">섹션 A 내용 · 스크롤을 내려보세요 ↓</div>
<div class="sticky-content-item">내용 1</div>
<div class="sticky-content-item">내용 2</div>
<div class="sticky-header">📌 STICKY HEADER — top: 0 에서 고정!</div>
<div class="sticky-content-item">섹션 B 내용</div>
<div class="sticky-content-item">내용 3</div>
<div class="sticky-content-item">내용 4</div>
<div class="sticky-content-item">내용 5</div>
<div class="sticky-content-item">내용 6</div>
<div class="sticky-content-item">내용 7 · 끝</div>
</div>
<p style="font-size:12px;color:var(--muted);margin-top:10px;">↑ 박스 안을 스크롤하면 노란 헤더가 상단에 고정되는 걸 확인하세요!</p>
</div>
</div>
</div>
<!-- ─── 비교표 ─── -->
<div class="section">
<div class="section-header">
<div class="pos-badge" style="background:rgba(255,255,255,0.05);border-color:var(--border);color:var(--muted);">비교표</div>
<div class="section-title-wrap">
<h2>5가지 한눈에 비교</h2>
<p>어떤 상황에서 무엇을 써야 할까?</p>
</div>
</div>
<div style="overflow-x:auto;">
<table class="compare-table">
<thead>
<tr>
<th>position</th>
<th>기준점</th>
<th>흐름 이탈</th>
<th>스크롤 고정</th>
<th>사용 예</th>
</tr>
</thead>
<tbody>
<tr class="ct-static">
<td>static</td>
<td>일반 흐름</td>
<td><span class="tc-no">❌ 아니오</span></td>
<td><span class="tc-no">❌</span></td>
<td>일반 텍스트, 기본 박스</td>
</tr>
<tr class="ct-relative">
<td>relative</td>
<td>자기 자신</td>
<td><span class="tc-no">❌ 아니오</span></td>
<td><span class="tc-no">❌</span></td>
<td>미세 조정, absolute 기준 부모</td>
</tr>
<tr class="ct-absolute">
<td>absolute</td>
<td>relative 부모</td>
<td><span class="tc-yes">✅ 예</span></td>
<td><span class="tc-no">❌</span></td>
<td>툴팁, 뱃지, 팝업</td>
</tr>
<tr class="ct-fixed">
<td>fixed</td>
<td>뷰포트(화면)</td>
<td><span class="tc-yes">✅ 예</span></td>
<td><span class="tc-yes">✅ 예</span></td>
<td>상단 헤더, 플로팅 버튼</td>
</tr>
<tr class="ct-sticky">
<td>sticky</td>
<td>스크롤 임계점</td>
<td><span class="tc-no">❌ 아니오</span></td>
<td><span class="tc-cond">🔶 조건부</span></td>
<td>테이블 헤더, 섹션 타이틀</td>
</tr>
</tbody>
</table>
</div>
</div>
<!-- ─── 핵심 정리 ─── -->
<div class="summary-grid">
<div class="sum-card sc-static">
<div class="sc-dot"></div>
<div class="sc-name">static</div>
<div class="sc-desc">기본값. 흐름대로 배치. top/left 무효.</div>
<div class="sc-when">✦ 특별한 배치 불필요할 때</div>
</div>
<div class="sum-card sc-relative">
<div class="sc-dot"></div>
<div class="sc-name">relative</div>
<div class="sc-desc">자기 자신 기준 이동. 원래 자리 유지.</div>
<div class="sc-when">✦ absolute 자식의 기준점 만들 때</div>
</div>
<div class="sum-card sc-absolute">
<div class="sc-dot"></div>
<div class="sc-name">absolute</div>
<div class="sc-desc">흐름 이탈. 부모 기준 자유 배치.</div>
<div class="sc-when">✦ 툴팁·뱃지·오버레이</div>
</div>
<div class="sum-card sc-fixed">
<div class="sc-dot"></div>
<div class="sc-name">fixed</div>
<div class="sc-desc">뷰포트 고정. 스크롤 무관.</div>
<div class="sc-when">✦ 내비바·플로팅 버튼</div>
</div>
<div class="sum-card sc-sticky">
<div class="sc-dot"></div>
<div class="sc-name">sticky</div>
<div class="sc-desc">흐름 유지 + 임계점 도달 시 고정.</div>
<div class="sc-when">✦ 테이블 헤더·섹션 타이틀</div>
</div>
</div>
</div>
<script>
// absolute 데모 이동
const absPositions = {
tr: { top:'20px', right:'20px', bottom:'auto', left:'auto' },
tl: { top:'20px', right:'auto', bottom:'auto', left:'20px' },
br: { top:'auto', right:'20px', bottom:'14px', left:'auto' },
center: { top:'50%', right:'auto', bottom:'auto', left:'50%', transform:'translate(-50%,-50%)' }
};
function moveAbs(pos) {
const box = document.getElementById('abs-box');
const p = absPositions[pos];
box.style.top = p.top;
box.style.right = p.right;
box.style.bottom = p.bottom;
box.style.left = p.left;
box.style.transform = p.transform || '';
document.querySelectorAll('.abs-ctrl-btn').forEach(b => b.classList.remove('active'));
document.getElementById('abs-btn-' + pos).classList.add('active');
// 라벨 업데이트
const labels = {
tr: 'top: 20px · right: 20px',
tl: 'top: 20px · left: 20px',
br: 'bottom: 14px · right: 20px',
center: 'top: 50% · left: 50% · transform: translate(-50%,-50%)'
};
box.textContent = 'absolute';
}
</script>
</body>
</html>
결과와 배운 점
파워포인트로 만들어진 자료보다 훨씬 학생들이 이해를 잘 하는 것을 느낄 수 있었습니다.
강의 자료실에 올려놓고 보도록 했더니 홈페이지 활성화 효과도 있어서 좋습니다.
이번 기수에 참여해서 정말 유용한 꿀팁을 많이 얻고 있는 느낌입니다.