‚Dark Mode‘ für Webseiten? Geräte-Voreinstellung, individuelle Einstellung oder beides?

Ja, auch das Thema ist schon "älter", spätestens mit iOS 13 (Mitte 2019) war das Thema irgendwie in aller Munde. Seitdem meint 'Dark Mode' eine Einstellung, die meist hellen Hintergründe und Flächen von Webseiten und Apps zu "invertieren" (also statt dunkler Schrift auf hellem Grund lieber helle Schrift auf dunklem Grund anzuzeigen).

In verschiedenen Kontexten nutze ich das schon länger, z.B. beim Lesen von eBooks. Ich empfinde die hellen Buchstaben als deutlich augenfreundlicher und muss immer an einen damaligen Prof. denken, seines Zeichens ABO-Psychologe und Psychophysiker, der (sinngemäß zititert) gesagt hat:

"Das Auge ist ein 'Lichtsucher' und 'Reizsucher'. Vor allem für elektronische (Lese-) Medien gilt: Bei dunkler Schrift auf hellem Grund wird zuvorderst die weiße Fläche (Lichtreiz) wahrgenommen und erst in einem zweiten Schritt der Buchstabe als 'Unterbrechung dieser leuchtenden Fläche'. Die kognitive Leistung der notwendigen Invertierung von 'Lücke in der Leuchtfläche' zu 'Buchstabe' kostet Energie und Zeit, die bei heller Schrift auf dunklem Grund recht einfach eingespart werden kann."

Ob das einer experimentellen Überprüfung standhält, weiß ich nicht, es erschien mir aber bereits vor Jahren irgendwie ein'leuchtend' 🙂 Wie auch immer, der Dark Mode ist da und zu einem dieser Webdesign- und Entwicklertrends geworden, die wohl nie wieder ganz verschwinden werden, daher:

Yay. Wir basteln uns einen Dark Mode (für Webseiten)!

Technisch sind diverse Dinge zu beachten:

  1. Es gibt einerseits Gerätevoreinstellungen auf Betriebssystem-Ebene.
  2. Benutzende von Webseite sollten die Möglichkeit haben, diese Voreinstellung mit individuellen EInstellungen für eine Webseite zu überschreiben.
  3. Bei wiederkehrenden Besuchenden wäre eine Speicherung der Präferenzen sinnvoll.

1. Dark Mode auf Systemebene: Gerätevoreinstellungen

Die Gerätevoreinstellungen gibt es z.B. seit iOS 13, Android 10 (bei Version 9 tlw. als "Entwickleroption" verfügbar), macOS Mojave (v.10.14) und Windows 10 (1809) auf diversen Geräten.

Dafür benätigt man nur wenige Zeilen Code, nämlich einen <meta> tag im <head> des html-Dokuments und das entsprechende CSS, welches mittels media queries (MDN Docs) @media (prefers-color-scheme: dark) bzw. @media (prefers-color-scheme: light) den entsprechenden Geräte-Einstellungen zugewiesen wird:

HTML

<!DOCTYPE html>
<html>
  <head>
    ....
    <meta name="color-scheme" content="dark light">
  </head>
  <body>
    ....
 </body>
</html>

CSS

/*light mode*/
@media (prefers-color-scheme: light) {
  /* CSS definitions*/
}

/*dark mode*/
@media (prefers-color-scheme: dark) {
  /* CSS definitions*/
}

Das Ganze funktioniert danach (entspr. Geräte, Betriebssysteme und Browser vorausgesetzt) automatisch und die Änderungen werden ohne Rrefresh der Seite beim Umschalten des Modus sofort wirksam (zumindest unter Win10, iOS 14):

In der Datei darkmode_device.html habe ich das mal zusammengeklickert. Einfach mal die Geräte-Enstellungen umschalten und testen.

2. Individuelle Benutzendeneinstellung

Geräteeinstellungen sind schön und gut, aber evtl. soll Seitenbesuchenden die Möglichkeit gegeben werden, individuelle Einstellungen unabhängig von denen auf Betriebssystem-Ebene vorzunehmen.

Das geht z.B. mit einem Schalter, den man in einem Menü oder den Einstellungen platziert. Dafür basteln wir uns z.B. einen Schalter mit dem Checkbox-Hack, mit dem man z.B. eine class von <body> manipuliert: Wenn (if) der Schalter :checked ist, der Dark Mode also momentan aktiviert ist, wird dieser .on('click') ausgeschaltet: Die Klasse dark wird entfernt. Wenn der Schalter zum Zeitpunkt des Anklickens dagegen nicht aktiviert ist (else-Variante), wird die Klasse hinzugefügt. Um den Schalter anzusprechen, nutze ich hier übrigens das <label for="darkmodeswitch">, da die eigentliche Checkbox ausgeblendet ist. Num Nachvollziehen bitte in die Beispieldatei unten schauen. Nicht übermäßig komplex, aber wirksam:

jquery

$(document).ready(function() {
   $('label[for="darkModeSwitch"]').on('click',function() {
      if ($('#darkModeSwitch').is(':checked')) {
         $('body').removeClass('dark');
      }else {
         $('body').addClass('dark');
      }
   });
});

Über diese Klasse kann mittels entspr. CSS dann ein Dark Mode umgesetzt werden:

CSS

/*light mode*/
body
   {background:#fff;color: #444;}
h1,h2,h3,h4,h5,h6
   {color:#bf304a;}

/*dark mode*/
body.dark
   {background: #222;color:#eee;}
.dark h1,.dark h2,.dark h3,.dark h4,.dark h5,.dark h6
   {color: #d1d2d3;}

Auch hier der Videobeweis, wie es aussieht (bzw. aussehen sollte...):

Das Skript, CSS und alles weitere (jQuery wird vom CDN geladen) gibts in darkmode_switch.html zum Herumspielen 🙂

3. Speicherung der Einstellung

Besonders cool wäre es ja, wenn man diese Einstellung irgendwie speichern könnte, damit bei wiederholtem Besuch die Einstellung nicht erneut vorgenommen werden muss. Neben "Datenbankfeld eines Benutzendenprofils" (aufwändig, für Webseiten ohne Login evtl. nicht realisierbar) oder der Speicherung in einem Cookie bietet der sog. 'Local Storage' (im Folgenden: localStorage, Infos hier: Wikipedia, MDN Docs) eine recht komfortable Möglichkeit, solche Informationen auf dem Benutzendengerät zu speichern. Darin lässt sich (hier mittels jQuery) recht komfortabel via localStorage.setItem('key','value'); ein "Schlüssel-Wert-Paar" speichern und der Wert mittels localStorage.getItem('key') wieder auslesen.

Das gesamte Vorgehen hat also zwei Bestandteile:

3.1 Wiederherstellung der Einstellung des letzten Seitenbesuchs

Beim Laden der Seite muss überprüft werden, ob es zum Schlüssel (hier: 'darkmode') einen (beliebigen) Wert im localStorage gibt. Wenn ja, wird in Abhängigkeit des Werts ('on' oder 'off') der Schalter eingestellt und die Klasse gesetzt, also der Zustand des vergangenen Seitenbesuchs wieder hergestellt. Wenn kein Wert für 'darkmode' existiert, wird der 'light' Mode verwedet:

jQuery

   // check if localStorage has a value
   if (localStorage.getItem('darkmode') != null) {
      // ... check if this value is 'on'
      if (localStorage.getItem('darkmode') == 'on') {
         // then set body class to 'dark' and button state to 'checked'
         $( '#darkModeSwitch' ).prop( 'checked', true );
         $('body').addClass('dark');
      // else (= storage value exists but must be 'off') ...
      } else {
         // remove body class 'dark' and set button state to 'unckecked'
         $( '#darkModeSwitch' ).prop( 'checked', false );
         $('body').removeClass('dark');
      }
   // in case localStorage is empty
   } else {
      // set button state tu 'unchecked', remove body class
      // and save setting in localStorage
      $( '#darkModeSwitch' ).prop( 'checked', false );
      $('body').removeClass('dark');
      localStorage.darkmode = "off";
   }

3.2 SchaLteroptionen

Neben dem Auslesen eventuell gespeicherter Werte aus dem localStorage muss natürlich trotzdem die Schalterfunktion umgesetzt werden: Diese muss nun nicht nur die Klasse ändern (wie bereits oben), sondern auch den localStorage (über-) schreiben. Das ist aber mit je einer zusätzlichen Zeile für die beiden Buttonstati einfach möglich:

jquery

$('label[for="darkModeSwitch"]').on('click',function() {
   if (localStorage.getItem('darkmode') == 'on') {
      $('body').removeClass('dark');
      localStorage.darkmode = 'off';
   } else {
      $('body').addClass('dark');
      localStorage.darkmode = 'on';
   }
});

Eine Umsetzung mit dem localStorage findet man in der Datei darkmode_localStorage.html. Modus umschalten, Seite schließen, erneut öffnen: Ta-dah! 🙂

Achtung: Verschiedene Browser bzw. Browsereinstellungen verhindern das Speichern von localStorage-Einträgen bzw. löschen diese beim Schließen des Browsers. Der "private Modus" von Safari gehört ebenso dazu wie "Firefox Klar" oder Einstellungen, alle Website-Daten beim Schließen eines Browsers zu löschen.

4. Kombination und Hierarchisierung

Wir haben jetzt also zwei Ansätze, nämlich (a) Gerätevoreinstellung und (b) Benutzendenpräferenz. Zweitere können wir mittels localStorage (teil-) persistent gestalten.

Um beide Ansätze miteinander zu kombinieren, muss man Überlegungen anstellen, welcher Einstellung mehr Gewicht beigemessen wird: Wenn z.B. ein/e wiederkehrende Benutzende/r mit einem localStorage-Eintrag (z.B. Dark Mode = on) mit einem Endgerät mit gegensätzlicher Voreinsteillung (Dark Mode = off) die Seite besucht - was soll dann gelten? Die Benutzendeneinstellung vom letzten Seitenbesuch oder die Geräteeinstellung?

Für mich ist die Betriebssystem-Voreinstellung nicht unernehblich, schließlich trifft der/die Benutzende damit eine generelle Voreinstellung, die für alle Apps und Webseiten (soweit unterstützt) gelten soll. Daher sollte die Überprüfung auf OS-Voreinstellung vor der Überprüfung des localStorage passieren. Meine Überlegungen hier (ja, ich weiß, dass man das anders machen kann 🙄 ):

Amateurhafte Skizze zwecks verschiedener Darkmode-Optionen, je nach Geräteeinstellung und vorherigen Seitenbesuchen


Das sieht in meinem Code nun so aus:

jQuery

$(document).ready(function() {
   // check device preference for dark mode:
   // if dark mode is active, set body class to 'dark',
   if (window.matchMedia("(prefers-color-scheme: dark)").matches) {
      $('body').addClass('dark');
      // set button state to 'checked' and set localStorage to 'on'
      $( '#darkModeSwitch' ).prop( 'checked', true );
      localStorage.darkmode = "on";
   //if no device-side dark mode is active ...
   } else {
      // ...check localStorage for a value from earlier visit. If storage has a value ...
      if (localStorage.getItem('darkmode') != null) {
         // ... check if this value is 'on'
         if (localStorage.getItem('darkmode') == 'on') {
         // then set body class to 'dark' and button state to 'checked'
            $( '#darkModeSwitch' ).prop( 'checked', true );
            $('body').addClass('dark');
         // else (means: storage value exists but must be 'off') ...
         } else {
            // remove body class 'dark' and set button state to 'unckecked'
            $( '#darkModeSwitch' ).prop( 'checked', false );
            $('body').removeClass('dark');
         }
      // if no device-side dark mode is active and localStorage is empty ...
      } else {
         // remove body class 'dark', set button state to 'unchecked' and set localStorage to 'off'
         $( '#darkModeSwitch' ).prop( 'checked', false );
         $('body').removeClass('dark');
         localStorage.darkmode = "off";
      }
   }

   // modify by switch: on click
   $('#darkModeSwitch').click(function() {
      //check if dark mode is inactive
      if (localStorage.getItem('darkmode') != 'on') {
         //set body class, button state, localStorage
         $('body').addClass('dark');
         $( '#darkModeSwitch' ).prop( 'checked', true );
         localStorage.darkmode = "on";
      // else (means: no dak mode is enabled)
      } else {
         //set body class, button state, localStorage
         $('body').removeClass('dark');
         $( '#darkModeSwitch' ).prop( 'checked', false);
         localStorage.darkmode = "off";
      }
   });
});

Das Ganze gibt es in der Datei darkmode.html als Gesamtkunstwerk zum Download.

Wie immer: Der Code geht sicherlich "schöner" und deckt nicht alle Eventualitäten ab. Auch der notwendige Refresh beim Umschalten des Gerätemodus ist nicht ganz so schön. Verbesserungsvorschläge gerne in die Kommentare.

Ansonsten viel Spaß beim Basteln 🙂

Der Artikel 'Dark Mode' für Webseiten von Martin Smaxwil (incl. der Videos) ist lizenziert unter einer Creative Commons Namensnennung 4.0 International Lizenz.

Der hier veröffentlichte Code ist natürlich frei verwendbar, also gemeinfrei, "public domain", CC0.
Über diese Lizenz hinausgehende Erlaubnisse können Sie unter https://medienmarmela.de/lizenzen erhalten.

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert.

Folgende HTML-Tags sind möglich:
<a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>

Zustimmung zur Datenverarbeitung