Try   HackMD

prefers-color-scheme

tags: css/media query

屬於CSS Media Query,用來檢測用戶是否有將系統的主題色設置為亮色(light mode)或暗色(Dark Mode)。
https://codepen.io/phoebe4561/pen/oNGPBpg?editors=0010

Image Not Showing Possible Reasons
  • The image file may be corrupted
  • The server hosting the image is unavailable
  • The image path is incorrect
  • The image format is not supported
Learn More →

支援度 https://caniuse.com/prefers-color-scheme

暗色主題本身是由 Apple 先提出的,所以 Mac OS、iOS 的支援度較高

設計規範參考

目前在 Apple 及 Material Design 中也都有針對亮色、暗色的佈景主題提供規範。

Material Design

Apple

prefers-color-scheme

There are two prefers-color-scheme values to choose from:

  • light - the user has expressed preference for a page that has a light mode or has not expressed an active preference
  • dark - the user has expressed preference for a page that has a dark mode

直接用prefers-color-scheme在css中

/* default, light mode styling */
.element {
  background-color: #fff;
  color: #000;
}

/* if user switches the system settings to dark mode */
/* this media query will be applied */
@media (prefers-color-scheme: dark) {
  .element {
    background-color: #000;
    color: #fff;
  }
}

暗色系與亮色中的色彩並不是黑白兩色互換,如果透過一個個的 @media 設定將會增加開發及管理的困難度,通常搭配CSS Variables 快速切換網頁的主題色

CSS Variables
(1)宣告變數:建議定義在 :root 最高層級的選取器便於取用,並且使用 --自訂名稱作為屬性的方式來宣告變數。
(2)取值:前方是撰寫我們需要套用的樣式屬性,後方使用 var (--變數名稱) 來套用。

:root {
  --theme-primary: #6222ee;
  --theme-background: #eee;
}

.element {
  /* ... */
  background-color: var(--theme-background);
  color: var(--theme-primary);
}

@media (prefers-color-scheme: dark) {
  .element {
    --theme-primary: #bb85fc;
    --theme-background: #111;
  }
}

js控制prefers-color-scheme

  1. 建立MediaQueryList object

MediaQueryList object維護一組針對 document 的 media query , 並且當 media query 相對應的文件狀態改變時,觸發註冊的事件處理器通知之。

The MediaQueryList object stores information from a media query.
The MediaQueryList object is a property of the window object.

  • Properties
    matches
    A boolean value
    回傳true 當 document 目前狀態符合 media query list 所維護的條件; 否則 false。
  • Methods
    addListener()
MediaQueryList.addListener(func)
  • Parameters
    func
    A function or function reference representing the callback function you want to run when the media query status changes. => change event

作為MediaQueryList object的callback function

MediaQueryList.addEventListener("change", () => { ..... });
  • Return value
    Void.

透過呼叫window.matchMedia()方法創建MediaQueryList object

mqList = window.matchMedia(mediaQueryString)

Parameters

mediaQueryString

  • A string
  • 指定要解析成為MediaQueryList object的media query字串

Return value

  • A new MediaQueryList object for the media query.
    用此object的屬性和方法去偵測 matches property

建立MediaQueryList object useDark : 偵測media query狀態的改變

const useDark = window.matchMedia("(prefers-color-scheme: dark)");
  1. 建立toggleDarkMode function :傳入一個參數(Boolean value)並根據傳入的參數t or f 決定是否在根原件上 document.documentElement (<html> element) 添加 .dark-mode class
function toggleDarkMode(state) { document.documentElement.classList.toggle("dark-mode", state); } toggleDarkMode(useDark.matches);

useDark object有matches property,用來檢測prefers-color-scheme 的狀態

useDark.matches returns a Boolean 傳入 toggleDarkMode function的參數,作為初始化模式(因為我們沒有在CSS中設定prefers-color-scheme media query)

若使用者啟用dark mode 則 useDark.matches return true, toggleDarkMode 新增 .dark-mode class,第一次開啟網站顯示dark mode

  1. 新增一listener監聽使用者點擊btn時或是系統設定改變時
// Listening for the changes in the OS settings and auto switching the mode (1) useDark.addListener((evt) => toggleDarkMode(evt.matches)); (2) useDark.addEventListener('change', (evt) => { toggleDarkMode(evt.matches); }); // Toggles the "dark-mode" class on click const button = document.querySelector(".btn"); button.addEventListener("click", () => { document.documentElement.classList.toggle("dark-mode");

記住使用者選擇的模式

初始載入的設定由 localStorage state取得狀態 而非基於prefers-color-scheme 的value

let darkModeState = false; const button = document.querySelector(".btn"); // MediaQueryList object const useDark = window.matchMedia("(prefers-color-scheme: dark)"); // Toggles the "dark-mode" class function toggleDarkMode(state) { document.documentElement.classList.toggle("dark-mode", state); darkModeState = state; } // Sets localStorage state function setDarkModeLocalStorage(state) { localStorage.setItem("dark-mode", state); } // Initial setting toggleDarkMode(localStorage.getItem("dark-mode") == "true"); // Listen for changes in the OS settings. useDark.addListener((evt) => toggleDarkMode(evt.matches)); // Toggles the "dark-mode" class on click and sets localStorage state button.addEventListener("click", () => { darkModeState = !darkModeState; toggleDarkMode(darkModeState); setDarkModeLocalStorage(darkModeState); });

圖片與Dark Mode

Q有些圖在dark mode中非常亮

解決方式:

(1)使用<picture> 中的media attribute 進行選擇性加載

<picture>
  <source srcset="light-image.jpg" media="(prefers-color-scheme: light)" />
  <source srcset="dark-image.jpg" media="(prefers-color-scheme: dark)" />
  <img src="light-image.jpg" />
</picture>

<picture> 支援度 https://caniuse.com/?search=picture

不佳

<picture>

  • 包含了零或零以上個<source>元素以及一個<img>元素,以為不同裝置提供同張圖片的不同版本。
  • 本身只是一個容器、只包含 global attributes。
  • <source>順序很重要
    瀏覽器會直接使用第一個符合的<source>而忽略掉後續的標籤。
  • <picture>結束前指定一個<img>
    當瀏覽器不支援<picture>或是沒有符合的<source>時,則<img>屬性會被選擇。
  • 為了決定載入哪一個 URL,user agent會檢視每一個 <source> 的 srcset、media以及 type屬性,以選出最適合當前版面以及顯示裝置支援度的圖片。

media 屬性
指定特定的媒體類型(跟 media query 很像)。如果 <source>的指定媒體類型被判斷為 false ,則瀏覽器會跳過它。

<picture>
  <source srcset="mdn-logo-wide.png" media="(min-width: 600px)">
  <img src="mdn-logo-narrow.png" alt="MDN">
</picture>

srcset 屬性
用來提供根據大小區分的可能的圖片清單。
每一個圖片描述句是由該圖片的 URL 以及以下描述組成(擇一):
1.寬度,結尾為 w
2.像素密度,結尾為 x,為高 DPI 螢幕提供高解析度圖片

<picture>
  <source srcset="logo-768.png 768w, logo-768-1.5x.png 1.5x">
  <source srcset="logo-480.png, logo-480-2x.png 2x">
  <img src="logo-320.png" alt="logo">
</picture>

type 屬性
為URL 指定 MIME type 。如果 user agent 不支援該 type 的話,此 <source>元素被略過。

<picture>
  <source srcset="logo.webp" type="image/webp">
  <img src="logo.png" alt="logo">
</picture>
  • <picture> 的常見使用案例:
  1. 圖像方向(art direction): 根據不同的 media 狀況裁切或調整圖片(例如在較小的螢幕上,載入原本有複雜細節圖片的較簡單版本圖片)
  2. 提供替代的圖片格式:以應對某些特定格式不被支援的情況
  3. 節省頻寬並加速頁面載入速度:透過針對觀看者的裝置載入最適當的圖片做到這點

(2)使用css filter

@media (prefers-color-scheme: dark) {
/* we are excluding SVG */
img:not([src$=".svg"]) {
    filter: brightness(70%);
  }
}

prefers-color-scheme meta tag設定

需要新增<meta>標註是否支援light, dark, or both modes, 因為某些UI元件可能尚未完整支援Dark Mode

<meta name="color-scheme" content="light dark" />

參考資料

https://www.ditdot.hr/en/dark-mode-website-tutorial
https://wcc723.github.io/css/2019/12/22/css-dark-mode/
MediaQueryList
MediaQueryList.addListener()
window.matchMedia()
關於<picture>的詳細說明