cover

結論先講

很多人寫 RWD 是這樣:

@media (max-width: 1024px) { /* 平板 */ }
@media (max-width: 768px)  { /* 手機 */ }
@media (max-width: 375px)  { /* 小手機 */ }

然後發現 1024px 剛好不包括筆電、768px 大於部分平板、375px 壓根不存在。斷點設錯整個 RWD 崩潰。

現代 RWD 三大改變:

  1. Mobile-first:先寫手機,斷點用 min-width 往上加
  2. Fluid:用 clamp() / min() / max() 讓數值隨視窗流動,減少斷點
  3. Container Query:不只看視窗,看元件容器的大小

這篇講現代 RWD 的正確思維。


為什麼 Mobile-first

傳統(Desktop-first)寫法

.card { width: 33%; }  /* 桌面:3 欄 */
 
@media (max-width: 1024px) {
  .card { width: 50%; }  /* 平板:2 欄 */
}
 
@media (max-width: 768px) {
  .card { width: 100%; }  /* 手機:1 欄 */
}

Mobile-first 寫法

.card { width: 100%; }  /* 預設:手機 1 欄 */
 
@media (min-width: 768px) {
  .card { width: 50%; }  /* 平板以上:2 欄 */
}
 
@media (min-width: 1024px) {
  .card { width: 33%; }  /* 桌面以上:3 欄 */
}

為什麼 mobile-first 更好

  1. 手機 CSS 先載(先級進增強,桌面才疊上去)— 手機載入更快
  2. 斷點語意清楚min-width: 768px = 「768 以上才啟用」
  3. 不會忘記「手機能看」:從小開始,自然覆蓋所有尺寸
  4. Tailwind / Bootstrap 預設 mobile-first,跟主流一致

斷點(Breakpoint)怎麼選

不要從裝置尺寸反推。要從內容反推。

錯誤做法

「iPhone 15 Pro Max 是 430px,我用 430 當斷點」 → 明天出新手機就崩。

正確做法

看你的內容在不同寬度下什麼時候開始壞。設在壞掉的那個寬度。

實用斷點建議

現代專案常見:

/* 手機(預設)            : 0-640 */
@media (min-width: 640px)   { /* sm: 大手機、小平板 */ }
@media (min-width: 768px)   { /* md: 平板 */ }
@media (min-width: 1024px)  { /* lg: 筆電 */ }
@media (min-width: 1280px)  { /* xl: 桌面 */ }
@media (min-width: 1536px)  { /* 2xl: 大螢幕 */ }

這是 Tailwind 預設的斷點,跟一般裝置對應合理。

為什麼不用 em

@media (min-width: 40em) { ... }

有人主張用 em 是為了「使用者放大字體時斷點跟著變」。好處合理,但實務上大多數人不放大字體,用 px 反而直觀。爭議隨你。


Fluid:讓數值流動,少用斷點

傳統:用斷點切換

.title { font-size: 16px; }
 
@media (min-width: 768px) {
  .title { font-size: 20px; }
}
 
@media (min-width: 1024px) {
  .title { font-size: 24px; }
}

768px 以下跟 1023px 中間字都是 16px,很醜。

現代:clamp() 流動

.title { font-size: clamp(16px, 2vw + 12px, 24px); }

解讀:

  • 最小 16px
  • 偏好值 2vw + 12px(跟著視窗流動)
  • 最大 24px

字體連續隨視窗大小變化,沒有「跳動」的瞬間。

clamp() 三個用法

/* 字體 */
font-size: clamp(1rem, 2vw + 0.5rem, 2rem);
 
/* 容器寬度 */
width: clamp(300px, 50vw, 800px);
 
/* 間距 */
padding: clamp(16px, 5vw, 64px);

min()max()

/* 不超過 800px,但可以更小 */
width: min(100%, 800px);
 
/* 至少 16px */
font-size: max(16px, 1vw);

實戰上,clamp(a, b, c) 等同 min(max(a, b), c)


Container Query:容器查詢(遊戲規則改變者)

Media Query 看視窗大小。Container Query 看容器大小。

為什麼重要

你做一張卡片元件。放在:

  • Sidebar(窄):應該單欄排列
  • Main(中):應該兩欄排列
  • Full Width(寬):應該三欄排列

傳統 Media Query 只看視窗寬度,不知道卡片現在在哪個容器裡。同一個元件無法複用

Container Query 解決這個問題:

/* 宣告 .card-container 是可以被查詢的容器 */
.card-container {
  container-type: inline-size;
}
 
/* 查詢容器本身的寬度 */
@container (min-width: 400px) {
  .card {
    display: grid;
    grid-template-columns: 150px 1fr;
  }
}
 
@container (min-width: 600px) {
  .card {
    grid-template-columns: 200px 1fr 1fr;
  }
}

卡片根據自己容器的寬度調整,不管視窗多大。真正的元件化 RWD

瀏覽器支援

  • Chrome 105+(2022-09)
  • Firefox 110+(2023-02)
  • Safari 16+(2022-09)

2026 年可以放心用。


圖片 RWD:不只是 max-width: 100%

最小做法

img { max-width: 100%; height: auto; }

圖片不超出容器,高度自動。但大螢幕還是載大圖,浪費。

進階:srcset + sizes

<img
  srcset="
    photo-320.jpg 320w,
    photo-640.jpg 640w,
    photo-1280.jpg 1280w
  "
  sizes="(max-width: 768px) 100vw, 50vw"
  src="photo-640.jpg"
  alt="..."
>

解讀:

  • 提供三種尺寸
  • sizes 告訴瀏覽器圖片實際顯示寬度(看 CSS 會用多寬)
  • 瀏覽器根據裝置 + DPR(像素密度)自動挑最適合的

<picture> + <source>:Art Direction

桌面用橫幅圖、手機用直幅圖:

<picture>
  <source media="(min-width: 768px)" srcset="hero-wide.jpg">
  <source media="(max-width: 767px)" srcset="hero-portrait.jpg">
  <img src="hero-wide.jpg" alt="...">
</picture>

AVIF / WebP:格式 fallback

<picture>
  <source type="image/avif" srcset="hero.avif">
  <source type="image/webp" srcset="hero.webp">
  <img src="hero.jpg" alt="...">
</picture>

排版方向:Logical Properties

做國際化 RWD 要懂 logical properties:

傳統邏輯阿拉伯文(RTL)時
margin-leftmargin-inline-start自動變右邊
margin-rightmargin-inline-end自動變左邊
padding-toppadding-block-start不變
widthinline-size橫向尺寸
heightblock-size縱向尺寸

直書(日文)跟右到左(阿拉伯)時這組超重要。


Dark Mode:用 prefers-color-scheme

:root {
  --bg: white;
  --text: black;
}
 
@media (prefers-color-scheme: dark) {
  :root {
    --bg: #1a1a1a;
    --text: #eee;
  }
}
 
body {
  background: var(--bg);
  color: var(--text);
}

系統切換 dark mode 自動變色。

給手動切換按鈕

[data-theme="dark"] {
  --bg: #1a1a1a;
  --text: #eee;
}

JS 切 data-theme 屬性,CSS 變數跟著變。


其他常用 Media Query

/* 降低動畫(使用者勾選減少動態) */
@media (prefers-reduced-motion: reduce) {
  * { animation: none !important; transition: none !important; }
}
 
/* 高對比模式 */
@media (prefers-contrast: high) {
  :root { --border-color: black; }
}
 
/* 列印樣式 */
@media print {
  .no-print { display: none; }
}
 
/* 直橫向 */
@media (orientation: landscape) { ... }

實戰 Checklist

  • Mobile-first(min-width 往上加)
  • 斷點從內容反推,不從裝置反推
  • 字體/容器/間距用 clamp() 流動
  • 元件 RWD 用 Container Query
  • 圖片用 srcset / <picture>
  • 支援 prefers-color-scheme: dark
  • 尊重 prefers-reduced-motion
  • 國際化用 logical properties

相關文章