banner
Vinking

Vinking

你写下的每一个BUG 都是人类反抗被人工智能统治的一颗子弹

Upgrade! Refactor the light and dark mode switching feature

A long time ago, I wrote an article introducing the dark mode of a website.

At that time, I wanted to create a simple toggle feature for light and dark modes on the website. The idea was:

  • During the day (from 6 AM to 6 PM), use pure CSS @media (prefers-color-scheme: dark) to detect whether the user's device has enabled dark mode.
  • At night (from 6 PM to 6 AM the next day), load another set of CSS files for nighttime use.

It seemed intuitive, simple, and convenient, and it solved the long-standing problem of being blinded by bright web pages at night. Initially, the effect was quite good, but after a while, I discovered two serious issues:

  • The toggle feature for light and dark modes implemented with this method required maintaining two CSS files long-term, one for daytime and one for nighttime. This meant that every time a new feature needed to be added or an outdated style removed, both CSS files had to be updated simultaneously. The CDN cache also needed to refresh both files to see the complete effect. Every few months, I also had to compare the two files to check for any missing styles.
  • The dark mode at night could not switch back to the light mode during the day. If styles were modified at night, it required manually changing the <link> content in the console to reload the daytime CSS file.

So, I started upgrading the light and dark mode toggle feature these days.

Ver 0.1#

When I first rewrote the light and dark mode toggle feature, I focused on the issue of "the dark mode at night cannot switch back to the light mode during the day," so I thought of using localStorage to store the current page's light and dark mode status. Light, Dark, and System correspond to light mode, dark mode, and system-following mode, respectively. The first load uses the system-following mode, and it can switch from the system-following mode to light and dark modes. Then, it simply loads the light.css, dark.css, and style.css files.

<link rel="stylesheet" id="linkCSS"/>
<script>
if (localStorage.getItem("Lighting")) {
  if (localStorage.getItem("Lighting") === 'Light') document.getElementById('linkCSS').href = 'https://cdn.vinking.top/css/light.css';
  else if(localStorage.getItem("Lighting") === 'System') document.getElementById('linkCSS').href = 'https://cdn.vinking.top/css/style.css';
  else if(localStorage.getItem("Lighting") === 'Dark') document.getElementById('linkCSS').href = 'https://cdn.vinking.top/css/dark.css';
}else{
  localStorage.setItem("Lighting", "System");
  document.getElementById('linkCSS').href = 'https://cdn.vinking.top/css/style.css';
}
</script>

Place the above code in the <head> so that it executes before the page renders, loading the corresponding CSS file. The toggle button works similarly, switching modes by changing localStorage. If clicked while in system-following mode, it first uses window.matchMedia('(prefers-color-scheme: dark)').matches to detect whether the device is in dark mode or light mode before switching to the opposite mode.

During local testing, it felt okay, but when deployed on the server, issues arose:

  • The first issue was still the need to maintain multiple CSS files long-term. This method even required one more CSS file than the initial method, making it painful to add button styles later.
  • I did not consider the priority issue when loading resources; CSS has the highest priority, and script has high priority. This caused the page's other CSS resources to load completely before executing the code inside <script>, which then began loading the critical CSS file. With the loading time of CSS resources, there would be a moment when the corresponding mode's CSS file was not loaded, significantly affecting the user experience.

After some modifications, the next version emerged.

Ver 0.6#

It is important to know that the differences between light.css, dark.css, and style.css lie in the different values of the :root selector variables. Since this is the case, I can place the :root content of the three files in the same file in a specific order. Different modes only need to add the corresponding class to <html> to load. Based on the principle that styles later in the CSS will override earlier styles, I can write this order.

:root{
  ...
}
@media (prefers-color-scheme:dark){:root{
  ...
}}
.Light{
  ...
}
.Dark{
  ...
}

When first loading in system-following mode, <html> does not add any class, allowing the use of the :root selector and the @media (prefers-color-scheme:dark) content when the device is in dark mode. When switching to dark mode or light mode, the class Dark or Light is added. At this point, the later .Dark/.Light styles can override the earlier :root styles.

At the same time, in the <head>, I can directly reference the corresponding CSS file without needing to use JS for judgment. Additionally, when opening the performance tab in the developer tools, I can see that when executing the code inside <script>, the Timings have not yet reached the First Paint (FP) timeline. Therefore, when the page first paints, it will directly use the styles of the corresponding mode without any style missing issues.

<link rel="stylesheet" href='https://cdn.vinking.top/css/style.css'/>
<script>
if (localStorage.getItem("Lighting")) {
  if (localStorage.getItem("Lighting") === 'Light') document.getElementsByTagName('html')[0].classList.add("Light");
  else if(localStorage.getItem("Lighting") === 'Dark') document.getElementsByTagName('html')[0].classList.add("Dark");
}else{
  localStorage.setItem("Lighting", "System");
}
</script>

Ver 1.0#

In actual use, there was an issue where clicking the toggle mode button resulted in a temporary background absence due to incomplete loading. This issue can be resolved using a preloading method by giving the button an onmouseover event, adding <link rel="prefetch" href=""> to achieve resource preloading when the mouse hovers over it. Finally, remember to add XXX.onmouseover = null to avoid repeated triggers.

Thus, the main framework for the light and dark mode toggle feature has been completed, and only minor issues like loading new code highlighting during the switch remain to be fixed.

Ps. A little complaint, why are beautiful horizontal images on Pixiv becoming harder to find? All the recommendations are vertical images...

This article is synchronized and updated to xLog by Mix Space. The original link is https://www.vinking.top/posts/codes/dark-mode-css-implementation

Loading...
Ownership of this post data is guaranteed by blockchain and smart contracts to the creator alone.