商用網站架設實務

LESSON-11 · solution

CSS 進階:Position、Sticky 與堆疊

這一頁不是直接複製答案;先逐檔看差異、修改原因與驗收證據,再把需要的改動帶回自己的資料夾。 這頁用 可觀察證據anchor 指出下一個動作。

Starter4 個小成果
Checkpoint一個核心缺口
Solution5 個差異

開始前確認

這一頁的結果:Starter 四個定位微步驟與 Checkpoint 缺口都可重現;390px、桌機、200% 文字與 Tab focus 沒有水平溢位或遮蔽;沒有 TODOCHECKPOINT、placeholder 或 JavaScript 假驗證訊息

工作方式:先把這一階段複製到自己的工作資料夾,開啟 files/index.html,記下修改前的畫面、Elements、Styles、Computed、捲動與 Tab狀態。

會用到的檔案:index.htmlstyle.css

本堂焦點:用 Position 控制元素參考點、捲動行為與前後層級

照這個順序做

一次只做一張卡。完成後先重新整理入口,再留下證據,才進入下一張。

差異 1

比較 static 的 normal flow 與 relative 保留原本空間後的位移。

目的

比較 staticnormal flowrelative 保留原本空間後的位移。

修改檔案

style.css

程式碼位置

.flow-card--moved

現在要做:先對照差異,再檢查 在 style.css 的「.flow-card--moved」對照這項變更:.flow-card--moved { position: relative; top: 1rem; left: 1rem; }

貼上位置:style.css 的 .flow-card--moved 區塊

.flow-card--moved {
  position: relative;
  top: 1rem;
  left: 1rem;
}

預期結果:第二張卡片往右下移動,但第三張卡片仍保留原本的文件流位置。

驗收證據:Computedposition、top、left,以及第二與第三張卡片的畫面位置。

卡住時先看這裡

常見錯誤:只複製完整答案,卻沒有先理解這一項差異的責任。

第一個檢查:只對照目前差異指定的檔案與 anchor。

差異 2

分辨正常文件流與 absolute 元素的關係。

目的

分辨正常文件流與 absolute 元素的關係。

修改檔案

index.html

程式碼位置

.featured-badge

現在要做:先對照差異,再檢查 在 index.html 的「.featured-badge」對照這項變更:<article class="service-card service-card--featured"><span class="featured-badge">推薦</span><h2>定期維護</h2></article>

貼上位置:index.html 的 .featured-badge 區塊

<article class="service-card service-card--featured"><span class="featured-badge">推薦</span><h2>定期維護</h2></article>

預期結果:推薦標籤貼在指定卡片角落,不影響其他卡片排版。

驗收證據:Elements 父層與捲動畫面位置。

卡住時先看這裡

常見錯誤:只複製完整答案,卻沒有先理解這一項差異的責任。

第一個檢查:只對照目前差異指定的檔案與 anchor。

差異 3

理解 sticky 依賴捲動與定位參考。

目的

理解 sticky 依賴捲動與定位參考。

修改檔案

style.css

程式碼位置

.site-header

現在要做:先對照差異,再檢查 在 style.css 的「.site-header」對照這項變更:.site-header { position: sticky; top: 0; z-index: 10; }

貼上位置:style.css 的 .site-header 區塊

.site-header {
  position: sticky;
  top: 0;
  z-index: 10;
}

預期結果:捲動內容時頁首停在容器頂端。

驗收證據:捲動畫面、Computed position 與 top。

卡住時先看這裡

常見錯誤:只複製完整答案,卻沒有先理解這一項差異的責任。

第一個檢查:只對照目前差異指定的檔案與 anchor。

差異 4

理解 fixed 以 viewport 為參考,並處理它可能遮住內容與鍵盤焦點的問題。

目的

理解 fixedviewport 為參考,並處理它可能遮住內容與鍵盤焦點的問題。

修改檔案

index.html、style.css

程式碼位置

.floating-contact、.stack-card--front、.page-shell

現在要做:先對照差異,再檢查 在 index.htmlstyle.css 的「.floating-contact、.stack-card--front、.page-shell」對照這項變更:<a class="floating-contact" href="#contact">聯絡我們</a> .page-shell { padding-bottom: 6rem; } .stack-card--front { z-index: 2; } :focus-visible { outline: 3px solid #1d4ed8; outline-offset: 3px; }

貼上位置:index.htmlstyle.css 的 .floating-contact、.stack-card--front、.page-shell 區塊

<a class="floating-contact" href="#contact">聯絡我們</a>

.page-shell { padding-bottom: 6rem; }
.stack-card--front { z-index: 2; }
:focus-visible { outline: 3px solid #1d4ed8; outline-offset: 3px; }

預期結果:聯絡入口固定在右下角但不遮住最後文字,兩張重疊卡片可比較前後層級,Tab focus 清楚可見。

驗收證據:捲動前後位置、Elements 的 href、兩張卡片的 Computed z-index、390px/200% 文字與 Tab。

卡住時先看這裡

常見錯誤:只複製完整答案,卻沒有先理解這一項差異的責任。

第一個檢查:只對照目前差異指定的檔案與 anchor。

差異 5

用最近的 positioned ancestor 建立 containing block,而不是讓 badge 以頁面根部為參考。

目的

用最近的 positioned ancestor 建立 containing block,而不是讓 badge 以頁面根部為參考。

修改檔案

style.css

程式碼位置

.service-card

現在要做:先對照差異,再檢查 在 style.css 的「.service-card」對照這項變更:.service-card { position: relative; }

貼上位置:style.css.service-card 區塊

.service-card { position: relative; }

預期結果:推薦標籤停在推薦卡右上角,服務卡移動時 badge 跟著卡片移動。

驗收證據:Elements 父子關係、Computed position 與卡片/badge 的相對座標。

卡住時先看這裡

常見錯誤:只複製完整答案,卻沒有先理解這一項差異的責任。

第一個檢查:只對照目前差異指定的檔案與 anchor。

階段檔案

完整檔案收在可展開的卡片中;先完成上方微步驟,再用這裡查閱與複製。檔案位置要和目前的相對路徑一致。

開啟 solution 的可執行入口

index.html入口頁:包含服務卡、長內容、重疊卡片、可聚焦控制項與定位任務。

這段要貼在哪裡:先在自己的工作資料夾建立同名相對位置,再複製內容。

<!doctype html>
<html lang="zh-Hant"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width, initial-scale=1"><title>安心維護|定位練習</title><link rel="icon" href="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Crect width='16' height='16' rx='4' fill='%230f4c5c'/%3E%3C/svg%3E"><link rel="stylesheet" href="style.css"></head>
<body><header class="site-header"><a class="brand" href="#top">安心維護</a><nav aria-label="主要導覽"><a href="#services">服務</a><a href="#evidence">驗收</a><a href="#contact">聯絡</a></nav></header>
<main id="top" class="page-shell"><p class="eyebrow">LESSON-11 · 用 Position 控制元素參考點、捲動行為與前後層級</p><h1>讓定位關係可以被看見</h1><p class="page-description">本頁用服務卡、長內容、固定入口與重疊卡片,觀察元素相對誰定位,以及如何留下畫面與 DevTools 證據。</p><section class="flow-demo" aria-labelledby="flow-title"><h2 id="flow-title">normal flow 與 relative</h2><article class="flow-card">第一張卡片:static 的 normal flow。</article><article class="flow-card flow-card--moved">第二張卡片:Starter 請讓它使用 relative。</article><article class="flow-card">第三張卡片:觀察它是否保留原本位置。</article></section>
<section id="services" aria-labelledby="services-title"><h2 id="services-title">服務方案</h2><article class="service-card service-card--featured"><span class="featured-badge">推薦</span><h3>定期維護</h3><p>推薦方案的 badge 應貼在這張卡片右上角,不應把其他內容往下推。</p><a href="#evidence">查看定位證據</a></article><article class="service-card"><h3>到府諮詢</h3><p>這張卡片留在 normal flow,方便和推薦卡比較。</p></article></section>
<section class="long-content" aria-labelledby="scroll-title"><h2 id="scroll-title">長內容與捲動</h2><p>內容刻意保留足夠高度,讓 sticky header 的前後狀態可以觀察。</p><p>在這裡按 Tab 也能確認 header、連結與固定入口的操作順序。</p><p>固定元素不會自動替內容讓出空間,所以頁面底部需要安全距離。</p><p>把 viewport 切換到 390px,再檢查長文字、按鈕與卡片是否仍可讀。</p><p>將瀏覽器文字放大到 200%,確認內容沒有依賴固定高度或水平捲軸。</p><p>這些段落讓 sticky、fixed 與 overflow 的差異有實際畫面證據。</p></section>
<section id="evidence" class="stack-demo" aria-labelledby="stack-title"><h2 id="stack-title">z-index 重疊實驗</h2><article class="stack-card stack-card--back">後層卡片:z-index 1</article><article class="stack-card stack-card--front">前層卡片:z-index 2</article></section>
<section id="contact" aria-labelledby="contact-title"><h2 id="contact-title">聯絡與鍵盤驗收</h2><p>請只用 Tab 走過導覽、卡片連結、固定 CTA 與下面的按鈕。</p><button type="button">可聚焦的按鈕</button> <a href="#top">回到頁面頂端</a></section></main><a class="floating-contact" href="#contact">聯絡我們</a></body></html>
style.css定位樣式:集中觀察 normal flowrelativeabsolutestickyfixedz-indexfocus-visible

這段要貼在哪裡:先在自己的工作資料夾建立同名相對位置,再複製內容。

:root { font-family: system-ui, -apple-system, "Segoe UI", "Noto Sans TC", sans-serif; color: #17324d; background: #f5f8fb; } * { box-sizing: border-box; } body { margin: 0; min-height: 100vh; line-height: 1.7; }
.site-header { width: 100%; padding: .9rem max(1rem, calc((100% - 820px) / 2)); display: flex; justify-content: space-between; gap: 1rem; align-items: center; background: #0f4c5c; color: #fff; position: sticky; top: 0; z-index: 10; } .site-header nav { display: flex; flex-wrap: wrap; gap: .75rem; } .site-header a { color: #fff; }
.page-shell { width: min(100% - 2rem, 820px); margin: 0 auto; padding: 2rem 2rem 6rem; background: #fff; } .eyebrow { color: #075985; font-weight: 800; } .page-description { color: #52657d; } section { margin-block: 2rem; }
.flow-demo { display: grid; gap: .75rem; } .flow-card { padding: 1rem; border: 1px solid #b9cddd; border-radius: .6rem; background: #f8fafc; } .flow-card--moved { position: relative; top: 1rem; left: 1rem; border-color: #0f766e; background: #ecfdf5; }
.service-card { position: static; min-height: 10rem; padding: 1.25rem; border: 2px solid #b9cddd; border-radius: .75rem; background: #fff; } .service-card--featured { border-color: #0f766e; position: relative; } .featured-badge { position: absolute; top: .75rem; right: .75rem; padding: .25rem .6rem; border-radius: 999px; background: #9a3412; color: #fff; font-weight: 800; }
.long-content { min-height: 125vh; padding-block: 1rem; } .stack-demo { position: relative; min-height: 14rem; } .stack-card { position: absolute; width: min(70%, 22rem); padding: 1rem; border: 2px solid #17324d; border-radius: .75rem; } .stack-card--back { top: 1rem; left: 1rem; z-index: 1; background: #dbeafe; } .stack-card--front { top: 4rem; left: 4rem; background: #fed7aa; z-index: 2; }
.floating-contact { position: fixed; right: 1rem; bottom: 1rem; z-index: 20; padding: .7rem 1rem; border-radius: .55rem; background: #9a3412; color: #fff; font-weight: 800; text-decoration: none; } button { padding: .65rem .9rem; border: 0; border-radius: .5rem; background: #0f4c5c; color: #fff; font: inherit; cursor: pointer; }
:focus-visible { outline: 3px solid #1d4ed8; outline-offset: 3px; }
@media (max-width: 560px) { .site-header { align-items: flex-start; flex-direction: column; } .page-shell { width: min(100% - 1rem, 820px); padding-inline: 1rem; } .stack-card { width: calc(100% - 2rem); } .stack-card--front { left: 2rem; } }

完成驗收

下一步

Solution 對照完成後,回到 README 整理證據,並準備下一堂課的先備知識。

回到 README,整理本堂證據並銜接下一堂