Hogyan készíts egy sötét és világos módot támogató Angular appot?

Üdvözöljük egy izgalmas utazásban a modern webfejlesztés egyik legnépszerűbb funkciójának megvalósítására: a sötét és világos mód támogatására egy Angular alkalmazásban. Napjainkban egyre inkább elvárás, hogy a felhasználók személyre szabhassák digitális élményeiket, és ennek egyik alappillére a téma kiválasztásának lehetősége. Legyen szó éjszakai böngészésről vagy épp nappali fényviszonyokról, a megfelelő téma nemcsak kényelmet biztosít, hanem javítja a felhasználói élményt és a hozzáférhetőséget is.

Ebben az átfogó cikkben lépésről lépésre végigvezetjük Önt azon, hogyan integrálhatja ezt a funkciót az Angular projektjébe. A kezdeti beállítástól a fejlettebb technikákig mindenre kitérünk, hogy appja ne csak funkcionális, hanem esztétikailag is vonzó legyen minden felhasználó számára.

Miért fontos a sötét és világos mód?

Mielőtt belemerülnénk a technikai részletekbe, érdemes megérteni, miért is érdemes időt és energiát fektetni a témaváltás implementálásába:

  • Felhasználói preferencia és kényelem: Nem mindenki szereti ugyanazt a színsémát. A sötét mód különösen népszerű az esti órákban, mivel csökkenti a szem terhelését és a kék fény kibocsátását. A világos mód viszont kiválóan olvasható erős fényviszonyok között.
  • Hozzáférhetőség: A megfelelő kontraszt és a testre szabható téma javítja az alkalmazás hozzáférhetőségét a különböző látáskárosult felhasználók számára.
  • Energiatakarékosság: OLED képernyőkön a sötét mód jelentősen csökkentheti az energiafogyasztást, ami hosszabb akkumulátor-üzemidőt eredményez mobil eszközökön.
  • Esztétika és márkaépítés: Egy jól megtervezett sötét és világos téma professzionális megjelenést kölcsönöz az alkalmazásnak, és erősíti a márka vizuális identitását.
  • Modern elvárások: A legtöbb modern alkalmazás és operációs rendszer ma már támogatja a témaváltást, így a felhasználók elvárják ezt a funkciót az újonnan fejlesztett appoktól is.

Az alapok: Hogyan működik a témaváltás?

Az Angular alkalmazásokban a sötét és világos mód implementálásának szíve a CSS változók (CSS Custom Properties) és a megfelelő témaklassz felváltása a body elemen. A lényeg, hogy egy globális CSS fájlban definiálunk alapértelmezett változókat (világos módra), majd egy másik osztályban (pl. .theme-dark) felülírjuk ezeket a változókat a sötét módhoz tartozó értékekkel. Ezt követően egy Angular szolgáltatás (theme service) segítségével kezeljük, hogy melyik osztály legyen aktív a body elemen, és eltároljuk a felhasználó választását a localStorage-ban.

Lépésről lépésre útmutató

1. Angular projekt létrehozása

Ha még nincs Angular projektje, hozzon létre egy újat a következő paranccsal:

ng new my-themed-app --style=scss
cd my-themed-app

A --style=scss opciót javasoljuk, mivel az SCSS sokkal rugalmasabb és hatékonyabb a CSS változók kezelésében.

2. Globális CSS változók definiálása

Nyissa meg a src/styles.scss fájlt, és definiálja a globális CSS változókat. Ezek lesznek az alkalmazás színei és egyéb stílusparaméterei. Először az alapértelmezett (világos) téma változóit definiáljuk a :root selectorban, majd a sötét téma változóit egy .theme-dark osztályban.

// src/styles.scss

:root {
  // Világos mód alapértelmezett színei
  --primary-bg: #f0f2f5;           // Háttérszín
  --secondary-bg: #ffffff;         // Másodlagos háttér (pl. kártyák)
  --text-color: #333333;           // Elsődleges szövegszín
  --secondary-text-color: #555555; // Másodlagos szövegszín
  --accent-color: #007bff;         // Kiemelő szín (pl. gombok)
  --border-color: #e0e0e0;         // Szegélyszín
  --shadow-color: rgba(0, 0, 0, 0.1); // Árnyék
}

.theme-dark {
  // Sötét mód színei
  --primary-bg: #1a1a1a;
  --secondary-bg: #2b2b2b;
  --text-color: #e0e0e0;
  --secondary-text-color: #b0b0b0;
  --accent-color: #64b5f6;
  --border-color: #3a3a3a;
  --shadow-color: rgba(0, 0, 0, 0.3);
}

body {
  margin: 0;
  font-family: Arial, sans-serif;
  background-color: var(--primary-bg);
  color: var(--text-color);
  transition: background-color 0.3s ease, color 0.3s ease; // Finom átmenet a témaváltáskor
}

// Példa egy komponens stílusára
.card {
  background-color: var(--secondary-bg);
  color: var(--text-color);
  border: 1px solid var(--border-color);
  box-shadow: 0 2px 5px var(--shadow-color);
  padding: 20px;
  margin: 20px;
  border-radius: 8px;
  transition: background-color 0.3s ease, color 0.3s ease, border-color 0.3s ease, box-shadow 0.3s ease;
}

button {
  background-color: var(--accent-color);
  color: var(--secondary-bg); // A gomb szövege mindig kontrasztos legyen
  border: none;
  padding: 10px 15px;
  border-radius: 5px;
  cursor: pointer;
  transition: background-color 0.3s ease;
}

button:hover {
  filter: brightness(1.1); // Kicsit világosabb a hover-nél
}

A kulcs itt az, hogy mindenhol a var(--változónév) szintaxist használjuk a stílusok definiálásához. Amikor a body elemen váltunk a .theme-dark osztályra, az összes --változónév automatikusan felülíródik a sötét mód értékeivel.

3. Téma szolgáltatás (Theme Service) létrehozása

Ez a szolgáltatás felel a téma állapotának kezeléséért, a localStorage-ban való tárolásáért és a body elemen lévő osztály beállításáért. Generáljunk egy szolgáltatást:

ng generate service services/theme

Ezután módosítsuk a src/app/services/theme.service.ts fájlt:

// src/app/services/theme.service.ts
import { Injectable, Renderer2, RendererFactory2 } from '@angular/core';
import { BehaviorSubject, Observable } from 'rxjs';

type Theme = 'light' | 'dark';

@Injectable({
  providedIn: 'root'
})
export class ThemeService {
  private _currentTheme: BehaviorSubject<Theme> = new BehaviorSubject<Theme>('light');
  public readonly currentTheme$: Observable<Theme> = this._currentTheme.asObservable();
  private renderer: Renderer2;
  private readonly THEME_KEY = 'user-theme';

  constructor(rendererFactory: RendererFactory2) {
    this.renderer = rendererFactory.createRenderer(null, null);
    this.initTheme();
  }

  private initTheme(): void {
    const savedTheme = localStorage.getItem(this.THEME_KEY) as Theme;
    if (savedTheme) {
      this.setTheme(savedTheme, false); // Ne mentse el újra, ha már el van mentve
    } else {
      // Alapértelmezett téma beállítása a rendszerbeállítások alapján
      const prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches;
      this.setTheme(prefersDark ? 'dark' : 'light', true);
    }
  }

  setTheme(theme: Theme, save: boolean = true): void {
    if (this._currentTheme.value === theme) {
      return; // Nincs változás
    }

    // Távolítsa el az előző téma osztályt
    if (this._currentTheme.value) {
      this.renderer.removeClass(document.body, `theme-${this._currentTheme.value}`);
    }

    // Adja hozzá az új téma osztályt
    this.renderer.addClass(document.body, `theme-${theme}`);
    this._currentTheme.next(theme);

    if (save) {
      localStorage.setItem(this.THEME_KEY, theme);
    }
  }

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

  getCurrentTheme(): Theme {
    return this._currentTheme.value;
  }
}

Magyarázat:

  • _currentTheme és currentTheme$: A BehaviorSubject segítségével reaktívan tudjuk figyelni a téma változásait bármely komponensből.
  • Renderer2: Ezt használjuk a body elemen lévő osztályok dinamikus hozzáadására és eltávolítására, ami biztonságosabb és Angular-kompatibilisebb, mint a közvetlen DOM manipuláció.
  • THEME_KEY: A localStorage kulcsa, amivel a felhasználó téma preferenciáját tároljuk.
  • initTheme(): A szolgáltatás inicializálásakor ellenőrzi a localStorage-t. Ha ott talál témát, azt állítja be. Ha nem, akkor a böngésző vagy operációs rendszer beállításait veszi figyelembe (prefers-color-scheme).
  • setTheme(theme: Theme, save: boolean): Ez a fő metódus. Eltávolítja az aktuális téma osztályt a body-ról, hozzáadja az újat, frissíti a BehaviorSubject értékét, és ha a save paraméter true, akkor elmenti az új témát a localStorage-ba.
  • toggleTheme(): Egy kényelmes metódus a téma váltására világos és sötét között.

4. Téma szolgáltatás integrálása az AppComponentbe

Az AppComponent lesz az alkalmazás gyökérkomponense, amely felelős a téma szolgáltatás inicializálásáért és egy téma váltó UI elem megjelenítéséért.

// src/app/app.component.ts
import { Component, OnInit } from '@angular/core';
import { ThemeService } from './services/theme.service';
import { Observable } from 'rxjs';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.scss']
})
export class AppComponent implements OnInit {
  title = 'my-themed-app';
  currentTheme$: Observable<'light' | 'dark'>;

  constructor(private themeService: ThemeService) {
    this.currentTheme$ = this.themeService.currentTheme$;
  }

  ngOnInit(): void {
    // A téma szolgáltatás már inicializálja a témát a konstruktorában,
    // de itt feliratkozhatunk, ha bármilyen további komponens logikát szeretnénk
    // a téma változására.
    // this.themeService.currentTheme$.subscribe(theme => {
    //   console.log('Current theme:', theme);
    // });
  }

  toggleTheme(): void {
    this.themeService.toggleTheme();
  }
}

5. Téma váltó UI implementálása

Adjunk hozzá egy egyszerű gombot vagy kapcsolót az app.component.html fájlba, amellyel a felhasználó válthat a témák között. Használjuk az async pipe-ot a currentTheme$ megfigyelésére.

<!-- src/app/app.component.html -->
<div class="container">
  <header>
    <h1>Angular App Sötét/Világos Móddal</h1>
    <button (click)="toggleTheme()">
      Téma váltása: {{ (currentTheme$ | async) === 'light' ? 'Világos' : 'Sötét' }} mód
    </button>
  </header>

  <main>
    <p>Ez egy példa szöveg az Angular alkalmazásban.</p>
    <div class="card">
      <h2>Kártya Cím</h2>
      <p>Ez egy kártya tartalom, amely alkalmazza a globális CSS változókat.</p>
      <button>Művelet</button>
    </div>
    <p>Könnyedén válthat a témák között a gomb segítségével.</p>
  </main>
</div>

Az app.component.scss fájlban hozzáadhatunk egy kis stílust a header és container elemekhez, amelyek szintén használják a CSS változókat:

// src/app/app.component.scss
.container {
  max-width: 960px;
  margin: 0 auto;
  padding: 20px;
}

header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  margin-bottom: 40px;
  padding: 20px;
  background-color: var(--secondary-bg);
  border-bottom: 1px solid var(--border-color);
  box-shadow: 0 2px 5px var(--shadow-color);
  border-radius: 8px;
}

h1 {
  color: var(--text-color);
  margin: 0;
}

main {
  padding: 20px 0;
}

p {
  line-height: 1.6;
  margin-bottom: 15px;
}

6. Kiegészítő tippek és legjobb gyakorlatok

A. Rendszerbeállítások figyelembe vétele (prefers-color-scheme)

Mint láthattuk a ThemeService-ben, az initTheme() metódus már kezeli a window.matchMedia('(prefers-color-scheme: dark)') lekérdezést. Ez a funkció lehetővé teszi, hogy az alkalmazás automatikusan felismerje a felhasználó operációs rendszerének vagy böngészőjének preferált téma beállítását, és ennek megfelelően állítsa be az alapértelmezett témát. Ez rendkívül javítja a felhasználói élményt, mivel az app már a kezdetektől a felhasználó kedvenc témájában jelenik meg.

B. Átmenetek és animációk

A transition CSS tulajdonság használata a body elemen és az egyes komponensekben, mint ahogy a példában látható, simábbá teszi a témaváltást. Ez egy apró, de annál fontosabb részlet, ami jelentősen hozzájárul a prémium felhasználói élményhez.

C. Komponens-specifikus stílusok kezelése

Ha egy komponensnek speciális stílusokra van szüksége, amelyeket nem lehet globális CSS változókkal kezelni, akkor a komponens stílusfájljában (pl. my-component.component.scss) is felülírhatja a változókat, vagy definiálhat új, lokális változókat. Például:

// src/app/my-component/my-component.component.scss
:host { // Vagy egy wrapper class
  .my-specific-element {
    background-color: var(--special-bg, #f0f0f0); // Fallback értékkel
    // Vagy felülírhat egy globális változót is
    // --text-color: purple;
  }
}

D. SSR (Server-Side Rendering) és FOUC elkerülése

Ha az alkalmazása SSR-t (Server-Side Rendering) használ, akkor a „flash of unstyled content” (FOUC) jelenség elkerülése érdekében fontos, hogy a szerver már a megfelelő témaklasszal renderelje a body taget. Ezt a Node.js szerveroldali kódban vagy az Angular Universal beállításainál kell kezelni, általában a felhasználói preferenciát egy sütiben tárolva, és azt figyelembe véve a kezdeti rendereléskor.

E. Hozzáférhetőség (Accessibility)

Mindig győződjön meg róla, hogy a választott színpaletták megfelelő kontrasztaránnyal rendelkeznek mindkét témában (különösen a szövegek és a háttér között). Használjon online kontrasztellenőrző eszközöket, hogy megfeleljen az WCAG (Web Content Accessibility Guidelines) irányelveinek.

F. További téma opciók (pl. kék, zöld)

A fenti architektúra könnyen kiterjeszthető több témára is. Egyszerűen adjon hozzá új témaklasszokat (pl. .theme-blue) a styles.scss fájlba, és frissítse a ThemeService-t, hogy támogassa az új témaneveket és az ezek közötti váltást.

Összefoglalás

A sötét és világos mód támogatása nem csupán egy divatos funkció, hanem egy alapvető követelmény a modern webes alkalmazásokban, amely jelentősen javítja a felhasználói élményt és a hozzáférhetőséget. Az Angular, a CSS változók és egy jól strukturált téma szolgáltatás kombinációjával rendkívül elegánsan és hatékonyan valósíthatjuk meg ezt a funkciót.

Ebben a cikkben végigvettük a témaváltás alapjait, a CSS változók definiálásától kezdve, a téma szolgáltatás implementációján át, egészen a felhasználói felület integrálásáig. Kitértünk olyan fontos szempontokra is, mint a rendszerbeállítások figyelembe vétele, az átmenetek, a hozzáférhetőség, és az SSR considerations. Reméljük, ez az útmutató segít Önnek abban, hogy Angular alkalmazásait még felhasználóbarátabbá és vizuálisan vonzóbbá tegye.

Ne habozzon kísérletezni, és fedezze fel a lehetőségeket, amelyeket a témaváltás kínál! A felhasználók hálásak lesznek az extra figyelemért és a személyre szabható élményért.

Leave a Reply

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