商用網站架設實務

LESSON-13 · starter

CSS 進階:Transition、動畫與樣式整理

你會依序完成3個本堂專屬微步驟;每一步只修改一個責任,看到結果後才進下一步。 這頁用 可觀察證據anchor 指出下一個動作。

Starter3 個小成果
Checkpoint一個核心缺口
Solution4 個差異

開始前確認

這一頁的結果:index.htmlclassCSS selector 對應

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

會用到的檔案:index.htmlstyle.cssmain.js

本堂焦點:class狀態transition 做出可驗收的按鈕與服務卡片回饋

照這個順序做

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

TODO-1

建立 HTML 元素與 CSS selector 之間可以被檢查的對應關係。

目的

建立 HTML 元素與 CSS selector 之間可以被檢查的對應關係。

修改檔案

index.html

程式碼位置

#action

現在要做:請在 index.html 的「#action」位置完成:在按鈕上加入 class="primary-button",並確認卡片使用 class="service-card"

貼上位置:index.html:#action 區塊,先保留同一檔案的其他內容。

<button id="action" class="primary-button" type="button">查看推薦方案</button>

預期結果:Elements 中的按鈕有 primary-button,兩張卡片都有 service-card;此時尚未期待動畫。

驗收證據:Elements 搜尋 .primary-button 與 .service-card,數量和 HTML 結構一致。

卡住時先看這裡

常見錯誤:class 寫成 primary_button,或把 class 放到父層導致 selector 對不到。

第一個檢查:只在 Elements 點擊按鈕,查看 class 屬性是否逐字為 primary-button。

TODO-2

分清楚 transition 需要前後狀態,而 animation 可以依 keyframes 自己播放。

目的

分清楚 transition 需要前後狀態,而 animation 可以依 keyframes 自己播放。

修改檔案

style.css

程式碼位置

.primary-button、.service-card 與 @keyframes attention

現在要做:請在 style.css 的「.primary-button、.service-card 與 @keyframes attention」位置完成:在一般 selector 放入短 transition,並建立一個小型 keyframes animation 作比較

貼上位置:style.css:.primary-button、.service-card 與 @keyframes attention 區塊,先保留同一檔案的其他內容。

.primary-button {
  transition: transform 200ms ease, background-color 200ms ease, box-shadow 200ms ease;
}

.service-card {
  transition: transform 200ms ease, box-shadow 200ms ease;
}

@keyframes attention {
  from { opacity: .4; transform: translateY(-4px); }
  to { opacity: 1; transform: translateY(0); }
}

預期結果:按鈕和卡片的 hover、focus-within 有平順變化;推薦標籤只播放一次短 animation。

驗收證據:hover 進入與移出、Tab focusStyles 命中;Computed 看到 transform、background-color、box-shadow 的 transition

卡住時先看這裡

常見錯誤:只在 :hovertransition,或把所有 property 都加入造成難以預測的效果。

第一個檢查:先在 Styles 搜尋 .primary-button 的一般規則,確認 transition 不在 hover 區塊裡。

TODO-3

尊重使用者希望減少動態的偏好,同時保留清楚的鍵盤 focus。

目的

尊重使用者希望減少動態的偏好,同時保留清楚的鍵盤 focus

修改檔案

style.css

程式碼位置

@media (prefers-reduced-motion: reduce)

現在要做:請在 style.css 的「@media (prefers-reduced-motion: reduce)」位置完成:為 transition 與 animation 提供 reduced motion 替代呈現

貼上位置:style.css@media (prefers-reduced-motion: reduce) 區塊,先保留同一檔案的其他內容。

@media (prefers-reduced-motion: reduce) {
  .primary-button { transition: background-color .01ms linear; }
  .service-card { transition: none; }
  .service-card__tag { animation: none; }
  .primary-button:hover,
  .primary-button:focus-visible,
  .service-card:hover,
  .service-card:focus-within { transform: none; box-shadow: none; }
}

預期結果:切換 reduce 後 transform 位移不再出現、animation 不播放或被縮短,outline 仍清楚。

驗收證據:Rendering 的 prefers-reduced-motionComputedtransition 與 Tab focus 對照正常/reduce 兩種狀態

卡住時先看這裡

常見錯誤:用一條全域 !important 蓋掉所有規則,或把 focus outline 一起移除。

第一個檢查:先只切換 Rendering 的 reduced motion,再按 Tab 到按鈕比較前後。

階段檔案

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

開啟 starter 的可執行入口

index.html入口檔案:建立 primary-button、service-card 與可操作的服務方案示範。

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

<!doctype html>
<html lang="zh-Hant">
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>LESSON-13|Transition 與動畫練習</title>
  <link rel="icon" href="../../../assets/favicon.svg" type="image/svg+xml">
  <link rel="stylesheet" href="style.css">
</head>
<body>
  <header class="site-header"><span class="brand">安心維護</span><span class="lesson-label">LESSON-13 · CSS 狀態實驗</span></header>
  <main class="page-shell">
    <p class="eyebrow">LESSON-13 · transition lab</p>
    <h1>讓服務卡片的狀態變化更容易理解</h1>
    <p class="page-description">用滑鼠與鍵盤觀察狀態,再用 Styles、Computed 與 Rendering 找到證據。</p>
<!-- TODO-1:請把 class="primary-button" 加到 #action;不要修改 id。 -->
    <section class="demo-panel" aria-labelledby="demo-title">
      <h2 id="demo-title">服務方案預覽</h2>
      <div class="service-list">
        <article class="service-card service-card--featured">
          <span class="service-card__tag">推薦服務</span>
          <h3>定期維護</h3>
          <p>每月一次的檢查與整理,讓訪客知道下一步可以怎麼開始。</p>
          <button id="action" type="button">查看推薦方案</button>
        </article>
        <article class="service-card">
          <span class="service-card__tag">入門服務</span>
          <h3>到府諮詢</h3>
          <p>先把需求說清楚,再選擇適合的服務方式。</p>
          <p class="card-note">本卡片用來觀察 service-card 的共用規則。</p>
        </article>
      </div>
      <p id="status" class="status" role="status">尚未操作。先移入按鈕,或按 Tab 觀察 focus。</p>
    </section>
    <aside class="evidence-note"><strong>這堂只看:</strong>class 對應、hover/focus 狀態、transition、animation 與 reduced motion。</aside>
  </main>
  <script src="main.js" defer></script>
</body>
</html>
style.css呈現層:集中放置狀態transition、animation 與 reduced-motion 規則。

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

: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: min(100% - 2rem, 920px); margin: 1rem auto 0; display: flex; flex-wrap: wrap; justify-content: space-between; gap: .5rem 1rem; align-items: center; }
.brand { color: #155e75; font-weight: 800; }
.lesson-label, .page-description, .card-note { color: #52657d; }
.page-shell { width: min(100% - 2rem, 860px); margin: 2rem auto; }
.eyebrow { color: #176b87; font-weight: 800; letter-spacing: .06em; }
.demo-panel { padding: clamp(1rem, 3vw, 1.5rem); border: 1px solid #cbd8e4; border-radius: 20px; background: #fff; }
.service-list { display: flex; flex-wrap: wrap; gap: 1rem; }
.service-card { flex: 1 1 16rem; min-width: 0; padding: 1.25rem; border: 1px solid #d8e0ea; border-radius: 16px; background: #fbfdff; }
.service-card__tag { display: inline-block; color: #176b87; font-weight: 800; }
.service-card h3 { margin: .35rem 0; }
.primary-button, button { padding: .7rem 1rem; border: 0; border-radius: 10px; background: #176b87; color: #fff; font: inherit; cursor: pointer; }
.primary-button:hover, .primary-button:focus-visible { background-color: #0f5268; box-shadow: 0 8px 16px rgb(23 107 135 / 24%); transform: translateY(-2px); }
.primary-button:focus-visible { outline: 3px solid #f3b63f; outline-offset: 3px; }
.service-card:hover, .service-card:focus-within { box-shadow: 0 12px 24px rgb(23 50 77 / 14%); transform: translateY(-2px); }
.status { min-height: 2rem; color: #17643d; }
.evidence-note { margin-top: 1rem; padding: .9rem 1rem; border-left: 4px solid #f3b63f; background: #fff8e6; }

/* TODO-2:請在一般 .primary-button 與 .service-card selector 加入短 transition,並加入 @keyframes attention。 */
/* TODO-3:請加入 @media (prefers-reduced-motion: reduce),移除非必要位移與 animation,但保留 focus-visible。 */
main.js行為層:只處理按鈕操作後的狀態提示,不假裝驗證 CSS

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

const status = document.querySelector("#status");
const action = document.querySelector("#action");

function showResult() {
  if (!status) return;
  status.textContent = "按鈕已被操作。請回到 Styles 與 Computed 檢查狀態規則。";
  console.log("LESSON-13:按鈕操作已執行");
}

if (action) action.addEventListener("click", showResult);

完成驗收

下一步

Starter 完成後,不要直接看 Solution;先前往 Checkpoint,自己找出最後一個核心缺口。

前往 Checkpoint,自己補最後一個核心缺口