Skip to content

Sass 斷點管理:用 map 收攏 RWD 的所有數值

用 Sass map 集中管理 RWD 斷點

RWD 專案最常見的維護問題不是斷點訂錯,是 768px 這個數字在四十個檔案裡各寫了一次。設計改版要調成 800px,你得全域搜尋替換,然後祈禱沒有漏掉。

為什麼要用 Sass 管

CSS 自訂屬性 在這裡幫不上忙,它不能用在媒體查詢的條件裡

css
:root { --md: 768px; }

@media (min-width: var(--md)) { }   /* ❌ 無效 */

斷點的值必須在編譯期就決定,這正是 Sass 變數擅長的事。

一張 map 管全部

scss
// abstracts/_breakpoints.scss
$breakpoints: (
  sm: 576px,
  md: 768px,
  lg: 992px,
  xl: 1200px,
) !default;

加上 !default,使用這個模組的人就能用 @use ... with 換掉整組。

取值要有防呆

scss
@use "sass:map";

@function bp($name) {
  @if not map.has-key($breakpoints, $name) {
    @error "斷點 #{$name} 不存在。可用的有:#{map.keys($breakpoints)}。";
  }
  @return map.get($breakpoints, $name);
}

直接用 map.get() 的話,打錯字會回傳 null@media (min-width: null) 就變成無效的查詢,樣式安靜地不生效。包一層函式讓它在編譯期就炸掉,見 sass:map

用在媒體查詢

scss
.card {
  padding: 16px;

  @media (min-width: bp(md)) {
    padding: 24px;
  }
}

這裡不需要插值

bp(md) 是函式呼叫,回傳的值可以直接放在 min-width: 後面。只有 變數 出現在 @media 條件裡才要 插值

不過每次都寫 @media (min-width: bp(md)) 還是囉嗦,實務上會再包一層 混入,做法見 響應式 mixin 實作

mobile-first:只寫 min-width

scss
.card {
  padding: 16px;                          // 手機(預設)

  @media (min-width: bp(md))  { padding: 24px; }
  @media (min-width: bp(lg))  { padding: 32px; }
}

由小到大,後面的覆蓋前面的。好處:

  • 只用 min-width 一種條件,不會有邊界重疊。
  • 手機拿到的是最精簡的樣式,不必先套桌機版再覆蓋回來。
  • 新增斷點只是往後追加一段。

混用 min 與 max 時要小心邊界

scss
@media (max-width: 768px) { }   // 768 成立
@media (min-width: 768px) { }   // 768 也成立 ← 重疊

在剛好 768px 時兩條都會套用。要混用的話,max-width 要減去一個極小值:

scss
@function bp-max($name) {
  @return bp($name) - 0.02px;
}

Bootstrap 就是這樣處理的。改用純 mobile-first 則完全不會遇到這個問題。

不只有寬度

同一張表的概念可以延伸到其他媒體特性:

scss
$media: (
  md:      "(min-width: 768px)",
  dark:    "(prefers-color-scheme: dark)",
  motion:  "(prefers-reduced-motion: reduce)",
  hover:   "(hover: hover) and (pointer: fine)",
  print:   "print",
);
scss
@media #{map.get($media, hover)} {
  .btn:hover { background: #1b4dc1; }
}

這裡就需要插值了

map 取出的是字串,要放進 @media 後面必須用 #{} 展開。

(hover: hover) 這個查詢很值得一提,它讓 hover 樣式只在真的有滑鼠的裝置上生效,觸控裝置就不會出現「點一下之後卡在 hover 狀態」的問題。

斷點該怎麼訂

依內容決定,不要對著機型訂。

把瀏覽器從最窄慢慢拉寬,觀察版面在哪裡開始難看:文字行長超過 75 字元、卡片擠成一團、導覽列換行,那裡就是斷點。

訂出來的數字通常會落在常見尺寸附近,但理由完全不同:前者是「這個版面撐不住了」,後者是「某支手機是這麼寬」。裝置尺寸每年都在變,內容的可讀性不會。

RWD 的整體策略見 RWD 最佳實踐媒體查詢

單位用 px 還是 em

scss
$breakpoints: (md: 48em);   // 48 × 16px = 768px

em 的好處是使用者把瀏覽器預設字級調大時,斷點會跟著放大,對無障礙比較友善。px 則直觀好溝通。

兩者都可接受,重點是整個專案統一

常見問題

斷點為什麼不能用 CSS 自訂屬性?

因為自訂屬性只能當屬性值使用,不能出現在媒體查詢的條件裡。斷點的數值必須在編譯期就決定,所以要用 Sass 變數或 map 管理。

斷點該怎麼訂?

依內容決定,不要對著特定機型的螢幕尺寸訂。把瀏覽器慢慢拉寬,版面在哪裡開始難看,那裡就是斷點。訂出來的數字通常會落在常見尺寸附近,但理由不同。

max-width 的斷點要減 0.02px 嗎?

混用 min-widthmax-width 時要,否則在剛好等於斷點的寬度上兩條規則會同時成立。減去一個極小值可避開這個重疊,Bootstrap 就是這樣做的。改用 mobile-first 只寫 min-width 則完全不會遇到。

斷點的單位該用 px 還是 em?

em 的話,使用者調大瀏覽器預設字級時斷點會跟著放大,對無障礙比較友善。用 px 則直觀好溝通。兩者都可接受,重點是整個專案統一。

延伸閱讀