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

## **支援度** [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
不佳------------------------------------------------------佳

`<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)