
沒想到久違的 Gatsby 系列還能繼續寫,最近為部落格更新了黑暗模式和手動切換功能,順便記錄下來。當然下面的實作方案不限于 Gatsby 使用,對于其他架構,思路都大同小異。
方案 1
最初實作的方案是直接使用媒體查詢和 css 變量。關鍵是把區分兩個模式的變量抽離出來,分别配置兩組變量,核心代碼如下:
@media screen and (prefers-color-scheme: dark) {
:root {
--linkColor: #ec9bab;
--fontColor: #ecc3cb;
--codeBlock: #3c495b;
--code: #c3c7cb;
--divider: #3e4b5e;
}
}
@media screen and (prefers-color-scheme: light) {
:root {
--linkColor: #683741;
--fontColor: hsl(284, 20%, 30%);
--codeBlock: #3c495b;
--code: #c3c7cb;
--divider: #eee;
}
}
body {
background-color: var(--backgroundColor);
color: var(--fontColor);
transition: all 0.5s ease-in-out;
}
(prism 使用者還要自己整理一下兩個模式的 prism 樣式)
css 變量除了在 IE 不能用之外其他浏覽器的适應性還算不錯,如果必須适配 IE 的話需要另外寫一份不用變量的樣式保底。
方案 2
按上面的寫法其實已經可以實作根據系統配置轉換明亮和黑暗模式,但是要做到直接在網頁通過一個切換按鈕手動修改還是遠遠不夠,方案 2 登場。
因為現在 JavaScript 不能改變系統級的明暗模式,隻能擷取和監聽模式變化,是以手動操作鐵定不能靠 prefers-color-scheme 了。
我們需要反過來通過 JavaScript 擷取 color-scheme 然後告訴 CSS 明暗模式發生變化,這樣的變化包括系統級的改變和使用者在網頁選擇改變。
首先把兩組變量的作用範圍改為 class:
.light-theme {
--linkColor: #ec9bab;
--fontColor: #ecc3cb;
--codeBlock: #3c495b;
--code: #c3c7cb;
--divider: #3e4b5e;
}
.dark-theme {
--linkColor: #683741;
--fontColor: hsl(284, 20%, 30%);
--codeBlock: #3c495b;
--code: #c3c7cb;
--divider: #eee;
}
下面這一段代碼我寫在 Layout.js 的 componentDidMount 中,隻有這裡才是伺服器渲染的範圍之外。
// setTheme 的實作
setTheme = themeName => {
localStorage.setItem('theme', themeName)
document.documentElement.className = themeName + '-theme'
this.setState({
theme: themeName,
})
}
// 第一部分
let localTheme = localStorage.getItem('theme')
if (localTheme) {
if (localTheme === 'dark') {
this.setTheme('dark')
} else {
this.setTheme('light')
}
} else if (window.matchMedia) {
if (window.matchMedia('(prefers-color-scheme: dark)').matches) {
this.setTheme('dark')
} else {
this.setTheme('light')
}
} else {
// default light
this.setTheme('light')
}
// 第二部分
const darkModeMediaQuery = window.matchMedia('(prefers-color-scheme: dark)')
darkModeMediaQuery.addListener(e => {
const darkModeOn = e.matches
if (darkModeOn) {
this.setTheme('dark')
} else {
this.setTheme('light')
}
})
先講 setTheme 的實作:
資料持久化
實作設定整個文檔的 class,然後 CSS 根據 class 配置變量(而不是方案 1 的媒體查詢)
修改元件狀态
第一部分是頁面初始化時處理頁面主題的邏輯:
首先查詢有無被儲存到 localstorage 的主題,有的話直接使用
沒有的話使用 JavaScript 查詢系統明暗方案,然後按系統配置設定
上面兩種都不可行的情況下預設使用 light
第二部分是 JavaScript 監聽系統明暗方案修改,響應式地改變頁面的明暗方案(意味着不需要重新整理頁面)
剩下的切換按鈕就沒什麼特别的,是以不放代碼了。
hack
最後附帶一個處理畫面閃爍的方法。
雖然已經在樣式添加 transition 屬性,但是從 JavaScript 程式知道使用者需要的明暗模式前仍然有一段空擋。如果使用者選擇了黑暗模式,就會造成畫面先是明亮模式,然後幾百毫秒後變為黑暗模式的尴尬局面。
我的解決方案是先把頁面 display 設為 none,在判斷并設定完頁面 class 之後再将頁面設為可見,這樣就避免了畫面閃爍,但是頁面展示時間會慢一點點點點。
document.documentElement.style.display = 'none'
// 同步的判斷程式
document.documentElement.style.display = 'block'
本文完〜