使用 Gatsby.js 搭建靜態(tài)博客黑暗模式

來(lái)源 | https://ssshooter.com/2020-06-25-gatsby-blog-8/
方案 1
@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 用戶(hù)還要自己整理一下兩個(gè)模式的 prism 樣式)
css 變量除了在 IE 不能用之外其他瀏覽器的適應(yīng)性還算不錯(cuò),如果必須適配 IE 的話(huà)需要另外寫(xiě)一份不用變量的樣式保底。
方案 2
按上面的寫(xiě)法其實(shí)已經(jīng)可以實(shí)現(xiàn)根據(jù)系統(tǒng)配置轉(zhuǎn)換明亮和黑暗模式,但是要做到直接在網(wǎng)頁(yè)通過(guò)一個(gè)切換按鈕手動(dòng)修改還是遠(yuǎn)遠(yuǎn)不夠,方案 2 登場(chǎng)。
因?yàn)楝F(xiàn)在 JavaScript 不能改變系統(tǒng)級(jí)的明暗模式,只能獲取和監(jiān)聽(tīng)模式變化,所以手動(dòng)操作鐵定不能靠 prefers-color-scheme 了。
我們需要反過(guò)來(lái)通過(guò)?JavaScript?獲取 color-scheme 然后告訴 CSS 明暗模式發(fā)生變化,這樣的變化包括系統(tǒng)級(jí)的改變和用戶(hù)在網(wǎng)頁(yè)選擇改變。
首先把兩組變量的作用范圍改為 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;}
下面這一段代碼我寫(xiě)在 Layout.js 的 componentDidMount 中,只有這里才是服務(wù)器渲染的范圍之外。
// setTheme 的實(shí)現(xiàn)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 lightthis.setTheme('light')}// 第二部分const darkModeMediaQuery = window.matchMedia('(prefers-color-scheme: dark)')darkModeMediaQuery.addListener(e => {const darkModeOn = e.matchesif (darkModeOn) {this.setTheme('dark')} else {this.setTheme('light')}})
先講 setTheme 的實(shí)現(xiàn):
數(shù)據(jù)持久化
實(shí)現(xiàn)設(shè)置整個(gè)文檔的 class,然后 CSS 根據(jù) class 配置變量(而不是方案 1 的媒體查詢(xún))
修改組件狀態(tài)
第一部分是頁(yè)面初始化時(shí)處理頁(yè)面主題的邏輯:
首先查詢(xún)有無(wú)被保存到 localstorage 的主題,有的話(huà)直接使用
沒(méi)有的話(huà)使用 JavaScript 查詢(xún)系統(tǒng)明暗方案,然后按系統(tǒng)配置設(shè)定
上面兩種都不可行的情況下默認(rèn)使用 light
第二部分是 JavaScript 監(jiān)聽(tīng)系統(tǒng)明暗方案修改,響應(yīng)式地改變頁(yè)面的明暗方案(意味著不需要刷新頁(yè)面)
剩下的切換按鈕就沒(méi)什么特別的,所以不放代碼了。
hack
最后附帶一個(gè)處理畫(huà)面閃爍的方法。
雖然已經(jīng)在樣式添加 transition 屬性,但是從 JavaScript 程序知道用戶(hù)需要的明暗模式前仍然有一段空擋。如果用戶(hù)選擇了黑暗模式,就會(huì)造成畫(huà)面先是明亮模式,然后幾百毫秒后變?yōu)楹诎的J降膶擂尉置妗?/span>
我的解決方案是先把頁(yè)面 display 設(shè)為 none,在判斷并設(shè)置完頁(yè)面 class 之后再將頁(yè)面設(shè)為可見(jiàn),這樣就避免了畫(huà)面閃爍,但是頁(yè)面展示時(shí)間會(huì)慢一點(diǎn)點(diǎn)點(diǎn)點(diǎn)。
document.documentElement.style.display = 'none'// 同步的判斷程序document.documentElement.style.display = 'block'

