# prefers-color-scheme ###### tags: `css/media query` 屬於CSS Media Query,用來檢測用戶是否有將系統的主題色設置為亮色(light mode)或暗色(Dark Mode)。 https://codepen.io/phoebe4561/pen/oNGPBpg?editors=0010 ![](https://i.imgur.com/kg80MfF.png) ## **支援度** [https://caniuse.com/prefers-color-scheme](https://caniuse.com/prefers-color-scheme) 暗色主題本身是由 Apple 先提出的,所以 Mac OS、iOS 的支援度較高 ### **設計規範參考** 目前在 Apple 及 Material Design 中也都有針對亮色、暗色的佈景主題提供規範。 Material Design - Material Design:[https://design.google/library/material-design-dark-theme/](https://design.google/library/material-design-dark-theme/) Apple - Mac:[https://developer.apple.com/design/human-interface-guidelines/macos/visual-design/dark-mode/](https://developer.apple.com/design/human-interface-guidelines/macos/visual-design/dark-mode/) - iOS:[https://developer.apple.com/design/human-interface-guidelines/ios/visual-design/dark-mode/](https://developer.apple.com/design/human-interface-guidelines/ios/visual-design/dark-mode/) ## 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中 ```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 (--變數名稱)` 來套用。 ```css :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](https://developer.mozilla.org/en-US/docs/Web/API/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()` ```javascript= 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 ```javascript= MediaQueryList.addEventListener("change", () => { ..... }); ``` * Return value Void. **透過呼叫window.matchMedia()方法創建`MediaQueryList` object** ```javascript= 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狀態的改變 ```javascript= const useDark = window.matchMedia("(prefers-color-scheme: dark)"); ``` 2. 建立`toggleDarkMode` function :傳入一個參數(Boolean value)並根據傳入的參數t or f 決定是否在根原件上 `document.documentElement` (`<html>` element) 添加 `.dark-mode` class ```javascript= 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 3. 新增一listener監聽使用者點擊btn時或是系統設定改變時 ```javascript= // 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 ```javascript= 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 進行選擇性加載 ```html <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 不佳------------------------------------------------------佳 ![](https://i.imgur.com/N9nidAr.png) `<picture>` * 包含了零或零以上個`<source>`元素以及一個`<img>`元素,以為不同裝置提供同張圖片的不同版本。 * 本身只是一個容器、只包含 global attributes。 * `<source>`順序很重要 瀏覽器會直接使用第一個符合的`<source>`而忽略掉後續的標籤。 * 在`<picture>`結束前指定一個`<img>` 當瀏覽器不支援`<picture>`或是沒有符合的`<source>`時,則`<img>`屬性會被選擇。 * 為了決定載入哪一個 URL,user agent會檢視每一個 `<source>` 的 srcset、media以及 type屬性,以選出最適合當前版面以及顯示裝置支援度的圖片。 media 屬性 指定特定的媒體類型(跟 media query 很像)。如果 <source>的指定媒體類型被判斷為 false ,則瀏覽器會跳過它。 ```html <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 螢幕提供高解析度圖片 ```html <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>元素被略過。 ```html <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](https://developer.mozilla.org/zh-CN/docs/Web/CSS/filter) ```css @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 ```html <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](https://developer.mozilla.org/en-US/docs/Web/API/MediaQueryList) [MediaQueryList.addListener()](https://developer.mozilla.org/zh-CN/docs/Web/API/MediaQueryList/addListener) [window.matchMedia()](https://developer.mozilla.org/en-US/docs/Web/API/Window/matchMedia#return_value) [關於<picture>的詳細說明](http://www.w3.org/html/wg/drafts/html/master/semantics.html#the-picture-element)