CSSでダークモード/ライトモードの切り替えを実装する

## はじめに
最近のWebアプリケーションでは、ユーザーの好みに応じてダークモードとライトモードを切り替える機能が一般的になってきています。この記事では、CSSのカスタムプロパティ（CSS変数）を使用して、シンプルかつ効果的なテーマ切り替え機能を実装する方法をご紹介します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2648069/6af30d36-6da3-2104-bf91-1cc55a4b1db1.png)


## 成果物のイメージ
ボタンによりテーマ切り替える。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2648069/ed0530c2-0823-a6fe-5748-75da47a80838.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2648069/428061d7-6c60-694b-7f9a-4585329d467a.png)


## 実装のポイント
- CSS変数を使用してテーマカラーを管理
- レスポンシブデザインへの対応
- アニメーションによる視覚的な演出
- JavaScriptによる動的なテーマ切り替え

## 実装手順

### 1. プロジェクトの構成
まず、以下のような構成でファイルを作成します：

```
output/
  ├── dark-theme.css
  ├── light-theme.css
  ├── responsive.css
  ├── animation.css
  └── index.html
```

### 2. テーマごとのCSS定義

#### ダークテーマ（dark-theme.css）
```css
:root {
    --background-color: #121212;
    --text-color: #ffffff;
    --primary-color: #BB86FC;
    --secondary-color: #03DAC6;
}

body {
    background-color: var(--background-color);
    color: var(--text-color);
}

.card {
    background-color: #1E1E1E;
    border: 1px solid #333333;
}
```

#### ライトテーマ（light-theme.css）
```css
:root {
    --background-color: #ffffff;
    --text-color: #333333;
    --primary-color: #6200EE;
    --secondary-color: #03DAC6;
}

body {
    background-color: var(--background-color);
    color: var(--text-color);
}

.card {
    background-color: #f5f5f5;
    border: 1px solid #e0e0e0;
}
```

### 3. レスポンシブデザインの実装（responsive.css）
```css
html {
    font-size: 16px;
}

h1 {
    font-size: 2.5em;
}

@media (max-width: 768px) {
    html {
        font-size: 14px;
    }
    h1 {
        font-size: 2em;
    }
}
```

### 4. アニメーションの追加（animation.css）
```css
@keyframes fadeIn {
    from {
        opacity: 0;
    }
    to {
        opacity: 1;
    }
}

.fade-in {
    animation: fadeIn 1.5s ease-in-out;
}
```

### 5. HTMLの実装
```html
<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>CSSテーマ確認</title>
    <link id="theme-link" rel="stylesheet" href="output/dark-theme.css">
    <link rel="stylesheet" href="output/responsive.css">
    <link rel="stylesheet" href="output/animation.css">
    <style>
        .switch-buttons {
            margin: 20px;
        }
        button {
            margin: 5px;
            padding: 10px 20px;
            cursor: pointer;
        }
        .card {
            margin: 20px auto;
            padding: 30px;
            width: 80%;
            max-width: 500px;
            text-align: center;
            border-radius: 8px;
        }
    </style>
</head>
<body>
    <div class="switch-buttons">
        <button onclick="switchTheme('dark')">ダークテーマ</button>
        <button onclick="switchTheme('light')">ライトテーマ</button>
    </div>
    <h1 class="fade-in">CSSテーマ確認ページ</h1>
    <div class="card fade-in">
        カードデザインのサンプルです。
    </div>
    <script>
        function switchTheme(theme) {
            const link = document.getElementById('theme-link');
            if (theme === 'dark') {
                link.href = 'output/dark-theme.css';
            } else {
                link.href = 'output/light-theme.css';
            }
        }
    </script>
</body>
</html>
```

### 6. Pythonによるファイル生成
以下のPythonスクリプトを使用して、必要なファイルを自動生成します：

```python
import os

os.makedirs('output', exist_ok=True)

# CSSファイルの内容
dark_css = '''
:root {
    --background-color: #121212;
    --text-color: #ffffff;
    --primary-color: #BB86FC;
    --secondary-color: #03DAC6;
}
body {
    background-color: var(--background-color);
    color: var(--text-color);
}
.card {
    background-color: #1E1E1E;
    border: 1px solid #333333;
}
'''

light_css = '''
:root {
    --background-color: #ffffff;
    --text-color: #333333;
    --primary-color: #6200EE;
    --secondary-color: #03DAC6;
}
body {
    background-color: var(--background-color);
    color: var(--text-color);
}
.card {
    background-color: #f5f5f5;
    border: 1px solid #e0e0e0;
}
'''

responsive_css = '''
html {
    font-size: 16px;
}
h1 {
    font-size: 2.5em;
}
@media (max-width: 768px) {
    html {
        font-size: 14px;
    }
    h1 {
        font-size: 2em;
    }
}
'''

animation_css = '''
@keyframes fadeIn {
    from {
        opacity: 0;
    }
    to {
        opacity: 1;
    }
}
.fade-in {
    animation: fadeIn 1.5s ease-in-out;
}
'''

# HTMLの内容
html_content = '''
<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>CSSテーマ確認</title>
    <link id="theme-link" rel="stylesheet" href="output/dark-theme.css">
    <link rel="stylesheet" href="output/responsive.css">
    <link rel="stylesheet" href="output/animation.css">
    <style>
        .switch-buttons {
            margin: 20px;
        }
        button {
            margin: 5px;
            padding: 10px 20px;
            cursor: pointer;
        }
        .card {
            margin: 20px auto;
            padding: 30px;
            width: 80%;
            max-width: 500px;
            text-align: center;
            border-radius: 8px;
        }
    </style>
</head>
<body>
    <div class="switch-buttons">
        <button onclick="switchTheme('dark')">ダークテーマ</button>
        <button onclick="switchTheme('light')">ライトテーマ</button>
    </div>
    <h1 class="fade-in">CSSテーマ確認ページ</h1>
    <div class="card fade-in">
        カードデザインのサンプルです。
    </div>

    <script>
        function switchTheme(theme) {
            const link = document.getElementById('theme-link');
            if (theme === 'dark') {
                link.href = 'output/dark-theme.css';
            } else {
                link.href = 'output/light-theme.css';
            }
        }
    </script>
</body>
</html>
'''

# ファイルを出力
with open('output/dark-theme.css', 'w') as f:
    f.write(dark_css)

with open('output/light-theme.css', 'w') as f:
    f.write(light_css)

with open('output/responsive.css', 'w') as f:
    f.write(responsive_css)

with open('output/animation.css', 'w') as f:
    f.write(animation_css)

with open('output/index.html', 'w') as f:
    f.write(html_content)

print("CSSとHTMLファイルを生成しました。")

```

## 実装のポイント解説

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2648069/8fb45ee2-d363-b6cc-a4bd-0229dd98f1fc.png)


### CSS変数の活用
テーマカラーをCSS変数として定義することで、以下のメリットがあります：

1. 色の一元管理が可能
2. 変数の再利用が容易
3. JavaScriptからの操作も可能

### レスポンシブデザイン
- メディアクエリを使用して画面サイズに応じたスタイル調整
- フォントサイズをemで指定し、相対的なサイズ調整を実現

### アニメーション
- `@keyframes`を使用したフェードインアニメーション
- ページの読み込み時やテーマ切り替え時の視覚的な演出

### テーマ切り替えの実装
JavaScriptを使用して`link`要素の`href`属性を動的に切り替えることで、テーマの切り替えを実現しています。

## まとめ

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2648069/0ee4ddd1-9637-61c2-f71e-b5518f910f9d.png)

この実装方法のメリットは以下の通りです：

1. シンプルで理解しやすい構造
2. CSS変数による効率的なテーマ管理
3. スムーズなアニメーション効果
4. レスポンシブ対応による優れたユーザー体験

今回の実装は基本的な例ですが、これをベースに以下のような拡張が可能です：

- ユーザーの設定を保存（LocalStorage使用）
- システムのテーマ設定との連動
- より複雑なアニメーション効果の追加

## 参考資料
- [CSS Custom Properties (Variables) - MDN](https://developer.mozilla.org/ja/docs/Web/CSS/Using_CSS_custom_properties)
- [CSS Animations - MDN](https://developer.mozilla.org/ja/docs/Web/CSS/CSS_Animations/Using_CSS_animations)