Tech Blog - Akihiro Suzuki

ブログに Dark Mode を実装

February 18, 2022

本ブログは Gatsby で実装されているわけですが、今回このブログに Dark Mode を実装しました 👍

この記事ではその手順を解説していきます。

まず、 Dark Mode を実装した #PR のリンクを貼っておきます。

GitHub - Introduce dark theme #5

動作を確認したい人はこのブログのホームを開いて、右上の太陽マーク(または月マーク)をクリックすると、テーマが切り替わります。

Tailwind CSS を導入

ダークテーマを実装するにあたり、CSS フレームワークを導入しました。 TailwindCSS が Dark Mode の切り替えにも対応していたのでこれを導入しました。

Tailwind CSS を使った Dark Mode については公式ドキュメントでも解説されています。

tailwindcss - Dark Mode

<!-- Dark mode enabled -->
<html class="dark">
  <body>
    <!-- Will be black -->
    <div class="bg-white dark:bg-black">
      <!-- ... -->
    </div>
  </body>
</html>

このように class の指定で切り替えが出来るのでとても便利です 👍

Gatsby への Tailwind CSS の導入方法は、Tailwind CSS と Gatsby の両方から解説されています。

Tailwind CSS のイントールと設定が出来たら、スタイルシートを Tailwind CSS の記法に書き換えていきます。

これは地道にやるしかないですw ( めっちゃ大変 😂 )

私が行った対応は以下です。

GitHub - Introduce dark theme #5 💄 rewrite with tailwindcss

PrismJS の Theme を変更

PrismJS はコードブロックにテーマを適用してくれるライブラリですが、デフォルトのテーマだと Dark Mode にした時に明る過ぎます。

そこで Dark Mode、Light Mode どちらでも違和感ないテーマを選びます。

最初は、 Dark Mode に切り替わったら PrismJS のテーマも切り替える みたいな実装にしたかったのですが、方法が分からず 💧

PrismJS でデフォルトで用意されているテーマは以下です

GitHub - PrismJS/prism

デフォルドで用意されているテーマを使っても良いのですが、今回は prism-themesを入れ、 Lucario というテーマを使ってみました。

こんな感じです!

package main

import "fmt"

func main() {
	fmt.Println("Hello, World")
}

ポケモンのルカリオをイメージしたテーマらしいですが、赤緑で止まっている私はこのポケモンについてよく知りません。

React Context API を使って Toggle ボタンを実装

CSS 周りの修正が終わったら、いよいよテーマの切り替えボタンなどを実装していきます。 💪

React の Context API を使った Dark Mode の実装について、Gatsby の公式ブログで解説している記事を見つけたので紹介します。 👀

Gatsby - Using React Context API with Gatsby

上の解説ではクラスコンポーネントで実装されていますが、私は関数コンポーネントで実装したかったので、実装方法は多少異なりますが、参考にしながら以下のように実装しました。

# theme.js

import React, { useCallback, useEffect, useState } from "react"

const light = "light"
const dark = "dark"

const defaultState = {
  theme: light, // light|dark
  toggleTheme: () => {},
}

export const ThemeContext = React.createContext(defaultState)

// Getting dark mode information from OS!
// You need macOS Mojave + Safari Technology Preview Release 68 to test this currently.
const supportsDarkMode = () =>
  window.matchMedia("(prefers-color-scheme: dark)").matches === true

export const ThemeProvider = ({ children }) => {
  const [theme, setTheme] = useState(light)

  const toggleTheme = useCallback(() => {
    setTheme(theme === light ? dark : light)
    localStorage.setItem("dark", JSON.stringify(!(theme === dark)))
  }, [theme])

  useEffect(() => {
    const lsDark = JSON.parse(localStorage.getItem("dark"))
    if (lsDark) {
      setTheme(dark)
    } else if (supportsDarkMode()) {
      setTheme(dark)
    }
  }, [])

  return (
    <ThemeContext.Provider value={{ theme, toggleTheme }}>
      {children}
    </ThemeContext.Provider>
  )
}



# ThemeToggleButton.js

import React, { useContext } from "react"
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"
import { faMoon, faSun } from "@fortawesome/free-solid-svg-icons"
import { ThemeContext } from "../lib/theme"

const ThemeToggleButton = ({ className }) => {
  const { toggleTheme, theme } = useContext(ThemeContext)
  const textColor = theme === "light" ? "text-orange-500" : "text-yellow-300"
  const styles = [
    textColor,
    "p-2",
    ...(className ?? '').split(' ')
  ]
  return (
    <button
      className={styles.join(' ')}
      onClick={() => toggleTheme()}
    >
      {theme === "light" ? <FontAwesomeIcon icon={faSun} size="lg" /> : <FontAwesomeIcon icon={faMoon} size="lg" />}
    </button>
  )
}

export default ThemeToggleButton

Gatsby で Context API を使う設定

wrapRootElement 関数を実装して export すると Gatsby が適切に呼び出してくれるので、上で実装した ThemeProvider で wrap して return します。

# gatsby-browser.js

import { ThemeProvider } from "./src/lib/theme"

export const wrapRootElement = ({ element }) => (
  <ThemeProvider>{element}</ThemeProvider>
)

おわりに

あとは適当な場所に Toggle ボタンを設置すれば完成です。 🎉

以下の太陽ボタン(月ボタン)がそれです。

Light Mode Dark Mode
light mode dark mode

profile

鈴木 章弘 (Suzuki Akihiro)
バックエンドエンジニア(TypeScript & マイクロサービス)
profile