商用網站架設實務

LESSON-13 · checkpoint

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

主要結構已經完成,只留下本堂一個核心缺口。先預測,再從指定檔案找出它。 這頁用 可觀察證據anchor 指出下一個動作。

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

開始前確認

這一頁的結果:先重現 transition 寫在 hover 造成的移出跳回

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

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

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

照這個順序做

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

CHECKPOINT-1

修正只有移入會平滑、移出卻瞬間跳回的真實缺口。

目的

修正只有移入會平滑、移出卻瞬間跳回的真實缺口。

修改檔案

style.css

程式碼位置

.primary-button:hover

現在要做:請在 style.css 的「.primary-button:hover」位置完成:找出寫在 hover/focus 狀態裡的 transition,移回 .primary-button 一般 selector

貼上位置:style.css:.primary-button:hover 區塊,先保留同一檔案的其他內容。

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

.primary-button:hover,
.primary-button:focus-visible {
  transform: translateY(-2px);
}

預期結果:移入和移出都平順;一般 selectorComputed 可讀到 transition狀態 selector 只保留變化後的值。

驗收證據:先觀察移入/移出差異,再在 Styles 找到 transition 的實際位置,最後用 Computed 檢查 propertyduration、timing function

卡住時先看這裡

常見錯誤:只把 duration 改短,卻沒有改 transition 所在的 selector

第一個檢查:只查看 .primary-button:hover 區塊是否還有 transition 宣告。

階段檔案

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

開啟 checkpoint 的可執行入口

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>
    <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" class="primary-button" 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; }

.primary-button:hover, .primary-button:focus-visible { 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); }
}
.service-card__tag { animation: attention 900ms ease both; }

@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; }
}

/* CHECKPOINT-1:transition 故意放在狀態 selector;移出時會瞬間跳回。 */
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);

完成驗收

下一步

Checkpoint 完成後,先說出你修正的原因,再前往 Solution 對照每個檔案差異。

前往 Solution,逐檔對照差異