Как сделать ночной режим в React-приложениях


11.06.2020 Время чтения - 13 минут 51

Как сделать ночной режим в React-приложениях

В современном мире ночной режим, который отображает светлые элементы интерфейса на тёмном фоне, быстро становится фаворитом у большинства пользователей. В этой статье вы узнаете, как эффективно реализовать ночной режим в React-приложении, используя библиотеку styled-components и некоторые функции React. Кроме того, мы обсудим плюсы и минусы ночного режима, а также причины, по которым он должен быть в каждом приложении.


Одной из наиболее часто запрашиваемых программных функций является ночной режим (или тёмный режим). Мы видим его почти во всех приложениях, которые привыкли использовать каждый день: начиная с мобильных и заканчивая веб-версиями. Ночной режим стал жизненно важным для компаний, которые хотят позаботиться о глазах своих пользователей.


Тёмный режим – это дополнительная функция, которая преобразует светлые элементы интерфейса в тёмные. Большинство крупных компаний (таких как YouTube, Twitter и Netflix) добавили ночной режим в свои приложения.


Несмотря на то, что мы не будем особо углубляться в сам React, базовые знания о нём, CSS и styled-components вам всё же понадобятся. Этот урок будет полезен для тех, кто хочет улучшить своё веб-приложение, ориентируясь на любителей ночного режима.

StackOverflow announces dark mode on Twitter
Анонс ночного режима в StackOverflow.

Тёмный режим снижает нагрузку на глаза, если вы долго работаете за компьютером или мобильным телефоном.


Что такое тёмный режим?

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


Почему вашему приложению нужен тёмный режим?

Ночной режим улучшает визуальную эргономику, уменьшая нагрузку на глаза, подстраивая экран под текущие условия освещения и обеспечивая удобство использования ночью или в темноте.

Прежде чем начать реализовывать тёмный режим в нашем приложении, давайте определимся с его преимуществами.


ЭКОНОМИЯ ЗАРЯДКИ

Тёмный режим в веб- и мобильных приложениях может продлить срок службы аккумулятора устройства. Google подтвердил, что дарк мод на OLED-экранах очень сильно продлил срок службы батареи.

Например, при яркости в 50% ночной режим в приложении YouTube экономит примерно на 15% больше энергии, чем обычный белый фон. При яркости экрана в 100% тёмный интерфейс экономит колоссальные 60% энергии.


ЭТО ПРОСТО КРАСИВО

Внешний вид тёмного режима является эстетичным, что значительно повышает привлекательность картинки.

Когда большинство продуктов имеют однотипную светлую палитру, ночной режим предлагает нечто другое – что-то таинственнее и новее.

К тому же он даёт прекрасные возможности для свежего взгляда на такой графический контент, как информационные панели, изображения и фотографии.

Сравнение тёмного и обычного режима на примере Twitter.

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


Что Такое Styled-Components?

В этой статье мы будем очень часто использовать библиотеку styled-components. Существует множество способов стилизации современного веб-приложения. Традиционный – метод стилей на уровне документов, при котором нужно или создать файл index.css и связать его с HTML или прописывать стили непосредственно внутри самого HTML-файла.

В стилях веб-приложений много чего изменилось с тех пор, как появился CSS-in-JS.

CSS-in-JS ссылается на паттерн, в котором CSS составлен с использованием JavaScript. Там используются теговые литералы для стилизации компонентов в файле JavaScript.

Styled-components – это библиотека CSS-in-JS, которая позволяет использовать все ваши любимые CSS-функции, включая медиа-запросы, псевдоселекторы и вложения.


Почему именно Styled-Components?

Библиотека Styled-components была выбрана по следующим причинам:

  • Нет неразберихи с именами классов. Вместо того, чтобы ломать голову над новым именем класса, styled-components генерирует уникальное имя само! Вам больше не придётся беспокоиться об ошибках или бессмысленных именах классов.
  • Использование пропсов. Styled-components позволяет расширять свойства стиля с помощью параметра props, обычно используемого в React, – таким образом, динамически влияя на ощущение компонента через состояние приложения.
  • Поддержка синтаксиса Sass. Написание Sass-синтаксиса из коробки без необходимости установки каких-либо препроцессоров или дополнительных инструментов возможно с помощью styled-components. В ваших определениях стилей вы можете использовать символ & для указания на текущий компонент.
  • Темы. Styled-components поддерживает темы, экспортируя компонент-оболочку ThemeProvider. Этот компонент присваивает тему всем компонентам React внутри себя через Context API. В дереве рендеринга все стилевые компоненты будут иметь доступ к предоставленной теме, даже если они имеют несколько уровней. В течении этого урока, мы будем углубляться в такого рода особенности styled-components.

Реализация Ночного Режима

В этой статье мы собираемся реализовать темный режим на простой YouTube-подобной веб-странице.

Чтобы продолжить, убедитесь, что вы клонировали исходный репозиторий.


СБОРКА

Давайте установим все зависимости в наш package.json файл. В терминале выполните следующую команду:

npm install

После успешной установки пропишите npm start. Вот как выглядит веб-страница без встроенного тёмного режима:

Чтобы установить styled-components, в терминале запустите:

npm install styled-components

РЕАЛИЗАЦИЯ

Для реализации ночного режима необходимо создать четыре разных компонента.

  • Theme – Содержит свойства цветов светлой и тёмной тем.
  • GlobalStyles – Содержит глобальные стили всего документа.
  • Toggler – Содержит элемент кнопки переключения режимов.
  • useDarkMode – Пользовательский хук, который обрабатывает логику смены темы и её сохранение в localStorag.

КОМПОНЕНТ ТЕМЫ

В папке src вы увидите компоненты в папке components. Создайте файл Themes.js и добавьте в него следующий код:

export const lightTheme = {
    body: '#FFF',
    text: '#363537',
    toggleBorder: '#FFF',
    background: '#363537',
}
export const darkTheme = {
    body: '#363537',
    text: '#FAFAFA',
    toggleBorder: '#6B8096',
    background: '#999',
}

Здесь мы определили и экспортировали объекты lightTheme и darkTheme с отличающимися переменными цветами. Не стесняйтесь экспериментировать и настраивать их значения в соответствии с вашими предпочтениями.

КОМПОНЕНТ ГЛОБАЛЬНЫХ СТИЛЕЙ

Не покидая папку components, создайте файл globalStyles.js и добавьте следующий код:

import { createGlobalStyle} from "styled-components"
export const GlobalStyles = createGlobalStyle`
  body {
    background: ${({ theme }) => theme.body};
    color: ${({ theme }) => theme.text};
    font-family: Tahoma, Helvetica, Arial, Roboto, sans-serif;
    transition: all 0.50s linear;
  }

Мы импортировали createGlobalStyle из styled-components. Метод createGlobalStyle заменяет устаревший на сегодня метод injectGlobal из третьей версии styled-components. Он генерирует React-компонент, который при добавлении в дерево компонентов внедряет глобальные стили в документ, в нашем случае – App.js.

К тому же мы определили компонент GlobalStyle и присвоили свойствам background и color значения из объекта темы. Таким образом, каждый раз, когда мы переключаемся между темами, значения меняются в зависимости от ночной темы или объектов светлой темы, которые мы передаем в ThemeProvider (будет создан позже).

Свойство перехода в 0.50s позволяет сменять тему так плавно, что при переключении тем можно легко заметить изменения.

НАПИСАНИЕ ФУНКЦИОНАЛА

Для реализации функции переключения тем нам нужно добавить всего несколько строк кода. В файле App.js добавьте следующий код (обратите внимание, что выделенный код – это то, что вы должны добавить):

import React, { useState, useEffect } from "react"; 
import {ThemeProvider} from "styled-components"; import { GlobalStyles } from "./components/Globalstyle"; import { lightTheme, darkTheme } from "./components/Themes"
import "./App.css"; import dummyData from "./data"; import CardList from "./components/CardList"; const App = () => { const [videos, setVideos] = useState([]);
const [theme, setTheme] = useState('light'); const themeToggler = () => { theme === 'light' ? setTheme('dark') : setTheme('light') }

useEffect(() => { const timer = setTimeout(() => { setVideos(dummyData); }, 1000); return () => clearTimeout(timer); }, []); return (

<ThemeProvider theme={theme === 'light' ? lightTheme : darkTheme}> <> <GlobalStyles/>

<div className="App">

<button onClick={themeToggler}>Switch Theme</button>

{ videos.map((list, index) => { return ( <section key={index}> <h2 className="section-title">{list.section}</h2> <CardList list={list} /> <hr /> </section> ); })} </div>

</> </ThemeProvider>
); }; export default App;

Выделенный код вы должны были добавить в App.js. Мы импортировали ThemeProvider из styled-components. ThemeProvider является вспомогательным компонентом в библиотеке styled-components, которая обеспечивает поддержку тем. Этот компонент внедряет тему во все нижеуказанные компоненты React через Context API.

В дереве рендеринга все стилевые компоненты будут иметь доступ к предоставленной теме, даже если они имеют несколько уровней.

Далее мы импортируем враппер GlobalStyle из ./components/Globalstyle. И, наконец, импортируем объекты lightTheme и darkTheme из ./components/Themes.

Для того, чтобы создать метод переключения, нам нужно состояние, которое содержит начальное значение цвета нашей темы. Итак, мы создаем состояние theme и устанавливаем начальное состояние light, используя хук useState.

Теперь о функциональности переключения:

Метод themeToggler использует троичный оператор для проверки состояния theme и переключает либо на тёмную, либо на светлую тему в зависимости от значения.

ThemeProvider — вспомогательный компонент styled-components, оборачивает всё в оператор return и внедряет любые компоненты под ним. Помните, что GlobalStyles внедряют глобальные стили в наши компоненты. Следовательно и вызываются они изнутри компонента-оболочки ThemeProvider.

Наконец, мы создали кнопку с событием onClick, которое назначает ей наш метод themeToggler.

Давайте посмотрим на наш текущий результат.

Ночная тема реализована не настойчиво.

Наш файл App.js нуждается в рефакторинге. Большая часть его кода не является DRY. (DRY означает «don’t repeat yourself», основной принцип разработки ПО, направленный на уменьшение количества повторений.) Похоже, что вся логика находится в App.js. Хорошая практика – помещать нашу логику отдельно, ради ясности. Итак, мы создадим компонент, который обрабатывает функционал переключения.

ПЕРЕКЛЮЧАТЕЛЬНЫЙ КОМПОНЕНТ

Всё ещё в папке components создайте файл Toggler.js и добавьте в него следующий код:

import React from 'react'
import { func, string } from 'prop-types';
import styled from "styled-components"
const Button = styled.button`
  background: ${({ theme }) => theme.background};
  border: 2px solid ${({ theme }) => theme.toggleBorder};
  color: ${({ theme }) => theme.text};
  border-radius: 30px;
  cursor: pointer;
  font-size:0.8rem;
  padding: 0.6rem;
  }
\`;
const Toggle = ({theme,  toggleTheme }) => {
    return (
        <Button onClick={toggleTheme} >
          Switch Theme
        </Button>
    );
};
Toggle.propTypes = {
    theme: string.isRequired,
    toggleTheme: func.isRequired,
}
export default Toggle;

Чтобы всё было аккуратно, мы стилизовали нашу кнопку-переключатель в компоненте Toggle, используя функцию styled из styled-components.

Сделано чисто для наглядности. Вы можете стилизовать кнопку так, как посчитаете нужным.

Внутри компонента Toggle мы передаем два пропса:

  • theme возвращает текущую тему (светлую или тёмную);
  • Функция toggleTheme будет использоваться для переключения между темами.

Теперь мы возвращаем компонент Button и назначаем функцию toggleTheme для события onClick.

И наконец, используем propTypes для определения наших типов, убедившись, что наш theme является string и isRequired, а toggleTheme является func и isRequired.

ИСПОЛЬЗОВАНИЕ ПОЛЬЗОВАТЕЛЬСКИХ ХУКОВ (useDarkMode)

При создании приложения масштабируемость имеет первостепенную роль, а это значит то, что наша бизнес-логика должна использоваться многократно в разных проектах.

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

Давайте создадим новый файл с именем useDarkMode.js в папке components и переместим нашу логику в файл с некоторыми изменениями. Добавьте в файл следующий код:

import { useEffect, useState } from 'react';
export const useDarkMode = () => {
    const [theme, setTheme] = useState('light');

    const setMode = mode => {
        window.localStorage.setItem('theme', mode)
        setTheme(mode)
    };

    const themeToggler = () => {
        theme === 'light' ? setMode('dark') : setMode('light')
    };

    useEffect(() => {
        const localTheme = window.localStorage.getItem('theme');
        localTheme && setTheme(localTheme)
    }, []);
    return [theme, themeToggler]
};

Здесь мы добавили несколько вещей:

  • setMode – мы используем localStorage для сохранения сессий в браузере. Иными словами, если пользователь выбрал тёмную тему, то он увидит её при следующем посещении или при обновлении страницы. Следовательно, эта функция устанавливает наше состояние и передает theme в localStorage.
  • themeToggler – эта функция использует троичный оператор для проверки состояния темы и переключает либо на тёмную, либо на светлую в зависимости от истинности условия.
  • useEffect – мы реализовали хук useEffect для проверки установки компонентов. Если пользователь ранее выбрал тему, мы передадим её нашей функции setTheme. В конце мы вернём theme, которая содержит выбранную тему и функцию themeToggler для переключения между режимами.

Думаю, вы согласитесь, что наш компонент с переходом на ночной режим выглядит гладко.

А теперь давайте перейдем к App.js для последних штрихов.

import React, { useState, useEffect } from "react";
import {ThemeProvider} from "styled-components"; 
import {useDarkMode} from "./components/useDarkMode";
import { GlobalStyles } from "./components/Globalstyle"; import { lightTheme, darkTheme } from "./components/Themes" import Toggle from "./components/Toggler" import "./App.css"; import dummyData from "./data"; import CardList from "./components/CardList"; const App = () => { const [videos, setVideos] = useState([]);
const [theme, themeToggler] = useDarkMode(); const themeMode = theme === 'light' ? lightTheme : darkTheme;
useEffect(() => { const timer = setTimeout(() => { setVideos(dummyData); }, 1000); return () => clearTimeout(timer); }, []); return (
<ThemeProvider theme={themeMode}>

<> <GlobalStyles/> <div className="App">

<Toggle theme={theme} toggleTheme={themeToggler} />

{ videos.map((list, index) => { return ( <section key={index}> <h2 className="section-title">{list.section}</h2> <CardList list={list} /> <hr /> </section> ); })} </div> </> </ThemeProvider> ); }; export default App;

Выделенный код нужно добавить в App.js.

Сначала мы импортируем наш пользовательский хук, деструктурируем пропсы theme и themeToggler и устанавливаем их с помощью функции useDarkMode.

Обратите внимание, что метод useDarkMode заменяет состояние темы, которое изначально было в App.js.

Мы объявляем переменную themeMode, которая отображает либо светлую, либо тёмную тему, в зависимости от состояния режима theme в данный момент.

Теперь нашему компоненту-оболочке ThemeProvider назначена только что созданная переменная themeMode для пропса theme.

И, наконец, вместо обычной кнопки передаём компонент Toggle.

Помните, что в нашем компоненте Toggle мы определили и стилизовали кнопку, а так же передали им theme и toggleTheme в качестве пропсов. Итак, всё, что нам нужно сделать, это передать эти пропсы соответствующим образом компоненту Toggle, который будет выполнять функцию нашей кнопки в App.js.

Ура! Наша ночная тема установлена и умеет не менять цвет, когда страница обновляется или открывается в новой вкладке.

Давайте посмотрим на текущий результат в действии:

Почти всё работает хорошо, но есть одна маленькая вещь, сделав которую, переключение тем станет великолепным. Переключитесь на ночную тему и обновите страницу. Видите, что синий цвет в кнопке загружается чуть раньше, чем серый? Это происходит потому, что наш хук useState изначально запускает тему light. После этого запускается useEffect, проверяет localStorage и только потом устанавливает тёмную тему. Давайте перейдем к нашему пользовательскому хуку useDarkMode.js и добавим немного кода:

import { useEffect, useState } from 'react';
export const useDarkMode = () => {
    const [theme, setTheme] = useState('light');     
const [mountedComponent, setMountedComponent] = useState(false)

const setMode = mode => { window.localStorage.setItem('theme', mode) setTheme(mode) }; const themeToggler = () => { theme === 'light' ? setMode('dark') : setMode('light') }; useEffect(() => { const localTheme = window.localStorage.getItem('theme'); localTheme ? setTheme(localTheme) : setMode('light')

setMountedComponent(true)

}, []); return [theme, themeToggler,
mountedComponent]
};

Выделенный код – всё, что нужно было добавить в useDarkMode.js. Мы создали другое состояние с именем mountComponent и установили значение по умолчанию в false, используя хук useState. Далее в хуке useEffect мы устанавливаем состояние mountComponent в значение true, используя setMountingComponent. Наконец, в возвращаемый массив добавляем состояние mountComponent.

А теперь давайте добавим немного кода в App.js, чтобы всё это дело заработало.

import React, { useState, useEffect } from "react";
import {ThemeProvider} from "styled-components";
import  {useDarkMode} from "./components/useDarkMode"
import { GlobalStyles } from "./components/Globalstyle";
import { lightTheme, darkTheme } from "./components/Themes"
import Toggle from "./components/Toggler"
import "./App.css";
import dummyData from "./data";
import CardList from "./components/CardList";
const App = () => {
  const [videos, setVideos] = useState([]);   
const [theme, themeToggler, mountedComponent] = useDarkMode();

const themeMode = theme === 'light' ? lightTheme : darkTheme; useEffect(() => { const timer = setTimeout(() => { setVideos(dummyData); }, 1000); return () => clearTimeout(timer); }, []);

if(!mountedComponent) return <div/>

return ( <ThemeProvider theme={themeMode}> <> <GlobalStyles/> <div className="App"> <Toggle theme={theme} toggleTheme={themeToggler} /> { videos.map((list, index) => { return ( <section key={index}> <h2 className="section-title">{list.section}</h2> <CardList list={list} /> <hr /> </section> ); })} </div> </> </ThemeProvider> ); }; export default App;

Мы добавили наше состояние mountComponent в качестве опоры для хука useDarkModeи проверили, установлен ли компонент, что и происходит в хуке useEffect. Если этого не произошло, то мы сделаем div пустым.

Давайте посмотрим на нашу тёмную веб-страницу.

Теперь, когда страница обновляется, цвет кнопки остаётся неизменным.


Заключение

Ночная тема всё чаще становится фаворитом среди пользователей, а реализовать её в веб-приложении React становится значительно проще, если использовать тематическую оболочку ThemeProvider в styled-components. Двигайтесь вперёд и экспериментируйте со стилевыми компонентами при реализации тёмной темы. Кстати, вместо кнопки можно сделать значки.


Оригинал статьи.

Читайте так же: Как создать привлекательный лендинг