1
mirror of https://github.com/jakejarvis/jarv.is.git synced 2025-04-26 21:08:26 -04:00

move light/dark class from <body> to <html> and inline restoration script to (hopefully) improve the white flashes

This commit is contained in:
Jake Jarvis 2021-12-15 13:49:56 -05:00
parent c2789e24d4
commit 766362a9da
Signed by: jake
GPG Key ID: 2B0C9CF251E69A39
14 changed files with 61 additions and 67 deletions

View File

@ -0,0 +1,21 @@
/* eslint-disable no-var */
// A super tiny script to restore dark mode off the bat (to hopefully avoid blinding flashes of white).
// NOTE: This is inlined by Hugo/esbuild (see layouts/partials/head/restore-theme.html) instead of Webpack.
import { getDarkPref } from "./src/utils/theme.js";
try {
var cl = document.documentElement.classList;
var pref = getDarkPref();
// set `<html class="dark">` if either the user has explicitly toggled in the past or if their OS is in dark mode
if (pref === "true" || (!pref && window.matchMedia("(prefers-color-scheme: dark)").matches)) {
cl.remove("light");
cl.add("dark");
}
// TODO: fix real-time switching (works but bulb icon isn't updated)
// window.matchMedia("(prefers-color-scheme: dark)").addEventListener("change", (e) => e.matches && setDark(true));
// window.matchMedia("(prefers-color-scheme: light)").addEventListener("change", (e) => e.matches && setDark(false));
} catch (error) {}

View File

@ -1,6 +1,6 @@
import { h } from "preact"; import { h } from "preact";
import { useState, useEffect } from "preact/hooks"; import { useState, useEffect } from "preact/hooks";
import { isDark, setDarkClass, setDarkPref } from "../utils/theme.js"; import { isDark, setDarkPref } from "../utils/theme.js";
// react components: // react components:
import BulbOn from "../assets/bulb-on.svg"; import BulbOn from "../assets/bulb-on.svg";
@ -11,7 +11,9 @@ const ThemeToggle = () => {
const [dark, setDark] = useState(isDark()); const [dark, setDark] = useState(isDark());
useEffect(() => { useEffect(() => {
return setDarkClass(dark); // sets appropriate `<html class="...">`
document.documentElement.classList.remove("dark", "light");
document.documentElement.classList.add(dark ? "dark" : "light");
}, [dark]); }, [dark]);
const handleToggle = () => { const handleToggle = () => {

View File

@ -1,28 +1,8 @@
import { h, render } from "preact"; import { h, render } from "preact";
import { getDarkPref, setDarkClass } from "./utils/theme.js";
// react components: // react components:
import ThemeToggle from "./components/ThemeToggle.js"; import ThemeToggle from "./components/ThemeToggle.js";
// check for existing preference in local storage
const pref = getDarkPref();
// do initialization before *any* react-related stuff to avoid white flashes as much as possible
if (pref) {
// restore user's preference if they've explicitly toggled it in the past
setDarkClass(pref === "true");
} else {
// check for OS dark mode preference and switch accordingly
// https://drafts.csswg.org/mediaqueries-5/#prefers-color-scheme
try {
setDarkClass(window.matchMedia("(prefers-color-scheme: dark)").matches);
} catch (e) {}
// TODO: fix real-time switching (works but bulb icon isn't updated)
// window.matchMedia("(prefers-color-scheme: dark)").addEventListener("change", (e) => e.matches && setDark(true));
// window.matchMedia("(prefers-color-scheme: light)").addEventListener("change", (e) => e.matches && setDark(false));
}
// finally render the nifty lightbulb in the header // finally render the nifty lightbulb in the header
if (typeof window !== "undefined" && document.querySelector(".theme-toggle")) { if (typeof window !== "undefined" && document.querySelector(".theme-toggle")) {
render(<ThemeToggle />, document.querySelector(".theme-toggle")); render(<ThemeToggle />, document.querySelector(".theme-toggle"));

View File

@ -3,17 +3,6 @@ const storageKey = "dark_mode";
export const getDarkPref = () => localStorage.getItem(storageKey); export const getDarkPref = () => localStorage.getItem(storageKey);
export const setDarkPref = (pref) => localStorage.setItem(storageKey, pref); export const setDarkPref = (pref) => localStorage.setItem(storageKey, pref);
// use the body class as a hint to what the theme was set to outside of the button component // use the `<html class="...">` as a hint to what the theme was set to outside of the button component
// there's probably (definitely) a cleaner way to do this..? // there's probably (definitely) a cleaner way to do this..?
export const isDark = () => document.body.classList?.contains("dark"); export const isDark = () => document.documentElement.classList.contains("dark");
// sets appropriate `<body class="...">`
export const setDarkClass = (dark) => {
if (dark) {
document.body.classList.add("dark");
document.body.classList.remove("light");
} else {
document.body.classList.add("light");
document.body.classList.remove("dark");
}
};

View File

@ -1,25 +1,18 @@
@use "sass:map"; @use "sass:map";
// Takes a map of CSS properties and theme keys (see below) and set both body.light and body.dark selectors. // Takes a map of CSS properties and theme keys (see below) and set both html.light and html.dark selectors.
// ex. @include themes.themed((color: "text", background-color: "background-inner")); // ex. @include themes.themed((color: "text", background-color: "background-inner"));
@mixin themed($properties) { @mixin themed($properties) {
// keep track of the original selector(s) calling this mixin for below // keep track of the original selector(s) calling this mixin for below
$selectors: #{&}; $selectors: #{&};
// add corresponding body.light and body.dark root selectors // add corresponding html.light and html.dark root selectors
@each $theme, $map in $themes { @each $theme, $map in $themes {
@at-root body.#{$theme} { @at-root html.#{$theme} {
// support theming root body element #{$selectors} {
@if $selectors == "body" {
@each $property, $color in $properties { @each $property, $color in $properties {
#{$property}: map.get($map, $color); #{$property}: map.get($map, $color);
} }
} @else {
#{$selectors} {
@each $property, $color in $properties {
#{$property}: map.get($map, $color);
}
}
} }
} }
} }

View File

@ -100,7 +100,7 @@ main {
// cool link underlines via messy SCSS hacking (see ../abstracts/_functions) // cool link underlines via messy SCSS hacking (see ../abstracts/_functions)
@each $theme, $map in themes.$themes { @each $theme, $map in themes.$themes {
@at-root body.#{$theme} #{&} { @at-root html.#{$theme} #{&} {
background-image: functions.underline-hack(map.get($map, "links"), map.get($map, "background-inner")); background-image: functions.underline-hack(map.get($map, "links"), map.get($map, "background-inner"));
} }
} }

View File

@ -110,23 +110,25 @@ header {
} }
// Dark mode toggle // Dark mode toggle
&.theme-toggle button { &.theme-toggle {
border: 0; margin-left: 1.4em;
padding: 0;
background: none;
margin: 0.3em -0.3em 0 1.4em;
cursor: pointer;
svg { button {
width: 1.56em; // 24.33px, don't ask border: 0;
height: 1.56em; padding: 0;
background: none;
margin: 0.3em -0.3em 0 0;
cursor: pointer;
svg {
width: 1.56em; // 24.33px, don't ask
height: 1.56em;
}
} }
} }
// no margin on the first child to make more room on narrow windows (before responsiveness kicks in). // no margin on the first child to make more room on narrow windows (before responsiveness kicks in)
// last child is the dark mode toggle -- margin is set directly on it in case it's hidden (if JS is disabled). &:first-child {
&:first-child,
&:last-child {
margin-left: 0; margin-left: 0;
} }
} }

View File

@ -129,7 +129,7 @@ div.highlight {
} }
// Syntax Highlighting (light) - modified from Monokai Light: https://github.com/mlgill/pygments-style-monokailight // Syntax Highlighting (light) - modified from Monokai Light: https://github.com/mlgill/pygments-style-monokailight
body.light { html.light {
div.highlight, div.highlight,
button.copy-button, button.copy-button,
:not(pre) > code { :not(pre) > code {
@ -215,7 +215,7 @@ body.light {
} }
// Syntax Highlighting (dark) - modified from Dracula: https://github.com/dracula/pygments // Syntax Highlighting (dark) - modified from Dracula: https://github.com/dracula/pygments
body.dark { html.dark {
div.highlight, div.highlight,
button.copy-button, button.copy-button,
:not(pre) > code { :not(pre) > code {

View File

@ -200,7 +200,7 @@ div.layout-home {
// Loop through $colors-home (see above) // Loop through $colors-home (see above)
@each $id, $colors in $colors-home { @each $id, $colors in $colors-home {
@each $theme, $color in $colors { @each $theme, $color in $colors {
@at-root body.#{$theme} div.layout-home a##{$id} { @at-root html.#{$theme} div.layout-home a##{$id} {
color: $color; color: $color;
background-image: functions.underline-hack($color, map.get(map.get(themes.$themes, $theme), "background-inner")); background-image: functions.underline-hack($color, map.get(map.get(themes.$themes, $theme), "background-inner"));
} }

View File

@ -17,7 +17,7 @@ css: |
div#content a#octocat svg { div#content a#octocat svg {
vertical-align: text-bottom; vertical-align: text-bottom;
} }
body.dark div#content a#octocat svg path { html.dark div#content a#octocat svg path {
fill: #d3d3d3; fill: #d3d3d3;
} }
draft: false draft: false

View File

@ -72,7 +72,7 @@ function optimizeHtml() {
collapseBooleanAttributes: true, collapseBooleanAttributes: true,
removeComments: true, removeComments: true,
minifyCSS: true, minifyCSS: true,
minifyJS: false, minifyJS: true,
}) })
) )
.pipe(gulp.dest(".", { overwrite: true })); .pipe(gulp.dest(".", { overwrite: true }));

View File

@ -7,11 +7,11 @@
{{- partial "functions/prepare-js" . -}} {{- partial "functions/prepare-js" . -}}
{{- partial "functions/prepare-css" . -}} {{- partial "functions/prepare-css" . -}}
<html lang="{{ .Site.LanguageCode | default "en" }}"> <html lang="{{ .Site.LanguageCode | default "en" }}" class="{{ .Site.Params.Theme.defaultTheme }}">
<head> <head>
{{ partial "head/_head" . }} {{ partial "head/_head" . }}
</head> </head>
<body class="{{ .Site.Params.Theme.defaultTheme }}"> <body>
{{ partialCached "page/header" . }} {{ partialCached "page/header" . }}
<main> <main>
{{ block "main" . }}{{ end }} {{ block "main" . }}{{ end }}

View File

@ -1,6 +1,7 @@
{{ partial "head/meta" . -}} {{ partial "head/meta" . -}}
{{ partial "head/open-graph" . -}} {{ partial "head/open-graph" . -}}
{{ partial "head/canonical" . -}} {{ partial "head/canonical" . -}}
{{ partialCached "head/restore-theme" . -}}
{{ partialCached "head/preload" . -}} {{ partialCached "head/preload" . -}}
{{ partial "head/styles" . -}} {{ partial "head/styles" . -}}
{{ partialCached "head/favicons" . -}} {{ partialCached "head/favicons" . -}}

View File

@ -0,0 +1,6 @@
{{ $themeScript := resources.Get "js/restore-theme.js" | js.Build (dict "target" "es2015" "format" "iife") }}
{{ with $themeScript }}
<script>
{{ .Content | safeJS }}
</script>
{{ end }}