Skip to content

Commit ee9afbd

Browse files
committed
Different strategy to enable dark mode
See the comment in custom-early-head for details.
1 parent 8ef21b6 commit ee9afbd

File tree

3 files changed

+83
-38
lines changed

3 files changed

+83
-38
lines changed

_includes/bt.html

-20
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,2 @@
1-
<!-- <!DOCTYPE html>
2-
<html lang="en"><head>
3-
<meta charset="utf-8">
4-
<meta http-equiv="X-UA-Compatible" content="IE=edge">
5-
<meta name="viewport" content="width=device-width, initial-scale=1">
6-
<title>Performance Matters | A blog about low-level software and hardware performance.</title>
7-
<link id="mainstyle" rel="stylesheet" href="/assets/css/dark.css">
8-
9-
<style>
10-
html {
11-
color: red;
12-
background: black;
13-
}
14-
</style>
15-
</head>
16-
<body>
17-
</body>
18-
19-
</html> -->
20-
211
<h1>{{ include.linkto }}</h1>
222
<a href="{{include.linkto}}">TO {{ include.linkto }}</a>

_includes/custom-head-early.html

+73-15
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,66 @@
1+
{% comment %}<!--
2+
So this is all a bit silly. Ultimately, what we want to do is load either
3+
light.css or dark.css depending on whether light or dark theme should
4+
be selected. We base that decision on the prefers-color-scheme media query
5+
by default, but also allow the user to override that in javascript. In
6+
principle we only want to load *one* of the two files, but that doesn't
7+
seem possible in light of browsers that don't support prefers-color-scheme
8+
at al: the will fail even a query like "not (prefers-color-scheme: dark)"
9+
because any query with unknown bits fails regardless of the "not".
10+
11+
In JavaScript, however, we can load just one file since we can implement
12+
the "not" logic outside of the query, and originally that's how it worked:
13+
load either light.css or dark.css in the JavaScript, while the <noscript>
14+
version had to load light unconditionally then dark conditioned on the query.
15+
16+
Futher, we need to load the stylesheet using document.write() in the JS,
17+
since using other DOM manipulatoin techniques like appendChild causes a
18+
flash of unstyled content while changing pages. document.write() avoids
19+
this: apparently CSS files injected that way are treated as render-blocking
20+
in the same as those which appear directetly in the HTML document.
21+
22+
Now for whatever reason, this original approach seemed to cause the google
23+
spider to fail to load the CSS, probably because of heuristics where it
24+
tries to avoid loading resoures that aren't critical to indexing. This in
25+
turn causes it to fail the mobile experience audit, since an unstyled page
26+
has tons of problems on mobile such as too-small or too-wide text.
27+
28+
So finally we converge on the current solution: we specify <link> elements
29+
in the HTML regardless of whether javascript is enabled, for the light
30+
and dark files, with the dark CSS conditioned on the media query.
31+
This just works for everyone who doesn't override the theme. Then we run
32+
the js which implements the override and if an override is present, a
33+
third <link id="override-style" is added which point to either the light
34+
or dark CSS as the case may be. This override is add using document.write()
35+
to avoid FOUC as discusssed above.
36+
37+
The settings page may need to add this override <link> dynamically if we
38+
switch from not using to using an override.
39+
40+
Finally, we apply the theme-color *after* the script element, using the
41+
media query. Although I don't think it's defined in the spec, theme color
42+
seems to work in the opposite way from CSS: the first applicable <meta>
43+
tag with theme-color defines the color. So we need to have a media query
44+
for both light and dark variants, and if the query fails because it's
45+
not supported, that's fine since those browsers aren't going to support
46+
theme-color anyway. If the user has overridden the theme, we emit a
47+
theme-color <meta> tag in the javascript without any media query: this
48+
one comes first so it "wins".
49+
50+
Ugh.
51+
-->{% endcomment %}
152
<meta name="color-scheme" content="light dark">
253
{%- capture lightcss -%}{{ '/assets/css/light.css' | relative_url }}{%- endcapture -%}
354
{%- capture darkcss -%}{{ '/assets/css/dark.css' | relative_url }}{%- endcapture %}
55+
{% assign dark-query = '(prefers-color-scheme: dark)' %}
56+
<link rel="stylesheet" href="{{ lightcss }}">
57+
<link rel="stylesheet" href="{{ darkcss }}" media="{{ dark-query }}">
458
<script>
5-
6-
var DARKMODE = (function() {
59+
var DARKMODE = (function() {
760
const i = {
861
PROP: 'force-color',
62+
OID: 'override-style',
63+
BANNER: '{{ site.darkmode.hide_banner }}',
964
getOverride: function () {
1065
try {
1166
return localStorage.getItem(i.PROP);
@@ -25,22 +80,25 @@
2580
gaTheme: function () {
2681
var o = i.getOverride();
2782
return o ? o + ' force' : i.get() + ' default';
83+
},
84+
makeLink: function (c) {
85+
e = document.createElement('link');
86+
e.id = i.OID;
87+
e.rel = 'stylesheet';
88+
e.href = (c === 'dark' ? '{{darkcss}}' : '{{lightcss}}');
89+
return e;
2890
}
29-
};
30-
return i;
91+
}
92+
return i;
3193
}());
3294

33-
if (DARKMODE.get() == 'dark') {
34-
var link = '<meta name="theme-color" content="#181818"><link id="mainstyle" rel="stylesheet" href="{{ darkcss }}">';
35-
} else {
36-
var link = '<meta name="theme-color" content="#fdfdfd"><link id="mainstyle" rel="stylesheet" href="{{ lightcss }}">';
37-
}
38-
document.write(link);
95+
if (DARKMODE.getOverride()) {
96+
var dm_color = DARKMODE.get();
97+
document.write(DARKMODE.makeLink(dm_color).outerHTML +
98+
'\n<meta name="theme-color" content="#' + (dm_color === 'dark' ? '181818' : 'fdfdfd') + '">');
99+
}
39100
</script>
40-
101+
<meta name="theme-color" content="#fdfdfd" media="not all and {{ dark-query }}">
102+
<meta name="theme-color" content="#181818" media="{{ dark-query }}">
41103
<script defer src="{{ '/assets/dark-mode.js' | relative_url }}"></script>
42104

43-
<noscript>
44-
<link rel="stylesheet" href="{{ lightcss }}">
45-
<link rel="stylesheet" href="{{ darkcss }}" media="(prefers-color-scheme: dark)">
46-
</noscript>

assets/dark-mode.js

+10-3
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,15 @@
55
(function(i) {
66

77
i.setSheet = function (color) {
8-
document.getElementById("mainstyle").setAttribute("href",
9-
"{{ '/assets/css/' | relative_url }}" + color + '.css');
8+
var existing = document.getElementById(i.OID);
9+
var link = i.makeLink(color);
10+
if (existing) {
11+
existing.href = link.href;
12+
} else {
13+
// element may not exist if there was no existing override when
14+
// the settings page was loaded so create it now
15+
document.head.appendChild(link);
16+
}
1017
}
1118

1219
/**
@@ -76,7 +83,7 @@
7683
// want the bar to suddenly disapear when the box is checked.
7784
// Finally, we don't show the bar if the user has
7885
// closed it this session, or twice ever (saved in localStorage)
79-
if ('{{ site.darkmode.hide_banner }}' === 'true') {
86+
if (i.BANNER === 'true') {
8087
return false;
8188
}
8289
if (!i.bothOk() || sessionStorage.getItem('dm-closed') || i.closeCount() >= 2) {

0 commit comments

Comments
 (0)