Sötét és világos mód implementálása egy Next.js weboldalon

Üdvözöljük a webfejlesztés világában, ahol a felhasználói élmény kulcsfontosságú! Napjainkban egyre nagyobb hangsúlyt kap a testreszabhatóság, és ezen belül az egyik legnépszerűbb funkció a sötét és világos mód (dark and light mode) váltogatásának lehetősége. Legyen szó programozókról, akik órákig bámulják a képernyőt, vagy egyszerű internetezőkről, akik az esti órákban böngésznek, a sötét mód jelentősen javíthatja az olvashatóságot és csökkentheti a szemfáradtságot. Ebben a cikkben részletesen bemutatjuk, hogyan implementálhatja ezt a modern és elengedhetetlen funkciót egy Next.js alapú weboldalon, lépésről lépésre, a legegyszerűbb beállításoktól a legösszetettebb kihívásokig.

Miért Elengedhetetlen a Sötét és Világos Mód Napjainkban?

A sötét mód több, mint egy egyszerű esztétikai választás; számos gyakorlati előnnyel jár mind a felhasználók, mind a weboldal tulajdonosai számára:

  • Felhasználói élmény (UX) javítása: A felhasználók maguk választhatják ki a számukra legkényelmesebb megjelenést. Ez növeli az elégedettséget és a weboldalon töltött időt.
  • Akadálymentesség: A magas kontrasztú sötét témák segíthetnek a látássérült, vagy bizonyos látásproblémákkal küzdő felhasználóknak. Emellett a fényérzékenységgel élők számára is kellemesebb környezetet biztosít.
  • Szemfáradtság csökkentése: Különösen rossz fényviszonyok között, vagy hosszú távú használat során a sötét háttér csökkenti a szemek terhelését.
  • Energiahatékonyság: OLED kijelzők esetén a sötét színek kevesebb energiát fogyasztanak, ami meghosszabbíthatja a készülék akkumulátorának élettartamát.
  • Modern megjelenés: Egy sötét móddal rendelkező weboldal gyakran modernnek és professzionálisnak tűnik, követi az aktuális trendeket.

Egy funkcionális és jól implementált sötét/világos mód tehát nem csak egy divatos kiegészítő, hanem egy alapvető eszköz a kiváló felhasználói élmény megteremtéséhez.

Az Alapok: Hogyan Működik a Témaváltás?

A téma (sötét vagy világos mód) váltása alapvetően két fő mechanizmusra épül:

  1. CSS változók (CSS Variables): Ez a modern és rugalmas megközelítés lehetővé teszi, hogy globálisan definiáljunk színeket, betűtípusokat és egyéb stílusjegyeket, majd ezeket felülírjuk egy adott téma (pl. sötét mód) esetén. Például létrehozhatunk egy --primary-color változót, amely világos módban egy sötét árnyalat, sötét módban pedig egy világosabb.
  2. JavaScript a téma állapotának kezelésére: Szükségünk van egy mechanizmusra, amely felismeri a felhasználó preferenciáját (akár a rendszerbeállításokat, akár egy korábbi választást), tárolja ezt az állapotot, és lehetővé teszi a váltást. Ezt az állapotot aztán a HTML vagy elemének egy data-theme attribútumán keresztül adhatjuk át a CSS-nek.

Amikor a felhasználó átvált sötét módba, a JavaScript frissíti a data-theme attribútumot (pl. <html data-theme="dark">), és a CSS változók automatikusan alkalmazzák az ehhez a témához tartozó stílusokat.

A Next.js Specifikumai: Kihívások és Megoldások

A Next.js egy erőteljes React keretrendszer, amely támogatja a szerveroldali renderelést (SSR) és a statikus oldalgenerálást (SSG). Ezek a funkciók nagyszerűek a teljesítmény és a SEO szempontjából, de egyben kihívást is jelentenek a témaimplementáció során. A legnagyobb probléma a FOUC (Flash of Unstyled Content), azaz a „stílusozatlan tartalom villanása”. Ez akkor fordul elő, ha a Next.js először a szerveroldalon rendereli az oldalt az alapértelmezett (gyakran világos) témával, majd a kliensoldalon, a JavaScript betöltődése után átvált a felhasználó preferált (sötét) témájára. Ez egy pillanatnyi, zavaró villanást eredményez, ami rontja a felhasználói élményt.

A FOUC elkerülésének kulcsa, hogy a téma preferenciáját a JavaScript mielőbb, még a React komponensek hidrálásának (client-side rendering) megkezdése előtt felismerje és alkalmazza. Ezt a Next.js _document.js fájljának segítségével fogjuk megoldani.

Lépésről Lépésre Implementáció Next.js-ben

1. CSS Beállítások: A Témák Alapja

Először is, definiálnunk kell a témafüggő CSS változókat. A globals.css (vagy egy dedikált témafájl) tökéletes erre a célra. A :root szektorban definiáljuk az alapértelmezett (világos) témát, majd a [data-theme="dark"] szelektorral felülírjuk a sötét módhoz tartozó változókat.

/* globals.css */

:root {
  --background-color: #ffffff;
  --text-color: #333333;
  --primary-color: #0070f3;
  --secondary-color: #eaeaea;
}

[data-theme="dark"] {
  --background-color: #1a1a1a;
  --text-color: #e0e0e0;
  --primary-color: #79afe0;
  --secondary-color: #333333;
}

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

h1, h2, h3, h4, h5, h6 {
  color: var(--text-color);
}

a {
  color: var(--primary-color);
}

Ezután minden komponensben és stílusban ezeket a CSS változókat használjuk a színek megadására. Így elegendő a változók értékét módosítani a téma váltásakor, és az egész oldal azonnal frissül.

2. Témaállapot Kezelése React Context API-val

A React Context API ideális választás a téma állapotának globális kezelésére a Next.js alkalmazásunkban. Létrehozunk egy ThemeContext-et és egy ThemeProvider komponenst.

Hozzon létre egy új fájlt, például context/ThemeContext.js:

// context/ThemeContext.js
import React, { createContext, useState, useEffect, useContext } from 'react';

const ThemeContext = createContext();

export const ThemeProvider = ({ children }) => {
  const [theme, setTheme] = useState('light'); // Alapértelmezett téma: világos

  useEffect(() => {
    // A komponens mountolásakor olvassuk ki a témát a localStorage-ból
    // Ha nincs, ellenőrizzük a rendszer preferenciáját
    const storedTheme = localStorage.getItem('theme');
    if (storedTheme) {
      setTheme(storedTheme);
    } else if (window.matchMedia('(prefers-color-scheme: dark)').matches) {
      setTheme('dark');
    } else {
      setTheme('light');
    }
  }, []); // Csak egyszer fusson le, mountoláskor

  useEffect(() => {
    // Amikor a 'theme' állapot változik, frissítsük a localStorage-t
    // és a HTML 'data-theme' attribútumát
    localStorage.setItem('theme', theme);
    document.documentElement.setAttribute('data-theme', theme);
  }, [theme]); // Akkor fusson le, ha a 'theme' változik

  const toggleTheme = () => {
    setTheme((prevTheme) => (prevTheme === 'light' ? 'dark' : 'light'));
  };

  return (
    <ThemeContext.Provider value={{ theme, toggleTheme }}>
      {children}
    </ThemeContext.Provider>
  );
};

export const useTheme = () => useContext(ThemeContext);

Ebben a kódban:

  • A ThemeContext a témaállapotot (theme) és a váltó függvényt (toggleTheme) biztosítja a gyerekelemek számára.
  • A ThemeProvider a useState hook-kal kezeli a téma állapotát.
  • Az első useEffect a komponens betöltődésekor megpróbálja kiolvasni a témát a localStorage-ból. Ha nem talál semmit, ellenőrzi a felhasználó rendszerbeállításait a window.matchMedia('(prefers-color-scheme: dark)') segítségével. Ez az ún. prefers-color-scheme media query, ami a felhasználó operációs rendszerének sötét/világos mód preferenciáját adja vissza.
  • A második useEffect biztosítja, hogy minden téma változáskor a localStorage frissüljön, és ami még fontosabb, a document.documentElement.setAttribute('data-theme', theme); sorral beállítsa a data-theme attribútumot a <html> elemen.

3. A Téma Alkalmazása és Váltó Gomb

Most, hogy van egy ThemeProvider-ünk, be kell csomagolnunk vele az egész alkalmazást. Ezt a pages/_app.js fájlban tehetjük meg.

// pages/_app.js
import '../styles/globals.css';
import { ThemeProvider } from '../context/ThemeContext';

function MyApp({ Component, pageProps }) {
  return (
    <ThemeProvider>
      <Component {...pageProps} />
    </ThemeProvider>
  );
}

export default MyApp;

Most már bármelyik komponensben használhatjuk a useTheme hook-ot a téma lekérdezésére és váltására. Hozzon létre egy ThemeToggle komponenst:

// components/ThemeToggle.js
import React from 'react';
import { useTheme } from '../context/ThemeContext';

const ThemeToggle = () => {
  const { theme, toggleTheme } = useTheme();

  return (
    <button onClick={toggleTheme} style={{
      padding: '10px 15px',
      border: 'none',
      borderRadius: '5px',
      cursor: 'pointer',
      backgroundColor: theme === 'light' ? '#333' : '#eee',
      color: theme === 'light' ? '#eee' : '#333',
      fontWeight: 'bold'
    }}>
      {theme === 'light' ? 'Váltás sötét módra' : 'Váltás világos módra'}
    </button>
  );
};

export default ThemeToggle;

Ezt a gombot bárhova beillesztheti az alkalmazásában, pl. a pages/index.js-be:

// pages/index.js
import Head from 'next/head';
import ThemeToggle from '../components/ThemeToggle';
import styles from '../styles/Home.module.css';

export default function Home() {
  return (
    <div className={styles.container}>
      <Head>
        <title>Sötét/Világos Mód Demo</title>
        <meta name="description" content="Sötét és világos mód implementálása Next.js-ben" />
        <link rel="icon" href="/favicon.ico" />
      </Head>

      <main className={styles.main}>
        <h1 className={styles.title}>
          Üdv a <a href="https://nextjs.org">Next.js!</a> oldalon
        </h1>

        <p className={styles.description}>
          Ez egy példa a sötét és világos mód implementálására.
        </p>

        <ThemeToggle />
      </main>
    </div>
  );
}

4. A FOUC Elkerülése: A Kritikus Lépés `_document.js`-sel

Ahogy korábban említettük, a fent bemutatott megoldás még mindig okozhat FOUC-ot, mert a useEffect hook csak a kliensoldali renderelés (hidrálás) után fut le. Ennek elkerülésére egy kis JavaScript kódot kell befecskendeznünk közvetlenül a <html> elembe a pages/_document.js fájl segítségével. Ez a script még azelőtt beállítja a data-theme attribútumot, mielőtt a React elkezdené az oldal renderelését.

Hozzon létre egy pages/_document.js fájlt (ha még nincs):

// pages/_document.js
import Document, { Html, Head, Main, NextScript } from 'next/document';

class MyDocument extends Document {
  render() {
    return (
      <Html>
        <Head />
        <body>
          <script dangerouslySetInnerHTML={{
            __html: `
              (function() {
                const setInitialTheme = () => {
                  const storedTheme = localStorage.getItem('theme');
                  const prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches;
                  const initialTheme = storedTheme || (prefersDark ? 'dark' : 'light');
                  document.documentElement.setAttribute('data-theme', initialTheme);
                };

                setInitialTheme();
              })();
            `,
          }} />
          <Main />
          <NextScript />
        </body>
      </Html>
    );
  }
}

export default MyDocument;

Ez a script azonnal lefut a böngészőben, amint a HTML dokumentum betöltődik, még a React keretrendszer inicializálása előtt. Elolvassa a localStorage-ból a mentett témát, vagy ha nincs, akkor a prefers-color-scheme alapján meghatározza az alapértelmezett témát, és beállítja az <html> elem data-theme attribútumát. Ennek köszönhetően az oldal már a megfelelő témával jelenik meg az első rendereléskor, teljesen kiküszöbölve a FOUC-ot.

Fontos megjegyezni, hogy a dangerouslySetInnerHTML használata biztonságos ebben az esetben, mivel mi magunk írjuk a befecskendezett kódot, és nincs külső, nem megbízható forrásból származó tartalom.

Haladó Tippek és További Megfontolások

  • Képek kezelése témától függően: Előfordulhat, hogy egyes képek jobban mutatnak világos, mások sötét háttér előtt. Ezt kezelhetjük CSS-sel a [data-theme="dark"] img { filter: invert(1); } segítségével, vagy dinamikusan betölthetünk különböző képfájlokat a téma alapján. Például: <img src={theme === 'dark' ? '/logo-dark.png' : '/logo-light.png'} alt="Logo" />.
  • Animációk és átmenetek: A témaváltást még simábbá tehetjük CSS átmenetekkel. Ahogy a példánkban is látható, a body elemen lévő transition tulajdonság segít.
    body {
      transition: background-color 0.3s ease, color 0.3s ease;
    }
  • Akadálymentesség és kontraszt: Mindig ügyeljünk arra, hogy a választott színek megfelelő kontrasztot biztosítsanak mindkét módban. Használjunk online kontrasztellenőrző eszközöket, hogy biztosítsuk a WCAG (Web Content Accessibility Guidelines) irányelvek betartását.
  • Külső könyvtárak: Léteznek népszerű könyvtárak is, mint például a next-themes, amelyek leegyszerűsíthetik a témaváltás implementálását. Ezek gyakran beépített megoldásokat kínálnak a FOUC kezelésére is. Bár a fenti útmutató a vanilla React és Next.js megközelítésre koncentrált, ezek a könyvtárak jó alternatívát jelenthetnek, ha gyorsabb, kevesebb kódolást igénylő megoldásra van szükségünk.
  • UI komponens könyvtárak: Ha olyan UI komponens könyvtárakat használunk, mint a Material-UI (MUI) vagy a Chakra UI, azok gyakran beépített témafunkciókkal rendelkeznek, amelyek integrálhatók a Next.js alkalmazásunkba. Ilyenkor a könyvtár saját téma providerét használjuk, és az integráció részletei eltérhetnek a fentiektől.

Tesztelés és Finomhangolás

Az implementáció befejezése után alaposan teszteljük az alkalmazást:

  • Böngészőkompatibilitás: Ellenőrizzük, hogy minden népszerű böngészőben (Chrome, Firefox, Safari, Edge) megfelelően működik-e a témaváltás.
  • FOUC ellenőrzés: Inkognitó módban, tiszta gyorsítótárral többször is töltsük be az oldalt. Váltogassuk a témát, majd zárjuk be a böngészőt, és nyissuk újra. Megjelenik-e a villanás? Ha nem, akkor sikeresen kezeltük a problémát.
  • Rendszerpreferencia: Teszteljük, hogy az oldal megfelelően reagál-e az operációs rendszer (Windows, macOS, Android, iOS) sötét/világos mód beállításaira.
  • Képek és ikonok: Győződjünk meg róla, hogy az összes kép és ikon jól látható és esztétikus mindkét témában.

Ne feledjük, a részletes tesztelés a felhasználói élmény kulcsa!

Összefoglalás és Következtetés

A sötét és világos mód implementálása egy Next.js weboldalon jelentősen hozzájárulhat a modern, felhasználóbarát és akadálymentes weboldalak létrehozásához. Bár a Next.js SSR/SSG jellegéből adódóan a FOUC elkerülése némi extra figyelmet igényel a _document.js használatával, a befektetett munka megtérül egy sima és konzisztens felhasználói élmény formájában.

A CSS változók, a React Context API és a stratégikusan elhelyezett JavaScript kód kombinálásával egy robusztus és karbantartható rendszert hozhatunk létre. Ne habozzon, építse be ezt a funkciót a következő projektjébe – felhasználói hálásak lesznek érte!

Leave a Reply

Az e-mail címet nem tesszük közzé. A kötelező mezőket * karakterrel jelöltük