---
# System prepended metadata

title: prefers-color-scheme
tags: [css/media query]

---

# 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
Ｑ有些圖在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)