Создание кнопки переключения темы с помощью Astrojs и tailwindCSS

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

С помощью Next.js

Я написал ThemeContext для переключения темного режима в моем блоге, построенный поверх Next.js. ThemeContext добавит class="dark" в корневой HTML при установке приложения. Таким образом, TailwindCSS может распознать текущую тему.1

// The code I am using in my blog built with Next.js
import { useEffect, useState, createContext } from "react";

const defaultState = {
  theme: "light",
  toggleDark: () => {},
![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/q0bxxcx8cz7eea9bmoem.jpg)
};

export const ThemeContext = createContext(defaultState);

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

  const rawSetTheme = (rawTheme) => {
    const root = window.document.documentElement;
    const isDark = rawTheme === "dark";
    root.classList.remove(isDark ? "light" : "dark");
    root.classList.add(rawTheme);
  };

  if (initialTheme) {
    rawSetTheme(initialTheme);
  }

  React.useEffect(() => {
    rawSetTheme(theme);
  }, [theme]);

  return (
    <ThemeContext.Provider value={[ theme, setTheme ]}>
      {children}
    </ThemeContext.Provider>
  );
};
Вход в полноэкранный режим Выход из полноэкранного режима

С помощью Astro.js

Но если мы хотим сохранить использование Context API, нам может понадобиться написать что-то вроде следующего.

// mainPage.astro
---
import ContextWrapperedComponent from "./ContextWrapperedComponent"
---

<ContextWrapperedComponent client:load />


// ContextWrapperedComponent

export const ContextWrapperedComponent = () => {
  // logic for context and components

  return (
    <div>
       // bunch of components that rely on context
    </div>
  )
}
Войти в полноэкранный режим Выйти из полноэкранного режима

Основано на идее Astro.js — частичная гидратация2нам нужно пометить весь ContextWrapperedComponent как client:load или client:only, чтобы сделать его гидратированным и внедрить его в HTML как Javascript. Но таким образом в HTML внедряется множество ненужных скриптов, что увеличивает общий размер пакета, а это не то, чего мы хотим.

Поэтому я решил переключить хранение состояния темного режима с Context на localStorage.

  • При первой установке я буду обращаться к значению key’s(blog-theme) в localStorage и определять тему страницы в соответствии с этим значением.
  • Как только пользователь переключит тему, я сброшу тему страницы и обновлю значение в localStorage.

На первый взгляд, эта реализация выглядит простой, но у нее есть много предостережений.


Предостережение 1 — мигающие страницы

Процесс рендеринга в Astro сначала отправляет HTML и CSS на клиент, монтирует гидратированную строку шаблона и прослушивает событие для внедрения скрипта. Такое поведение может привести к некоторым нежелательным результатам.

// This code will update the page's theme upon the finsish 
// of the first time painting, but that is not what we want.

useEffect(() => {
  let theme: "light" | "dark";

  if (typeof localStorage !== "undefined" && localStorage.getItem("theme")) {
    theme = localStorage.getItem("theme") as "light" | "dark";
  } else if (window.matchMedia("(prefers-color-scheme: dark)").matches) {
    theme = "dark";
  } else {
    theme = "light";
  }

  if (theme === "light") {
    setTheme("light");
  } else {
    setTheme("dark");
  }
}, []);
Вход в полноэкранный режим Выход из полноэкранного режима

Например, если ваша тема по умолчанию имеет темный режим, но то, что хранится в localStorage, имеет светлый режим. Сначала HTML будет отображать темный режим, как только клиентская сторона будет смонтирована, событие вызовет инъекцию гидратированного скрипта, ваша страница будет обновлена как светлый режим, и этот процесс будет продолжаться каждый раз, когда пользователь будет менять страницы. В результате ваши страницы будут постоянно мигать.

Чтобы решить проблему мигания, нам нужно обновить тему до первой покраски браузера.

К счастью, Astro.js уже позаботились о нашей спине, в их API вы можете сделать скрипт, отправляемый клиенту с HTML и CSS as-is со специальным атрибутом is:inline.3. Astro не будет гидратировать этот скрипт и заниматься какой-либо оптимизацией. Серьезно говоря, это не приветствуется философией Astro, но это необходимо для нашего приложения. Кроме того, официальный документ Astro.js также придерживается этого подхода.4

// With this code, we can update the page's theme before the first-time painting.
// You should put the code into <script is:inline>

const html = document.querySelector("html");
const theme = (() => {
  if (
    typeof localStorage !== "undefined" &&
    localStorage.getItem("theme")
  ) {
    return localStorage.getItem("theme");
  }
  if (window.matchMedia("(prefers-color-scheme: dark)").matches) {
    return "dark";
  }
  return "light";
})();

if (theme === "light") {
  html.classList.remove("dark");
} else {
  html.classList.add("dark");
}
Вход в полноэкранный режим Выход из полноэкранного режима

Оговорка 2 — Переход кнопки переключения темного режима

Изначально моя кнопка переключения темного режима в темном режиме отображает иконку солнца, а в светлом режиме — иконку луны. Проблема этой конструкции близка к оговорке 1, она будет иметь проблему мигания. Сначала я подумал, что могу обновить иконку так же, как это было сделано в приведенном выше решении, но позже я обнаружил, что это не работает, и причина довольно проста.

Поскольку нам нужно, чтобы кнопка переключения была интерактивной, она должна иметь префикс client:load, чтобы указать Astro.js гидрат этого скрипта. Но при первом рисовании гидратированный скрипт еще не внедрен, поэтому скрипт is:inline не может найти целевую кнопку для обновления иконки. Кнопка появится только после завершения первого рисования.

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

Заключение

Кодирование с помощью Astro.js — это новое путешествие, у него много потенциала, который еще предстоит раскрыть, я буду продолжать обновлять то, что я нашел во время исследования.


  1. TailwindCSS — темный режим

  2. Astro — частичная гидратация

  3. Astro — is:inline

  4. withAstro — документация — GItHub

Оцените статью
devanswers.ru
Добавить комментарий