Astrojs 및 tailwindCSS로 테마 토글 버튼 빌드

아스트로가 우리에게 가져다준 것은 기본적으로 자바스크립트가 없는 세상입니다. 그들은 이 철학을 사용하여 번들 크기를 줄이고 브라우저의 첫 번째 페인트 속도를 높이지만 블로그에서 어두운 테마 버튼을 토글하는 것과 같이 개발자에게 많은 새로운 과제를 안겨줍니다.

Next.js 사용



Next.js 위에 구축된 내 블로그에서 어두운 모드를 전환하는 ThemeContext를 작성했습니다. ThemeContext는 앱을 마운트할 때 루트 HTML에 class="dark"를 추가합니다. 이런 식으로 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 - Partial Hydration 2 아이디어를 기반으로 전체ContextWrapperedComponentclient:load 또는 client:only로 레이블을 지정하여 수화시키고 HTML에 Javascript로 주입해야 합니다. 그러나 이런 식으로 주입되어 우리가 원하는 것이 아닌 전체 번들 크기를 늘리는 불필요한 스크립트가 많이 있습니다.

그래서 어두운 모드 상태의 저장소를 Context에서 localStorage로 전환하기로 결정했습니다.
  • 처음 마운트할 때 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에서 특수 속성 is:inline 3 을 사용하여 있는 그대로 HTML 및 CSS로 클라이언트에 스크립트를 보낼 수 있습니다. 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에 가깝습니다. 깜박임 문제가 있습니다. 처음에는 위의 솔루션으로 했던 것처럼 아이콘을 업데이트할 수 있다고 생각했지만 나중에 이것이 작동하지 않는다는 것을 발견했고 그 이유는 매우 간단합니다.

    상호 작용하려면 토글 버튼이 필요하기 때문에 Astro.js가 이 스크립트를 수화함을 나타내기 위해 접두사client:load를 붙여야 합니다. 그러나 첫 번째 페인팅 시 하이드레이트된 스크립트가 아직 주입되지 않았으므로 is:inline 스크립트가 아이콘을 업데이트할 대상 버튼을 찾을 수 없습니다. 버튼은 첫 번째 페인팅이 완료된 후에만 나타납니다.

    이 문제를 해결하려면 토글 버튼의 ​​디자인을 변경해야 합니다. 이제 태양과 달 아이콘이 동시에 표시되지만 어느 것이 현재 테마인지 나타내기 위해 희미하게 전환됩니다.

    결론



    Astro.js를 사용한 코딩은 새로운 여정이며 발견할 가능성이 많습니다. 탐색 중에 발견한 내용을 계속 업데이트할 것입니다.



    TailwindCSS - dark mode

    Astro - Partial Hydration

    Astro - is:inline

    withAstro - docs - GItHub

    좋은 웹페이지 즐겨찾기