رِزومه

در این مقاله فنی عمیق، ما بررسی می‌کنیم چگونه می‌توانیم یک انتخاب‌کننده تم (light/dark) را بدون افکت چشمک‌زن نامطلوب که در زمان بارگذاری صفحه رخ می‌دهد، پیاده‌سازی کنیم. ما به‌طور جزئی چرخهٔ رندر مرورگر را تحلیل می‌کنیم، عوامل عمیق FOUC (Flash of Unstyled Content) را بررسی می‌کنیم و یک راه‌حل قوی مبتنی بر پیش‌بارگذاری همزمان در`<head>`. این تکنیک تجربه کاربر را روان و حرفه‌ای تضمین می‌کند.

مشکل: این لعنتی پاپالیSh tem

یک کابوس برای تجربه کاربری

سیناريو را تصور کنید: شما ساعت‌ها را صرف کردید تا یک تم تاریک زیبا برای برنامه وب خود طراحی کنید. رنگ‌ها به完美 manera متوازن‌اند، تضاد بهینه است و کاربران شما این گزینه را دوست دارند. اما مشکلی خجالت‌انگیز وجود دارد: در هر بار بارگذاری صفحه، برای یک قسمت از ثانیه، تم روشن پیش‌فرض نمایش داده می‌شود قبل از اینکه تم تاریک جایگزین شود.

این "flash" بصری، اگرچه کوتاه باشد (گاهی کمتر از ۱۰۰ میلی‌ثانیه)، بلافاصله توسط چشم انسان قابل‌درک است و تجربه‌ای ناخوشایند به‌وجود می‌آورد. برای کاربران که به‌دلیل راحتی بصری یا قابلیت دسترسی تم تاریک را انتخاب کرده‌اند، این چشمک‌زدن می‌تواند حتی دردناک باشد، به‌ویژه در محیطی با نور ضعیف.

FOUC: یک مشکل وب قدیمی

این پدیده یک گونه از چیزی است که آن را "Flash of Unstyled Content" (FOUC) می‌نامند، یک مشکل کلاسیک در توسعه وب که از روزهای اولیه CSS برمی‌گردد. FOUC هنگامی رخ می‌دهد که مرورگر موقتاً محتوای HTML را بدون اعمال استایل‌های CSS نمایش می‌دهد، که منجر به نمایش موقت محتوای بدون استایل می‌شود.

در مورد مورد خاص ما، ما دربارهٔ محتوایی که به‌طور کامل بدون استایل نیست، صحبت نمی‌کنیم، بلکه دربارهٔ یکفلاش teme اشتباه(FOWT) - محتوا استایل داده شده است، اما با تم نادرست. این به ویژه ناراحت کننده است زیرا نشان می‌دهد که برنامه ما "فراموش می‌کند" ترجیح کاربر را در هر بار بارگذاری صفحه.

تأثیر بر ادراک کیفیت

این مشکل، اگرچه تکنیکی است، پیامدهای مهمی بر ادراک کیفیت برنامه شما دارد:

کاهش لکلمعان این احساس را به وجود می‌آورد که برنامه ناقص یا بهینه‌سازی نشده باشد. کاربران عموماً این نقص‌های بصری کوچک را ناتوانی کلی در حرفه‌ای بودن مرتبط می‌دانند.

شکستگی همبستگیبرنامه به‌نظر می‌رسد ترجیحات کاربر را «فراموش» می‌کند و احساس ناهم‌خوانی بین رابط و انتظارات ایجاد می‌کند.

خستگی بصریبرای کاربران حساس به نور یا مبتلا به میگرن، این فلش نورانی می‌تواند بیش از یک ناراحتی ظاهری ساده باشد.

عملکرد ادراکیبه‌طرز عجیب، حتی اگر وب‌سایت شما به سرعت بارگذاری شود، این لمعان می‌تواند الإحاء دهد که برنامه‌ای کند یا غیر پاسخگو است.

تحلیل فنی مسئله

برای درک اینکه چگونه این مشکل را حل کنیم، باید ابتدا بفهمیم چرا این مشکل رخ می‌دهد. تپش به دلیل تاخیر زمانی بین سه رویداد کلیدی در چرخه زندگی یک صفحه وب رخ می‌دهد :

  1. پارس اولیه HTML: مرورگر ساختار صفحه شما را می‌خواند و تجزیه می‌کند.

  2. اعمال استایل‌های CSS: مرورگر قوانین CSS را اعمال می‌کند و rendering بصری را محاسبه می‌کند

  3. اجرای JavaScript: کد شما که تم را تغییر می‌دهد اجرا می‌شود

مشکل زمانی پیش می‌آید که رویداد شماره ۳ (اجرا کردن جاوااسکریپت) پس از آن رخ می‌دهد که مرورگر پیش‌تر شروع یا پایان رویداد شماره ۲ (اعمال استایل‌ها) را داشته باشد. در این لحظه، مرورگر قبلاً تصمیم گرفته است کدام تم را نمایش دهد و کد شما به دیرستی می‌رسد تا بتواند پیش از رندر اول آن را تحت تأثیر قرار دهد.

چرا یک اسکریپت کلاسیک کافی نیست؟

روشی حدسی اما غیر مؤثر

راه‌حل طبیعی‌ترین برای یک توسعه‌دهنده این بود که یک اسکریپت را در پایان ما`<body>`که تم ترجیحی کاربر را بررسی کرده و آن را اعمال می‌کند. این رویکرد به روش‌های سنتی بهتر وب پایبند است که بارگذاری اسکریپت‌ها در انتهای صفحه را پیشنهاد می‌دهند تا رندر مسدود نشود.

// À la fin de <body> - L'APPROCHE INSUFFISANTE
document.addEventListener('DOMContentLoaded', () => {
  const theme = localStorage.getItem('preferred-theme');
  if (theme === 'dark') {
    document.documentElement.setAttribute('data-bs-theme', 'dark');
  }
});

این رویکرد در نگاه اول منطقی به نظر می‌رسد. ما صبر می‌کنیم تا DOM آماده شود، سپس تم را اعمال می‌کنیم. ساده، نیست؟ متأسفانه، این سادگی یک عیب اساسی مربوط به زمان‌بندی چرخه رندر مرورگر را پنهان می‌کند.

درک رویداد DOMContentLoaded

رویداد`DOMContentLoaded`در زمان بارگذاری کامل و تجزیه سند HTML اولیه توسط مرورگر آغاز می‌شودبدون انتظارپایان بارگذاری استایل‌ها، تصاویر و زیر-چارچوب‌ها. این یک نکته مهم برای درک است.

این دنبالهٔ عادی رویدادها :

  1. مرورگر شروع به دانلود HTML می‌کند

  2. او HTML را به محض دریافت آن تجزیه و تحلیل می‌کند.

  3. او برچسب‌ها را می‌یابد`<link>`برای CSSها و شروع به دانلودشان کنید

  4. او برچسب‌ها را می‌یابد.`<script>`و آن‌ها را اجرا می‌کند (بر اساس نوع و ویژگی‌هایشان)

  5. او DOM (Document Object Model) را می‌سازد.

  6. رویداد DOMContentLoaded راه‌اندازی می‌شود

  7. او همچنان استایل‌ها را اعمال می‌کند و چیدمان را انجام می‌دهد

  8. اولین paint (نمایش) رخ می‌دهد

  9. رویداد`load`وقتی که تمام منابع بارگذاری شده‌اند، اجرا می‌شود

مسئله؟ بین مرحله ۶ (DOMContentLoaded) و مرحله ۸ (premier paint)، مرورگر تصمیمات خود را درباره نحوه نمایش صفحه گرفته است. اگر اسکریپت تغییر تم شما در مرحله ۶ اجرا شود، هم‌اکنون بیش از حد دیر شده است تا یک نمایش اولیه با سبک‌های پیش‌فرض را جلوگیری کنید.

مشکل بلاکینگ رندر

در واقع، زمان‌بندی حتی پیچیده‌تر است. مرورگرهای مدرن از تکنیک‌های بهینه‌سازی پیچیده برای بهبود عملکرد ادراکی استفاده می‌کنند. سعی می‌کنند اولین Paint (First Contentful Paint) را به سرعت‌ترین شکل ممکن انجام دهند تا کاربر چیزی روی صفحه ببیند.

CSS به طور پیش‌فرض "render-blocking" هستند، یعنی مرورگر صبر می‌کند تاStylesheet‌ها را دانلود و پارس کند قبل از اولین paint. این منطقی است: ما نمی‌خواهیم محتوایی بدون استایل نمایش دهیم.

اما اینجا فخ است: هنگامی که مرورگر این استایل‌های CSS را برای اولین بار اعمال می‌کند، او این کار را بر اساس وضعیت فعلی DOM انجام می‌دهد. اگر ویژگی`data-bs-theme`هنوز برچسب آن تعریف نشده است.<html>, مرورگر استایل‌های پیش‌فرض را اعمال می‌کند (معمولاً تم روشن).

سپس، وقتی اسکریپت شما اجرا می‌شود و این ویژگی را تغییر می‌دهد، مرورگر باید:

  1. باز محاسبه تمام استایل‌های متأثر از این تغییر

  2. بازسازی چیدمان در صورت نیاز

  3. عناصر تحت تأثیر را دوباره رنگ بندی کنید

این فرآیند محاسبه مجدد و رنگ‌پخش آن است که باعث چشمک‌زدن مرئی می‌شود.

نمایش مسئله

برای درک بهتر این دنباله مشکل‌دار، یک نمودار دنباله دقیق را بررسی می‌کنیم:

@startuml
actor Utilisateur
participant Navigateur
participant "پارسر HTML" as Parser
participant "مدل شیء CSS" as CSS
participant "Script.js (پایان body)" as Script
participant "موتور رندر" as Render

Utilisateur -> Navigateur: Demande la page
activate Navigateur

Navigateur -> Parser: Parse le HTML
activate Parser
Parser -> CSS: Découvre et charge les CSS
activate CSS
Parser -> Parser: Construit le DOM
Parser -> Navigateur: DOM Ready (DOMContentLoaded)
deactivate Parser

CSS -> CSS: Parse les feuilles de style
CSS -> Render: CSSOM Ready
deactivate CSS

activate Render
Render -> Render: Calcule les styles avec data-bs-theme=""note right: موضوع پیش‌فرض (روشن)
Render -> Navigateur: Paint اولین (موضوع روشن)
note right: کاربر تم روشن را می‌بیند
deactivate Render

Navigateur -> Script: اسکریپت را اجرا می‌کند
activate Script
Script -> Script: localStorage را می‌خواند
Script -> Navigateur: setAttribute('data-bs-theme', 'dark')
deactivate Script

activate Render
Render -> Render: استایل‌ها را با data-bs-theme= محاسبه می‌کند"dark"
note right: Thème sombre
Render -> Navigateur: Repaint (Thème Sombre)
note right: Clignotement visible
deactivate Render

deactivate Navigateur
@enduml

این نمودار واضحاً problème را نشان می‌دهد: اولین paint قبل از اینکه اسکریپت ما فرصت داشته باشد تم مناسب را تنظیم کند، رخ می‌دهد. paint بعدی که رخ می‌دهد، لمعان قابل‌توجه را ایجاد می‌کند.

تلاش‌های ناکارآمد برای حل

برخی روش‌ها برای حل این مشکل امتحان شده‌اند، اما اکثر آن‌ها معایب خاص خود را دارند:

رویکرد ۱: پنهان کردن محتوا تا بارگذاری

body {
  opacity: 0;
  transition: opacity 0.3s;
}

body.loaded {
  opacity: 1;
}

این رویکرد تمام محتوا را تا زمانی که جاوااسکریپت-theme مناسب را تنظیم نکرده باشد، پنهان می‌کند. مشکل؟ این کار به‌طور مصنوعی نمایش محتوا را به تعویق می‌اندازد، که باعث می‌شود سایتی کندتر به نظر برسد. علاوه بر این، اگر جاوااسکریپت غیرفعال باشد، کاربر چیزی را نخواهد دید!

روش 2 : استفاده از loader/spinner

مثل رویکرد ۱، اما با یک spinner بارگذاری. این مشکل را پنهان می‌کند اما عملکرد واقعی را بهبود نمی‌دهد و یک تأخیر محسوس بی‌معنی اضافه می‌کند.

روش 3: پیش‌فرض به تم تاریک

برخی توسعه‌دهندگان تم تیره را به‌عنوان پیش‌فرض در CSS تعریف می‌کنند. این موجب می‌شود تا برای کاربران تم تیره، لمعان جلوگیری شود، اما مشکل معکوسی را برای کاربران تم روشن ایجاد می‌کند!

هیچ‌یک از این رویکردهاacceptable نیستند زیرا علائم را به جای علت ریشه‌ای درمان می‌کنند.

راه‌حل واقعی : عمل زودتر

کلید برای حل این مشکل این است که متوجه شویم باید ویژگی را تعریف کنیم`data-bs-theme` پیشکه مرورگر شروع به اعمال استایل‌های CSS نکند. این به این معناست که اسکریپت ما باید در دورهٔ زندگی صفحه زودتر اجرا شود، و دقیقاً این چیزی است که در بخش بعدی به بررسی آن می‌پردازیم.

حل: بارگذاری زودهنگام (Early Loading)

اصلهٔ اساسی

حل شایستگی به مشکل چشمک زدن ما بر یک اصل ساده ولی قدرتمند است :همگام‌سازی وضعیت برنامه با فرآیند نمایش مرورگر. به‌جای انتظار برای بارگذاری صفحه تا تم را تنظیم کنیم، باید آن را تنظیم کنیمدر حالی کهبارگذاری، حتی پیش از اینکه استایل‌های CSS اعمال شوند.

این روش به "Early Loading" یا "Synchronous Preloading" در اصطلاح توسعه وب شناخته می‌شود. ایده این است که منطق تشخیص تم ما را به زودی‌ترین ممکن در دورة زندگی صفحه اجرا کنیم، به طور ایده‌آل داخل تگ`<head>`, قبل از اینکه مرورگر شروع به دانلود فایل‌های CSS کند.

چرا <head> مکان ایده‌آل است

Le `<head>`یک اسناد HTML به صورت توالی توسط مرورگر پردازش می‌شود، از بالا به پایین. هر element در ترتیب ظاهر شدن پردازش می‌شود. این ویژگی برای راه‌حل ما بسیار مهم است.

وقتی مرورگر یک برچسب را می‌بیند`<script>`در`<head>`بدون ویژگی‌ها`async` ou defer, il :

  1. پارس کردن HTML را متوقف می‌کند

  2. اسکریپت را دانلود کن(اگر خارجی) یا تخت (به‌صورت inline)

  3. اسکریپت را فوراً اجرا کن

  4. تجدید پردازش HTML

این رفتار، اغلب به‌عنوان یک مشکل عملکرد در نظر گرفته می‌شود (از این رو پیشنهاد عادی قرار دادن اسکریپت‌ها در انتهای صفحه است)، در این حالت دقیق، حامی ما می‌شود. با قرار دادن اسکریپت تشخیص تم ما در ابتدای`<head>`, ما تضمین می‌کنیم که قبل از اینکه مرورگر به تگ‌ها برسد، اجرا شود`<link>`قالbهاي‍‍‌سب‍‍ک‌مان.

معماری حل در سه لایه

راه‌حل کامل ما شامل سه لایه وابسته به یکدیگر است که هر یک نقش خاصی دارد :

لایه 1 : Persistence (localStorage) این لایه مسئول ذخیره‌سازی و بازیابی انتخاب کاربر بین جلسات است.

لایه ۲: همگام‌سازی زودهنگام (اسکریپت درونخطی در <head>) این لایه وضعیت برنامه را با DOM قبل از رندر اولیه همگام‌سازی می‌کند.

لایه 3 : استایل‌های واکنش‌گرا (CSS با انتخابگرهای ویژگی) این لایه سبک‌های بصری را بر اساس حالتی که توسط لایه ۲ تعریف شده است، تعریف می‌کند.

اکنون هر لایه را به‌صورت تفصیلی بررسی می‌کنیم.

مرحله ۱: ذخیره‌سازی انتخاب کاربر

localStorage: حافظه‌ی پایدار شما

Le localStorage`یک API ذخیره‌سازی وب است که اجازه می‌دهد جفت‌های کلید-مقدار را به‌صورت پایدار در مرورگر ذخیره کند. برعکس کوکی‌ها، داده‌های`localStorage:

  • هرگز به سرور به‌صورت خودکار ارسال نمی‌شوند.

  • یک ظرفیت ذخیره‌سازی بزرگ‌تر دارند (معمولاً 5-10 مگابایت)

  • تاریخ انقضا ندارند (تا زمان حذف صریح پابرجاست)

  • به پروتکل و دامنه محدود می‌شوند (Same-Origin Policy)

برای مورد استفاده ما، le`localStorage`عالی است چون :

  1. ما نیازی به اشتراک‌گذاری این اطلاعات با سرور نداریم.

  2. ما می‌خواهیم ترجیح به‌صورت نامحدود ادامه یابد

  3. حجم ذخیره‌سازی مورد نیاز بسیار کم است (چند بایت)

پیاده‌سازی پشتیبان‌گیری از تم

این چگونه انتخاب کاربر را هنگام تغییر تم ذخیره می‌کنیم :

// Fonction complète pour changer le thème
function setTheme(newTheme) {
  // Validation de l'entrée
  if (!['light', 'dark', 'auto'].includes(newTheme)) {
    console.error('Thème invalide:', newTheme);
    return;
  }

  try {
    // Sauvegarde dans localStorage
    localStorage.setItem('preferred-theme', newTheme);

    // Application immédiate dans le DOM
    document.documentElement.setAttribute('data-bs-theme', newTheme);

    // Dispatch d'un événement personnalisé pour notifier d'autres composants
    window.dispatchEvent(new CustomEvent('theme-changed', {
      detail: { theme: newTheme }
    }));

    console.log('Thème changé:', newTheme);
  } catch (error) {
    console.error('Erreur lors de la sauvegarde du thème:', error);
    // Fallback : on applique quand même le thème visuellement
    document.documentElement.setAttribute('data-bs-theme', newTheme);
  }
}

// Exemple d'utilisation avec un bouton
document.getElementById('theme-toggle').addEventListener('click', () => {
  const currentTheme = document.documentElement.getAttribute('data-bs-theme') || 'light';
  const newTheme = currentTheme === 'light' ? 'dark' : 'light';
  setTheme(newTheme);
});

مدیریت موارد خطا

مهم است که مواردی که …​ را مدیریت کنیم`localStorage`موجود نیست یا قابل دسترسی نیست. چند سناریو می‌تواند مانع دسترسی به شود`localStorage`:

ناوبری خصوصی سخت: Safari در حالت مرور خصوصی یک استثنا ایجاد می‌کند`QuotaExceededError`در حین تلاش‌ها برای نوشتن در`localStorage`.

تنظیمات حریم خصوصیبرخی مرورگرها یا افزونه‌های حریم خصوصی ممکن است دسترسی به را مسدود کنند`localStorage`.

محدودیت‌های دامنه : Le `localStorage`در پروتکل قابل دسترسی نیست`file://`در برخی مرورگرها.

فضای ذخیره‌سازی اشباع شدهاگرچه نادر است، فضای ذخیره‌سازی می‌تواند به‌طور کامل پر شود.

به همین دلیل کد ما از یک بلوک استفاده می‌کند.`try…​catch`برای مدیریت این موارد به‌صورت لطف‌آمیز، با ادامهٔ ارائهٔ ویژگی تغییر تم حتی وقتی پایداری موجود نباشد.

استراتژی‌های پیشرفته پایداری

برای برنامه‌های پیچیده‌تر، می‌توانید استراتژی‌های بیشتری در نظر بگیرید :

همگام‌سازی سرور (اختیاری)

async function setTheme(newTheme) {
  // Sauvegarde locale immédiate
  localStorage.setItem('preferred-theme', newTheme);
  document.documentElement.setAttribute('data-bs-theme', newTheme);

  // Synchronisation serveur en arrière-plan (si l'utilisateur est connecté)
  if (userIsAuthenticated()) {
    try {
      await fetch('/api/user/preferences', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ theme: newTheme })
      });
    } catch (error) {
      console.warn('Échec de la synchronisation serveur:', error);
      // L'échec n'est pas critique car la préférence est déjà sauvegardée localement
    }
  }
}

این روش امکان همگونی ترجیحات بین دستگاه‌ها برای کاربران متصل را فراهم می‌آورد، در حالی که واکنش‌پذیری محلی فوری را حفظ می‌کند.

مرحله 2 : اسکریپت بارگذاری Preloud در `<head>

قلب راه‌حل

اینجا است که جادو به‌طور واقعی کار می‌کند. ما یک اسکریپت کوچک قرار می‌دهیم.انلاینمستقیماً در ماست`<head>`, تمام برچسبهای ما`<link>`قالب‌های استایل. این اسکریپت به‌طور عمدی به‌سادگی، خودمختار و برای اجرا به سرعت حداکثر ممکن طراحی شده است.

<!DOCTYPE html>
<html lang="fr">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Mon Site Incroyable</title>

    <!-- ==========================================
         NOTRE SCRIPT MAGIQUE DE PRÉ-CHARGEMENT
         Ce script DOIT être le premier élément
         dans le <head> après les meta tags
         ========================================== -->
    <script>
      // IIFE pour ne pas polluer le scope global
      (function() {
        'use strict';

        try {
          // Lecture de la préférence sauvegardée
          const savedTheme = localStorage.getItem('preferred-theme');

          // Si une préférence existe, on l'applique immédiatement
          if (savedTheme) {
            document.documentElement.setAttribute('data-bs-theme', savedTheme);
          }
          // Optionnel : Détecter la préférence système si aucune sauvegarde
          else if (window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches) {
            document.documentElement.setAttribute('data-bs-theme', 'dark');
          }
          // Sinon, le thème par défaut du CSS sera utilisé (généralement 'light')

        } catch (error) {
          // En cas d'erreur (localStorage bloqué, etc.), on log discrètement
          // et on laisse le thème par défaut s'appliquer
          console.warn('Impossible de charger la préférence de thème:', error);
        }
      })();
    </script>
    <!-- FIN DU SCRIPT MAGIQUE -->

    <!-- Les feuilles de style sont chargées APRÈS le script -->
    <link rel="stylesheet" href="css/bootstrap.min.css">
    <link rel="stylesheet" href="css/styles.css">

    <!-- Autres ressources du head -->
    <link rel="icon" href="favicon.ico">
</head>
<body>
    <!-- Contenu de la page -->
</body>
</html>

ساختار اسکریپت : هر خط مهم است

ب질을 کنیم این اسکریپت را خط به خط برای درک هر ديزاین設計 :

IIFE (عبارت تابعی بلافاصله فراخوانی می‌شود)

(function() {
  // ...
})();

این ساختار یک تابع را ایجاد می‌کند که بلافاصله اجرا می‌شود. چرا؟ برای جداسازی متغیرهای ما در یک حوزه محلی و جلوگیری از آلودهٔ حوزه سراسری. حتی اگر ما فقط`const`(که یک حوزة بلوک دارد)، IIFE یک روش خوب است که intentions ما را واضح می‌کند و در برابر تداخل‌های احتمالی نام‌ها محافظت می‌کند.

حالت سخت‌گیرانه

'use strict';

in directive faal mikonad hale sakhtgirane javascrippt ra, ke : - استفاده از متغیرهای غیر اعلام‌شده را ممنوع می‌کند - خطا برای عملیات‌های خطرناک ایجاد کن - بهبود عملکرد در برخی موتورهای JavaScript

برای یک اسکریپت حساس مثل این، ما امنیت حداکثر را می‌خواهیم.

بلوک try…​catch

try {
  // Code principal
} catch (error) {
  console.warn('Impossible de charger la préférence de thème:', error);
}

این بلوک به‌طور کلی ضروری است. مطمئن می‌سازد که اگر چیزی به‌خوبی نرود (localStorage مسدود، خطای غیرممکن در سینتکس و غیره)، اسکریپت ما بارگذاری کامل صفحه را مسدود نخواهد کرد. استفاده از`console.warn`به جای`console.error`نشان می‌دهد که این یک مشکل غیر بحرانی است.

خواندن localStorage

const savedTheme = localStorage.getItem('preferred-theme');

این خط می‌تواند در برخی موارد (حالت خصوصی سخت Safari) یک استثنا بزند. به همین دلیل آن را در یک بلوک try…​catch قرار داده‌ایم.

برنامه شرطی

if (savedTheme) {
  document.documentElement.setAttribute('data-bs-theme', savedTheme);
}

ما تم را فقط در صورت یافتن تم ذخیره‌شده اعمال می‌کنیم؛ در غیر این صورت، به CSS اجازه می‌دهیم تا از تم پیش‌فرض خود استفاده کند. این روش قوی‌تر از یک مقدار پیش‌فرض کد شده در JavaScript است.

تشخیص ترجیح سیستم (به‌علاوه)

یک بهبود اختیاری اما شایسته این است که تم ترجیح کاربر را از سیستم عامل او تشخیص دهید، در صورتی که او هنوز در برنامه شما گزینش صریحی نکرده باشد:

else if (window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches) {
  document.documentElement.setAttribute('data-bs-theme', 'dark');
}

این ویژگی از Media Query استفاده می‌کند.`prefers-color-scheme`برای پرس و جو از سیستم. در macOS، Windows 10+، iOS و اندروید مدرن، این درخواست ترجیح سیستمی کاربر را برمی‌گرداند.

مزایا : - تجربه سفارشی از اولین بازدید - سازگاری با محیط سیستم کاربر - هیچ ذخیره‌سازی لازم برای اولین بازدید نیست

ملاحظات : - همهٔ مرورگرها این ویژگی را پشتیبانی نمی‌کنند (اما پشتیبانی از آن از 2020 عالی است) - بررسی`window.matchMedia`سازگاری را تضمین می‌کند - کاربر می‌تواند این گزینه را همیشه بازنویسی کند

عملکرد: چرا این اسکریپت سریع است

اسکریپت پیش‌بارگذاری ما به گونه‌ای طراحی شده است که بسیار سریع باشد :

اندازه حداقل: حدود 300 بایت غیر-minified، 200 بایت minified. این مقدار ضعیف است در مقایسه با هر تصویر یا کتابخانه JavaScript.

به صورت خطی: هیچ درخواست HTTP اضافی وجود ندارد. اسکریپت در HTML است، بنابراین بلافاصله در دسترس است.

عملیات همگام ساده: خواندن یک کلید در localStorage (عملیهٔ فوق‌العاده سریع) و تغییر یک ویژگی DOM (عملیهٔ مرورگر).

هیچ وابستگی: هیچ فریم‌ورک، هیچ کتابخانه، فقط جاوااسکریپت وانیل. هیچ زمان استارت‑اپ، هیچ پارсинг وابستگی.

اجرا یک‌بار: این اسکریپت فقط یک بار در زمان بارگذاری اجرا می‌شود. هیچ listener رویدادی، هیچ حلقه، هیچ محاسبه پیچیده‌ای ندارد.

در عمل، روی سخت‌افزار مدرن، این اسکریپت در کمتر از 1 میلی‌ثانیه اجرا می‌شود؛ زمان نامحسوسی که هیچ تاثیری بر عملکرد بارگذاری صفحه ندارد.

قرارگیری بهینه در `<head>

ترتیب عناصر در`<head>`مهم است. این ترتیب پیشنهادی است :

<head>
    <!-- 1. Métadonnées critiques -->
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">

    <!-- 2. Notre script de pré-chargement (IMMÉDIATEMENT après les meta) -->
    <script>
      (function() { /* notre code */ })();
    </script>

    <!-- 3. Titre de la page -->
    <title>Mon Site</title>

    <!-- 4. Feuilles de style -->
    <link rel="stylesheet" href="styles.css">

    <!-- 5. Autres ressources (fonts, favicons, etc.) -->
    <link rel="preconnect" href="https://fonts.googleapis.com">
    <link rel="icon" href="favicon.ico">

    <!-- 6. Autres scripts avec defer ou async -->
    <script src="app.js" defer></script>
</head>

این دستور ضمانت می‌دهد که:

  1. Charset قبل از پردازش متنی تعریف می‌شود.

  2. اسکریپت ما پیش از بارگذاری CSS اجرا می‌شود

  3. CSSها پس از بارگذاری، مستقیماً تم مناسب را اعمال می‌کنند

  4. منابع غیر بحرانی دیگر در آخر بارگذاری می‌شوند

مرحله 3 : قدرت انتخاب‌کننده‌های attribut CSS

سیستم تم Bootstrap 5

Bootstrap 5 یک سیستم زیبای مدیریت تم‌ها مبتنی بر ویژگی‌های سفارشی CSS (متغیرهای CSS) و انتخابگرهای ویژگی‌ها را معرفی کرده است. این سیستم از ویژگی`data-bs-theme`بر روی عنصر`<html>`برای تعیین کدام مجموعه از متغیرهای رنگ باید اعمال شود.

زیبای این سیستم در سادگی آن نهفته است: به جای بارگذاری استایل‌شیت‌های مختلف یا toggle کلاس‌ها بر روی هزاران عنصر، ما صرفاً یک ویژگی را در یک عنصر تغییر می‌دهیم و CSS به‌دلیل بقASCADE، بقیه را انجام می‌دهد.

ساختار CSS برای یک سیستم تم

در اینجا یک ساختار CSS کامل برای پیاده‌سازی یک سیستم طرح/theme مقاوم آورده شده است:

/**
 * SYSTÈME DE THÈME COMPLET
 * Utilise les Custom Properties CSS pour une maintenance facile
 */

/* ============================================
   THÈME PAR DÉFAUT (LIGHT)
   Défini sur :root pour être le fallback
   ============================================ */
:root {
  /* Couleurs de base */
  --color-primary: #0d6efd;
  --color-secondary: #6c757d;
  --color-success: #198754;
  --color-danger: #dc3545;
  --color-warning: #ffc107;
  --color-info: #0dcaf0;

  /* Couleurs de fond */
  --bg-primary: #ffffff;
  --bg-secondary: #f8f9fa;
  --bg-tertiary: #e9ecef;

  /* Couleurs de texte */
  --text-primary: #212529;
  --text-secondary: #6c757d;
  --text-tertiary: #adb5bd;

  /* Couleurs de bordure */
  --border-color: #dee2e6;
  --border-color-subtle: #e9ecef;

  /* Couleurs d'ombre */
  --shadow-sm: rgba(0, 0, 0, 0.075);
  --shadow-md: rgba(0, 0, 0, 0.15);
  --shadow-lg: rgba(0, 0, 0, 0.25

مقالات مرتبط