طريقة إضافة وضع الليلي للموقع Dark mode

سليمان العثمان
سليمان العثمان
١٥ نوفمبر ٢٠٢٣
Cover Image for طريقة إضافة وضع الليلي للموقع Dark mode

من الأمور المستحسنة وجود وضع الليلي "Dark mode" في الموقع، بل أصبحت شبه مُتطلّب و تفضيل للكثير من المستخدمين، و في هذه التدوينة سنتطرق إلى طُرق و أساليب لضبط وضع الليلي في الموقع و التعامل مع Media queries من خلال جافاسكربت.

في البداية سأضع مثالين سنطبّق عليهم إضافة وضع الليلي، و هما لإطاريّ tailwindcss و bootstrap، لذلك إذا كنت تستخدم مكتبة أو إطار آخر لـ css فيمكنك تعديل بضعة أسطر لإضافة الوضع الليلي إلى موقعك.

مثال bootstrap

<!doctype html>
<html lang="en">
  <head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>System theme</title>
    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-T3c6CoIi6uLrA9TneNEoa7RxnatzjcDSCmG1MXxSR1GAsXEV/Dwwykc2MPK8M2HN" crossorigin="anonymous">
  </head>
  <body>
    <div class="vh-100 vw-100 d-flex flex-column justify-items-center justify-content-center text-center">
        <h1>Bootstrap</h1>
        <div class="d-flex justify-content-center">
            <button id="toggle-theme" class="btn btn-primary">Toggle mode!</button>
        </div>
    </div>
    
    <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.bundle.min.js" integrity="sha384-C6RzsynM9kWDrMNeT87bh95OGNyZPhcTNXj1NW7RuBCsyN/o0jlpcV8Qyq46cDfL" crossorigin="anonymous"></script>
    <script src="app.bootstrap.js">
    </script>
</body>
</html>

مثال tailwindcss

<!doctype html>
<html>
<head>
    <title>System theme</title>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <link href="/dist/output.css" rel="stylesheet">
</head>
<body class="dark:bg-neutral-900">
    <div class="w-screen h-screen flex flex-col gap-6 justify-center items-center">
        <h1 class="text-3xl font-light underline dark:text-neutral-50">
            Tailwindcss
        </h1>
        <button id="toggle-theme"
            class="px-4 py-2 rounded-xl bg-neutral-700 text-white dark:bg-neutral-700 dark:hover:bg-neutral-600 duration-200 dark:text-neutral-200">
            Toggle mode!
        </button>
    </div>
    <script src="app.js">
    </script>
</body>
</html>

إضافة زر لتغيير الوضع theme mode و حفظه في ذاكرة المتصفح

في البداية سنكتب دالة ( Function ) ثم نربطها مع الزر الموجود و الذي يحمل أيدي tooggle-theme، الكود يحتوي على شرط مكوّن من جزئين :

الجزء الأول : تحقق من وجود قيمة dark للمفتاح theme.

الجزء الثاني : التحقق من وجود مفتاح theme في localStorage

localStorage : ذاكرة لتخزين قيّم يحددها الموقع في المتصفح، مثل حفظ تفضيلات المستخدم من ثيم أو token أو غيرهما.

Tailwindcss

في حال تحقق أحد الجزئين يُنفّذ الكود بداخل الشرط "الأقواس المعكوفة"، و هو حذف class المُسمّى بـ dark من الوسم الرئيسي html : main tag. ثم تغيير قيمة مفتاح theme إلى light.

في حال عدم تحقق الشرط، يتم إضافة class dark إلى وسم html و تغيير قيمة مفتاح theme إلى dark.

const toggleTheme = () => {
    if (localStorage.theme === 'dark' || (!('theme' in localStorage))) {
        document.documentElement.classList.remove('dark');
        localStorage.theme = 'light';
        return;
    }
    document.documentElement.classList.add('dark');
    localStorage.theme = 'dark';
}

document.querySelector('#toggle-theme').addEventListener('click', toggleTheme);
Bootstrap

المفهوم و الطريقة نفسها بإختلاف إن bootstrap تعتمد على إضافة خاصية data-bs-theme لوسم الرئيسي html يحمل قيمة dark، و في حال تغيير الوضع إلى وضع النهاري يتم حذف الخاصية.

const toggleTheme = () => {
    if (localStorage.theme === 'dark' || (!('theme' in localStorage))) {
        document.documentElement.removeAttribute('data-bs-theme');
        localStorage.theme = 'light';
        return;
    }
    document.documentElement.setAttribute('data-bs-theme', 'dark');
    localStorage.theme = 'dark';
}

document.querySelector('#toggle-theme').addEventListener('click', toggleTheme);

معرفة تفضيل وضع ثيم النظام

يمكن معرفة وضع النظام و الثيم المستخدم من خلال query media التي يوفّرها المتصفح لنا و التعامل معها من خلال جافاسكربت عن طريق الكود التالي :


if (localStorage.theme === 'dark' || (!('theme' in localStorage) && window.matchMedia('(prefers-color-scheme: dark)').matches)) {
    // Add dark
} else {
    // Remove dark
}

الكود يحوي على شرط له جزئين أساسيين، الجزء الأول تمّ شرحه في الكود السابق، و الجزء الثاني هو إستعلام عن قيمة prefers-color-scheme إذا كانت dark و في حال تحقق الشرطّين يتم تنفيذ الكود الذي يحتويه الشرط، و في حال عدم تحققه يتم تنفيذ الكود الآخر، و هُنا مثال لضبط tailwindcss عليه :

if (localStorage.theme === 'dark' || (!('theme' in localStorage) && window.matchMedia('(prefers-color-scheme: dark)').matches)) {
    document.documentElement.classList.add('dark');
} else {
    document.documentElement.classList.remove('dark');
}

و لضبط bootstrap :

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

مزامنة تغيير وضع ثيم النظام

من الأشياء التي يمكن توفيرها بالموقع هو مزامنة تغيير اللحظي لثيم النظام و تغيير ثيم الموقع بناءً عليه، و يمكن ذلك من خلال إضافة event listener على التغيير change لـ matchMedia، من خلال الكود التالي :

window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', ({ matches }) => {
    if (matches) {
        // Add dark
    } else {
        // Remove dark
    }
})

في هذا المثال نتنصّت على matchMedia في حال تمّ تحديث قيمته و مطابقته لـ dark يتحقق الشرط بداخله و يتنفذ الجزء الخاص به و في حال لم يتحقق يتم تنفيذ الجزء الخاص بعدم تحقق الشرط، و من أجل ضبطه بحيث لا يتعارض مع حفظ المستخدم لتفضيله نعدّل الكود بحيث يُصبح كالتالي لـ tailwindcss :

window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', ({ matches }) => {
    if (('theme' in localStorage)) return;
    if (matches) {
        document.documentElement.classList.add('dark');
    } else {
        document.documentElement.classList.remove('dark');
    }
});

حيث نُضيف شرط في حال وجود مفتاح theme في localStorage يتنفّذ return و نخرج من الدالة كلها و لا يتنفّذ بقية الكود، و في حال عدم وجودها يتمّ تنفيذ باقي الكود، و هنا مثال لـ bootstrap.

window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', ({ matches }) => {
    if (('theme' in localStorage)) return;
    if (matches) {
        document.documentElement.setAttribute('data-bs-theme', 'dark');
    } else {
        document.documentElement.removeAttribute('data-bs-theme');
    }
})

شيفرة js كاملة :

Tailwindcss :

const toggleTheme = () => {
    if (localStorage.theme === 'dark' || (!('theme' in localStorage))) {
        document.documentElement.classList.remove('dark');
        localStorage.theme = 'light';
        return;
    }
    document.documentElement.classList.add('dark');
    localStorage.theme = 'dark';
}

if (localStorage.theme === 'dark' || (!('theme' in localStorage) && window.matchMedia('(prefers-color-scheme: dark)').matches)) {
    document.documentElement.classList.add('dark');
} else {
    document.documentElement.classList.remove('dark');
}


window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', ({ matches }) => {
    if (('theme' in localStorage)) return;
    if (matches) {
        document.documentElement.classList.add('dark');
    } else {
        document.documentElement.classList.remove('dark');
    }
});

document.querySelector('#toggle-theme').addEventListener('click', toggleTheme);

Bootstrap :

const toggleTheme = () => {
    if (localStorage.theme === 'dark' || (!('theme' in localStorage))) {
        document.documentElement.removeAttribute('data-bs-theme');
        localStorage.theme = 'light';
        return;
    }
    document.documentElement.setAttribute('data-bs-theme', 'dark');
    localStorage.theme = 'dark';
}

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


window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', ({ matches }) => {
    if (('theme' in localStorage)) return;
    if (matches) {
        document.documentElement.setAttribute('data-bs-theme', 'dark');
    } else {
        document.documentElement.removeAttribute('data-bs-theme');
    }
});

document.querySelector('#toggle-theme').addEventListener('click', toggleTheme);

تنويه

موضوع Media queries ليس محصورًا على تحديد وضع النظام و تفضيلاته، بل يتعدى لأكثر من ذلك، لذلك إن أردت أن تستزيد فيمكنك مشاهدة التدوينة التالية على مدونة Tutomena

المراجع و المصادر

Tailwindcss dark mode

Bootstrap color modes

Mozilla media queries

Detect System Theme Preference Change Using JavaScript

تصميم@sulealothman