Тёмная тема на сайте: полное руководство по CSS и JavaScript с сохранением выбора пользователя

Тёмная тема на сайте: полное руководство по CSS и JavaScript с сохранением выбора пользователя

Тёмная тема перестала быть просто модным трендом — сегодня это ожидаемая пользователями функция, которая снижает нагрузку на глаза, экономит заряд батареи на мобильных устройствах и просто выглядит стильно. В этом руководстве мы разберём, как правильно реализовать переключение между светлой и тёмной темами на вашем сайте с помощью CSS и JavaScript, сохраняя выбор пользователя и учитывая системные настройки.

Почему тёмная тема — это важно

Исследования показывают, что более 80% пользователей предпочитают использовать тёмные темы в вечернее время, а многие — постоянно. Это не просто эстетическое предпочтение: тёмный интерфейс уменьшает синее излучение экрана, что особенно важно при работе в тёмное время суток. Кроме того, на OLED-экранах чёрные пиксели вообще не потребляют энергию, что продлевает автономность устройства.

Важно: реализуя тёмную тему, вы не просто меняете цвета местами. Нужно пересчитывать контрастность, проверять читаемость текста и адаптировать изображения для разных режимов.

Базовый подход: CSS-переменные

Современный способ реализации тем — использование CSS-переменных (custom properties). Это позволяет централизованно управлять цветовой палитрой и легко переключаться между темами.

Определяем цветовые схемы

Создадим корневые переменные для светлой и тёмной тем:

:root {
  /* Светлая тема (по умолчанию) */
  --bg-primary: #ffffff;
  --bg-secondary: #f5f5f5;
  --text-primary: #333333;
  --text-secondary: #666666;
  --accent-color: #4a90e2;
  --border-color: #e0e0e0;
}

[data-theme="dark"] {
  /* Тёмная тема */
  --bg-primary: #121212;
  --bg-secondary: #1e1e1e;
  --text-primary: #ffffff;
  --text-secondary: #b0b0b0;
  --accent-color: #64b5f6;
  --border-color: #333333;
}

Применяем переменные в стилях

Теперь используем эти переменные во всех элементах:

body {
  background-color: var(--bg-primary);
  color: var(--text-primary);
  transition: background-color 0.3s, color 0.3s;
}

.card {
  background-color: var(--bg-secondary);
  border: 1px solid var(--border-color);
}

button {
  background-color: var(--accent-color);
  color: white;
}

JavaScript для переключения тем

Добавим интерактивности с помощью простого скрипта:

class ThemeSwitcher {
  constructor() {
    this.themeToggle = document.querySelector('#theme-toggle');
    this.currentTheme = localStorage.getItem('theme') || 'light';
    this.init();
  }

  init() {
    this.setTheme(this.currentTheme);
    
    if (this.themeToggle) {
      this.themeToggle.addEventListener('click', () => {
        this.toggleTheme();
      });
    }
    
    // Проверяем системные настройки
    this.checkSystemPreference();
  }

  setTheme(theme) {
    document.documentElement.setAttribute('data-theme', theme);
    localStorage.setItem('theme', theme);
    
    // Обновляем текст на кнопке если она есть
    if (this.themeToggle) {
      this.themeToggle.textContent = theme === 'dark' 
        ? 'Переключить на светлую тему' 
        : 'Переключить на тёмную тему';
    }
  }

  toggleTheme() {
    const newTheme = this.currentTheme === 'dark' ? 'light' : 'dark';
    this.currentTheme = newTheme;
    this.setTheme(newTheme);
  }

  checkSystemPreference() {
    // Если пользователь не делал выбор, используем системные настройки
    if (!localStorage.getItem('theme')) {
      const prefersDark = window.matchMedia('(prefers-color-scheme: dark)');
      
      if (prefersDark.matches) {
        this.setTheme('dark');
      }
      
      // Слушаем изменения системных настроек
      prefersDark.addEventListener('change', (e) => {
        if (!localStorage.getItem('theme')) {
          this.setTheme(e.matches ? 'dark' : 'light');
        }
      });
    }
  }
}

// Инициализируем когда DOM загружен
document.addEventListener('DOMContentLoaded', () => {
  new ThemeSwitcher();
});

Используйте localStorage для сохранения выбора пользователя между сессиями. Это улучшает пользовательский опыт — посетитель не должен каждый раз переключать тему.

Продвинутые техники

Плавные переходы

Добавьте плавности переключению:

* {
  transition: background-color 0.3s ease, 
              color 0.3s ease, 
              border-color 0.3s ease;
}

/* Для элементов, которые не должны анимироваться */
img, 
video, 
iframe {
  transition: none;
}

Адаптация изображений

Для разных тем можно использовать разные изображения:

.logo {
  background-image: url('logo-light.png');
}

[data-theme="dark"] .logo {
  background-image: url('logo-dark.png');
}

Использование CSS-фильтров

Для простой инверсии цветов изображений:

[data-theme="dark"] img {
  filter: brightness(0.8) contrast(1.2);
}

Доступность и UX

  • Минимальный контраст текста должен быть 4.5:1 для обычного текста и 3:1 для крупного
  • Предоставьте видимый переключатель в доступном месте
  • Не используйте только цвет для передачи информации
  • Тестируйте тему с реальными пользователями

Частые ошибки

  1. Игнорирование системных настроек пользователя
  2. Слишком резкие переходы между темами
  3. Недостаточный контраст в тёмной теме
  4. Забывают про сохранение выбора пользователя
  5. Не адаптируют изображения и иконки

FAQ: Часто задаваемые вопросы

Как определить, какую тему предпочитает пользователь?

Используйте медиа-запрос prefers-color-scheme и сохраняйте выбор пользователя в localStorage. Всегда уважайте явный выбор пользователя над системными настройками.

Нужно ли делать отдельную кнопку переключения?

Да, это лучшая практика. Пользователи ожидают контроль над интерфейсом. Разместите переключатель в навигации или настройках.

Как тестировать тёмную тему?

Тестируйте на разных устройствах, при разном освещении, с разными пользователями. Проверяйте контрастность с помощью инструментов вроде Lighthouse или axe DevTools.

Что делать со сторонними виджетами?

Многие популярные виджеты (чат-боты, формы) поддерживают тёмную тему через настройки. Если нет — используйте CSS-фильтры или свяжитесь с разработчиками.

Как быть с печатной версией?

Используйте медиа-запрос @media print для печатной версии, где обычно предпочтительна светлая тема независимо от выбора пользователя.