Kopi Yin-Yang Monokromatik oleh Alex

Kontrol Mode Terang/Gelap dengan JavaScript dan PHP

Membuat kontrol mode terang/gelap pada situs web itu sebenarnya tidak rumit. Kadang yang membuatnya menjadi rumit adalah karena adanya kelas-kelas utilitas.

Tabel Konten
  1. Mengubah Mode dengan Klik Tombol 
  2. Menyimpan Mode untuk Dimuat di Sesi Berikutnya 
  3. Menggunakan Kuki 
  4. Bahasa Pemrograman Sisi-Peladen untuk Memperkuat Pondasi 
  5. Menggunakan Kueri Media 
  6. Kontrol Mode dengan Kelas Utilitas 

Kelas utilitas adalah kelas-kelas di dalam CSS yang bersifat presentasional (tidak semantik) dan hanya bertujuan untuk mendeskripsikan penampilan di permukaan “apa adanya” sesuai dengan nama kelas tersebut. Beberapa contoh nama kelas yang Saya maksud di sini adalah nama-nama seperti .color-black, .font-bold dan .is-hidden.

Nama-nama kelas seperti ini sangat umum dijumpai di dalam framework seperti Bootstrap, Semantic UI, dan Tailwind CSS. Meskipun berguna untuk mempercepat proses pembuatan aplikasi daring berskala besar, namun fitur-fitur semacam ini biasanya akan menimbulkan masalah ketika pengembang hendak memperbarui desain tanpa harus mengubah struktur HTML, seperti untuk mengubah tampilan menjadi terang atau gelap dengan sekali klik tombol.

Selain untuk menjelaskan mekanisme kontrol tema terang/gelap, Saya juga ingin menunjukkan bagaimana fitur-fitur kelas utilitas dapat membuat mekanisme kontrol terang/gelap menjadi rumit.

Mengubah Mode dengan Klik Tombol 

Untuk mengizinkan pengguna mengubah tema secara langsung, biasanya dalam situs web tersebut terdapat sebuah tombol untuk mengubah skema warna dari skema terang ke skema gelap atau sebaliknya. Pada tahap awal, kita buat saja seperti ini:

<a href="#">Tes</a>

Kemudian kita perlu membuat mekanisme agar ketika tautan (tombol) tersebut diklik, maka dia akan menambahkan kelas dark pada elemen akar, atau sebaliknya, akan menyingkirkan kelas dark dari elemen akar jika sudah ada:

let root = document.documentElement,
    toggle = root.querySelector('a');

toggle.addEventListener('click', e => {
    root.classList.toggle('dark');
    e.preventDefault();
}, false);

Dengan begitu, setiap kali tombol tersebut diklik maka akan memberikan efek seperti ini di dalam kode HTML:

Mengubah kelas elemen akar dengan klik tombol.
Menambahkan dan menyingkirkan kelas gelap dengan JavaScript.

Dengan menambahkan dan menyingkirkan kelas dark pada elemen akar sebenarnya sudah cukup untuk mengontrol keseluruhan tampilan karena kode CSS memiliki aturan ruang lingkup. Sehingga kita bisa membuat mode dasarnya sebagai mode terang. Mode gelap dapat dibuat dengan memanfaatkan kehadiran kelas tersebut saja.

Semua kode CSS dapat dinyatakan di dalam satu berkas:

/* Mode terang */
body {
  background: #fff;
  color: #000;
}

/* Mode gelap */
:root.dark body {
  background: #000;
  color: #fff;
}

Untuk mengubah label tombol sesuai dengan tema saat itu, kita tinggal mengubah visibilitas label berdasarkan kehadiran kelas dark seperti ini:

<a href="#">
  <span>Gelap</span>
  <span>Terang</span>
</a>
:root:not(.dark) a span:nth-child(1),
:root.dark a span:nth-child(2) {
  display: none;
}

Menyimpan Mode untuk Dimuat di Sesi Berikutnya 

Mekanisme pengubah tema di atas hanya berlaku untuk sesi saat itu saja. Ketika pengguna memuat ulang laman atau berpindah ke laman yang lain di situs web yang sama, maka efek gelap yang sudah diatur akan secara otomatis hilang. Untuk mengatasi masalah tersebut, kita perlu menerapkan fitur penyimpanan lokal pada peramban untuk menyimpan mode tema saat itu sebelum laman ditinggalkan:

toggle.addEventListener('click', e => {
    root.classList.toggle('dark');
    if (root.classList.contains('dark')) {
        localStorage.setItem('is-dark', 1);
    } else {
        localStorage.removeItem('is-dark');
    }
    e.preventDefault();
}, false);

Ketika diinspeksi, maka akan muncul item is-dark dengan nilai "1" pada mode gelap:

Menyimpan data dengan Window.localStorage
Data is-dark tersimpan di dalam penyimpanan lokal peramban.

Data tersebut tidak akan terhapus kecuali pengguna secara sengaja menghapusnya. Memuat ulang laman, menutup peramban dan mematikan perangkat tidak akan menghapus data tersebut.

Nilai item akan selalu tersimpan sebagai string, oleh karena itu meskipun Saya menyimpan nilai 1 pada item is-dark, yang tersimpan nantinya adalah "1". Itu sudah menjadi standar:

“The keys and the values are always in the UTF-16 DOMString format.”

Tapi itu tidak mengapa, karena di sini yang kita butuhkan hanya kuncinya saja yaitu is-dark. Nilai yang tersimpan di dalam item is-dark tidak begitu penting selama nilai tersebut bisa dievaluasi sebagai boolean benar.

Dengan mengecek kehadiran kunci tersebut, maka kita bisa menentukan mode gelap secara otomatis ketika laman dimuat kembali:

if (localStorage.getItem('is-dark')) {
    document.documentElement.classList.add('dark');
}

Pastikan untuk meletakkan kode tersebut sedekat mungkin dengan elemen akar pembuka. Tempat yang paling ideal menurut Saya adalah setelah pendefinisian set karakter dan sebelum pemuatan berkas CSS:

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <meta content="width=device-width" name="viewport">
    <script>
    if (localStorage.getItem('is-dark')) {
        document.documentElement.classList.add('dark');
    }
    </script>
    <link href="style.css" rel="stylesheet">
    <style> … </style>
    …

Semakin cepat kelas dark ditambahkan pada elemen akar, semakin bagus. Karena Saya melihat ada yang menyatakan kondisi penambahan kelas ini terlalu jauh di bawah dokumen sehingga tema gelap akan terlambat datang karena tertahan oleh berkas-berkas CSS dan JavaScript eksternal yang lain yang sedang dimuat sebelum kondisi tersebut berhasil dieksekusi.

Lihat Demo

Menggunakan Kuki 

Fitur kuki pada peramban bisa digunakan sebagai alternatif. Kekurangan dari fitur kuki adalah dia memiliki jangka waktu kadaluarsa (bisa diatasi dengan menyatakan jangka waktu kadaluarsa selama mungkin), dan cenderung rumit untuk diaplikasikan karena kita perlu mengatur waktu kadaluarsa pada setiap item kuki.

toggle.addEventListener('click', e => {
    root.classList.toggle('dark');
    if (root.classList.contains('dark')) {
        document.cookie = 'is-dark=1; expires=Fri, 31 Dec 9999 23:59:59 GMT; path=/';
    } else {
        document.cookie = 'is-dark=; expires=Thu, 01 Jan 1970 00:00:00 GMT; path=/';
    }
    e.preventDefault();
}, false);
if (document.cookie.split(';').some(v => v.includes('is-dark=1'))) {
    document.documentElement.classList.add('dark');
}

Kelebihan dari fitur kuki adalah dia biasanya mampu berinteraksi dengan bahasa sisi-peladen, sehingga tema gelap cenderung dapat dipertahankan meskipun fitur JavaScript sudah dimatikan, atau ketika kode JavaScript gagal dieksekusi karena beberapa kesalahan sintaks pada kode-kode JavaScript lain di sekitarnya.

Lihat Demo

Bahasa Pemrograman Sisi-Peladen untuk Memperkuat Pondasi 

Bahasa sisi-peladen yang Saya gunakan sebagai contoh di sini adalah bahasa pemrograman PHP. Bahasa-bahasa pemrograman sisi-peladen yang lain tentu memiliki aturan penulisan yang berbeda untuk mendapatkan data kuki dari peramban. Para pengembang diharapkan dapat menyesuaikan diri.

Karena data penyimpanan lokal peramban tidak bisa diakses oleh PHP, maka kita perlu menggunakan kuki. Kita menggunakan data kuki tersebut untuk mencetak kelas dark jika item is-dark ditemukan di dalam $_COOKIE. Dengan begini, maka kelas dark dapat ditambahkan ke dalam kode HTML, bahkan sebelum proses pengiriman data dilakukan:

<html class="<?= !empty($_COOKIE['is-dark']) ? 'dark' : ""; ?>">

Catatan: Kode JavaScript di sisi klien untuk mengecek kehadiran item kuki boleh ditiadakan karena sudah diatasi oleh kode PHP di sisi peladen.

Sebagai bonus, tombol pengontrol mode tema juga sebenarnya bisa dibuat supaya bekerja di sisi peladen dengan cara mengecek adanya kueri tertentu. Kode ini harus dinyatakan sebelum tajuk respons dikirim:

if (isset($_GET['is-dark'])) {
    $v = (int) $_GET['is-dark'];
    if (1 === $v) {
        setcookie('is-dark', $v, strtotime('Fri, 31 Dec 9999 23:59:59 GMT'), '/');
    } else {
        setcookie('is-dark', null, -1, '/');
    }
    // Variabel `$current_no_query` adalah tautan saat ini tanpa penambahan kueri `is-dark`
    // Masing-masing aplikasi memiliki cara tersendiri untuk membentuk “tautan saat ini”
    header('location: ' . $current_no_query);
    exit;
}

Kemudian, pada tombol kontrol mode tema, pada atribut href supaya dibuat menjadi seperti ini:

<a href="?is-dark=<?= !empty($_COOKIE['is-dark']) ? 0 : 1; ?>">

Menggunakan Kueri Media 

Sebuah fitur baru pada CSS memungkinkan kita untuk mengecek apakah peramban atau perangkat telah diatur ke mode terang atau mode gelap. Tidak semua peramban mendukung fitur ini:

/* Mode terang */
body {
  background: #fff;
  color: #000;
}

/* Mode gelap */
@media (prefers-color-scheme: dark) {
  body {
    background: #000;
    color: #fff;
  }
}

Untuk mengubah mode tidak dilakukan melalui situs web melainkan melalui pengaturan skema warna pada peramban atau perangkat masing-masing.

Pengaturan mode gelap pada sistem operasi Macintosh.
Pengaturan mode gelap pada sistem operasi Macintosh (sumber gambar).

Melalui JavaScript, kita bisa mengecek apakah kita sedang berada pada mode terang atau gelap dengan cara seperti ini:

if (window.matchMedia('(prefers-color-scheme: dark)').matches) {
    // Gelap
} else {
    // Terang (bawaan?)
}
if (window.matchMedia('(prefers-color-scheme: light)').matches) {
    // Terang
} else {
    // Gelap (bawaan?)
}

Perubahan juga bisa dikontrol melalui objek media yang didapatkan:

let media = window.matchMedia('(prefers-color-scheme: dark)');
media.addEventListener('change', e => {
    console.log(e.matches ? 'Gelap' : 'Terang (bawaan?)');
});

Kontrol Mode dengan Kelas Utilitas 

Kontrol mode terang/gelap dengan kelas utilitas hampir tidak bisa dilakukan dengan baik karena kita perlu menyeleksi semua elemen HTML yang diperlukan untuk ditambahi kelas utilitas mode gelap. Kalau kalian menggunakan AJAX, biasanya kode HTML yang dibentuk dari hasil respons perlu untuk diseleksi kembali sehingga akan menambah beban kerja JavaScript.

Berikut ini adalah contoh penerapan pada Bootstrap, untuk mengubah warna bar navigasi saja, tanpa menyimpan data ke penyimpanan lokal:

let navbars = document.querySelectorAll('.navbar'),
    btn = document.querySelector('.btn');

btn.addEventListener('click', e => {
    navbars.forEach(navbar => {
        navbar.classList.toggle('bg-dark');
        navbar.classList.toggle('navbar-dark');
    });
    e.preventDefault();
}, false);

Tidak praktis.

3 Komentar

  • Sugeng

    Saat ini saya menggunakan metode klik tombol untuk fitur mode gelap.

    Dari membaca artikel ini saya mempunyai ide untuk mengkombinasikan metode klik tombol dengan kueri media.

    Kurang lebih cara kerjanya seperti ini:

    • Jika perambaan pengguna mendukung fitur mode gelap, maka fitur mode gelapnya akan menyesuaikan dengan setelan perambaan pengguna.
    • Meskipun begitu, pengguna tetap bisa mengubah mode gelapnya melalui klik tombol.
    • Perubahan mode gelap melalui klik tombol tersebut akan tersimpan dan meng-override setelan perambaan.

    Jika berkenan mungkin mas Taufik bisa memberikan contoh penerapannya supaya saya tidak terlalu pusing menyusun kodenya dari nol. hehe..

    Terima kasih.

    • Taufik Nurrohman

      Tergantung yang mau diprioritaskan yang mana mas. Mau memprioritaskan fitur natif atau fitur klik tombol.

      Kalau ingin memprioritaskan fitur natif, kita cukup nambahin duplikat kode CSS mode gelap, tapi tanpa kelas mode gelap:

      body {
        background: #fff;
        color: #000;
      }
      
      .dark body {
        background: #000;
        color: #fff;
      }
      
      @media (prefers-color-scheme: dark) {
        body {
          background: #000;
          color: #fff;
        }
      }

      Kemudian di bagian JavaScript, supaya dilepas saja kelas mode gelapnya kalau memang sudah mendukung fitur mode gelap melalui perangkat:

      if (matchMedia('(prefers-color-scheme: dark)').matches) {
          root.classList.remove('dark');
          toggle.classList.add('active');
      }

      Kekurangannya, kode CSS untuk mode gelap harus ditulis dua kali. Satu untuk versi kelas dan satu untuk versi kueri media.

      Kalau ingin memprioritaskan fitur klik tombol, kode CSS bisa dibiarkan seperti apa adanya. Cuma di bagian JavaScript kita supaya nggak cuma mengecek fitur penyimpanan lokal tapi juga perlu mengecek fitur mode gelap natif:

      if (localStorage.getItem('is-dark')) {
          root.classList.add('dark');
          toggle.classList.add('active');
      }
      
      if (matchMedia('(prefers-color-scheme: dark)').matches) {
          root.classList.add('dark');
          toggle.classList.add('active');
      } else {
          root.classList.remove('dark');
          toggle.classList.remove('active');
      }

      Kekurangannya, kalau berdasarkan kode di atas sih seharusnya fitur natif bakalan mengalahkan status mode gelap dari fitur klik tombol. Kalau saja ada cara untuk mengubah pengaturan mode gelap di perangkat melalui JavaScript, kita bisa memaksa perangkat untuk mematikan pengaturan mode gelapnya. Tapi sepertinya itu tidak mungkin bisa dilakukan karena masalah privasi pengguna (bisa digunakan untuk memicu praktik XSS). Jadi efeknya, kalau pengguna mencoba mengganti ke mode gelap sedangkan pada perangkat dia memakai mode terang, efeknya bakalan hilang di sesi berikutnya karena perintah di blok else.

    • Taufik Nurrohman

      Atau pakai cara ini mungkin bisa mas, supaya fitur natif nggak begitu memaksa. Dengan mengecek aksi perubahan mode dari preferensi untuk mengatur nilai penyimpanan lokal:

      let media = matchMedia('(prefers-color-scheme: dark)');
      media.addEventListener('change', e => {
          if (e.matches) {
              localStorage.setItem('is-dark', 1);
          } else {
              localStorage.removeItem('is-dark');
          }
      });






Semua kode HTML akan dihapus kecuali kode-kode HTML yang dituliskan sebagai contoh. Gunakan sintaks Markdown untuk memberi gaya pada komentar.


Batal