天天看點

使用 Gatsby.js 搭建靜态部落格黑暗模式

使用 Gatsby.js 搭建靜态部落格黑暗模式

沒想到久違的 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'      

本文完〜

繼續閱讀