前言
深色模式(Dark Mode), 也叫暗黑模式, 顾名思义, 它给人最直观的感触, 就是黑.
但「深色模式」要实现理想的视觉体验, 不只是简略地将底色变黑, 将文字变白这么简答. Google 在 Material Design 的设计指南中对于「深色模式」列出的设计规范中, 第一条就是『不要应用 100% 的纯黑』.
前端工程师须要和设计师沟通, 如何做出一个对用户体验良好、实现老本正当的「彩色模式」计划;另一方面也须要关怀, 如何正当应用 CSS 组合来实现「深色模式」.那么, 前端如何检测以后运行环境出于「深色模式」呢?, 并依据以后的色彩模式, 编写对应的 CSS 呢?
「深色模式」相干的 CSS MediaQuery
以后的浏览器或者其它 WebView 大都反对了对于深色模式的 CSS MediaQuery prefers-color-scheme
, 它的格局如下:
@media (prefers-color-scheme: <light|dark>)
其中, light
示意「浅色模式」, dark
示意「深色模式」.
在 CSS 中应用
/* Light mode */@media (prefers-color-scheme: light) { body { color: black; background-color: white; }}/* Dark mode */@media (prefers-color-scheme: dark) { body { color: white; background-color: black; }}
对应的, 你也能够在 styled-components
这类 CSS in JS 计划中应用这样.
通过 JS 检测以后环境是否处于深色模式
在我的开发工作中, 我波及到了在基于 WebKit 引擎中检测「以后环境是否为深色模式」的需要.如何通过 JS 来检测以后环境是否为「深色模式」呢?既然 CSS 是通过 MediaQuery 来判断. 自然而然地, 咱们会想到应用 [matchMedia
] API 来判断.
window.matchMedia('<mqString>')
返回一个 listenable-like 对象 [MediaQueryList
], 它继承自 [EventTarget
], 这意味着你能够通过间接它取得最新的 MediaQuery 检测状况:
// detect if on light modevar isLight = window.matchMedia('(prefers-color-scheme: light)').matches;// detect if on dark modevar isDarkMode = window.matchMedia('(prefers-color-scheme: dark)').matches;
对于支流浏览器而言, [matchMedia
] API 的反对较好, Chrome >=9
, Safari >=5.1
即反对. 更多浏览器的反对状况可参考 [Can I use matchMedia
]
监听以后环境色彩模式的变动
同时, window.matchMedia('<mqString>')
返回的 [MediaQueryList
] 具备 addEventListener
/removeEventListener
接口. 在环境的 MediaQuery 个性发生变化时, [MediaQueryList
] 会 emit change
事件. 这意味着你能够监听它相干的 MediaQuery 的最新状况:
// listenable-like object [`MediaQueryList`]var mqList = window.matchMedia('<mqString>');mqList.addEventListener('change', (event) => { // matched prefer if (event.matches) { } else { // not match prefer }});
于是, 你能够在环境的色彩模式变动时第一工夫检测到:
var mqList = window.matchMedia('(prefers-color-scheme: dark)');mqList.addEventListener('change', (event) => { // is dark mode if (event.matches) { } else { // not dark mode }});
MediaQueryList
对象的兼容性问题
参考兼容性列表, 咱们能发现一些问题. 比方, IE 和 Opera 不反对在 addEventListener(...)
时, 应用对象作为 EventListener
传入
Safari
援用 MDN 上兼容性列表的 note:
Before Safari 14, [MediaQueryList
] is based onEventTarget
, so you must useaddListener()
andremoveListener()
to observe media query lists.
对于 Safari < 14 而言, [MediaQueryList
] 有以下兼容性问题,
- 没有
MediaQueryList.addEventListener
, 只有 [MediaQueryList.addListener
], 其签名为addListener(func)
, 参考这里 - 没有
MediaQueryList.removeEventListener
, 只有 [MediaQueryList.removeListener
], 其签名为addListener(func)
, 参考这里
Internet Explorer
- 只有 IE >= 10 的版本反对 [
MediaQueryList
] 个性. - 在 IE 上, [
MediaQueryList
] 对象并非继承自 [EventTarget
]. 它反对 [MediaQueryList.addListener
] 和 [MediaQueryList.removeListener
]
解决兼容性问题
容易想到, 咱们只须要须要将上文的代码稍作更改, 就能够在 Safari < 14, IE >= 10 的环境下顺利运行:
var mqList = window.matchMedia('<mqString>');// 检测 MediqQueryList 对象上是否存在 addEventListener 办法if (mqList.addEventListener) { mqList.addEventListener('change', (event) => {});} else if (mqList.addListener) { // 否则, 检测 MediqQueryList 对象上是否存在 addListener 办法 mqList.addListener((event) => {});}
对 removeEventListener
/removeListener
的解决相似. 咱们能够应用一个函数 observeMediaChange(medieQuery, listener)
来暗藏解决细节:
/** * @param {MediaQueryList} mqList * @param {((this: MediaQueryList, ev: MediaQueryListEvent) => any)} listener */function observeMediaChange(mqList, listener) { let disposeFunc = () => {}; if (mqList.addEventListener && mqList.removeEventListener) { mqList.addEventListener('change', listener); disposeFunc = () => { mqList.removeEventListener('change', listener); }; } else if (mqList.addListener && mqList.removeListener) { mqList.addListener(listener); disposeFunc = () => { mqList.removeListener(listener); }; } return disposeFunc;}var mqList = window.matchMedia('<mqString>');observeMediaChange(mqList, (event) => { // is dark mode if (event.matches) { } else { // not dark mode }});
React Hooks 封装
咱们能够基于 window.matchMedia('<mqString>')
返回的 [MediaQueryList
] 来封装一个即时而灵活的 useIsDarkMode
Hooks:
import { useState, useEffect } from 'react';function checkIsDarkMode() { try { return window.matchMedia('(prefers-color-scheme: dark)').matches; } catch (err) { return false; }}function useIsDarkMode() { const [isDarkMode, setIsDarkMode] = useState(checkIsDarkMode()); useEffect(() => { const mqList = window.matchMedia('(prefers-color-scheme: dark)'); /** * * @param {MediaQueryListEvent} event */ const listener = (event) => { setIsDarkMode(event.matches); }; mqList.addEventListener('change', listener); return () => { mqList.removeEventListener('change', listener); }; }, []); return isDarkMode;}
在 React Functional Component 中能够这样应用:
function Foo() { const isDarkMode = useIsDarkMode(); return <div className={isDarkMode ? 'theme-dark' : 'theme-light'}>...</div>;}
留神 上述 useIsDarkMode
实现未思考浏览器兼容性问题, 如果须要在 Safari < 14 这样的环境中运行, 可应用 observeMediaChange
来革新 useIsDarkMode
外部的实现
扩大浏览
- [Can I use
matchMedia
] - [
MediaQueryList
]
工作机会
若你是校招生正在寻求实习, 或者你思考换工作, 可将简历发到 richardo2016#gmail.com. 我可帮助内推以下公司:
- 阿里巴巴(深圳, 杭州, 北京, 上海, 成都)
- 网易(杭州)
- 腾讯(成都, 深圳)
- 字节跳动(杭州)