طريقة إضافة وضع الليلي للموقع 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