diff --git a/assets/js/navbar.js b/assets/js/navbar.js index e7e43cc8..2db93bbf 100644 --- a/assets/js/navbar.js +++ b/assets/js/navbar.js @@ -6,10 +6,6 @@ const colorsBG = ['body', 'secondary', 'tertiary'] let scrollPosition = 0 -function sleep(ms) { - return new Promise(resolve => setTimeout(resolve, ms)) -} - function getStyle(el, styleProp) { let y @@ -65,7 +61,10 @@ function getBackgroundColor (section) { // use body background when section background is undefined or transparent if (color === 'rgba(0, 0, 0, 0)' || color === 'transparent') { - color = window.getComputedStyle(document.body).getPropertyValue('background-color') + const style = window.getComputedStyle(document.body) + // custom properties are not animated, so this yields the target color of a color mode + // switch rather than the intermediate color the body is currently fading through + color = style.getPropertyValue('--bs-body-bg').trim() || style.getPropertyValue('background-color') } return color @@ -126,6 +125,18 @@ function parseRGB (color) { b: parseInt(match[3]) } } + + // custom properties such as --bs-body-bg resolve to a hex color + const hex = color.match(/^#([\da-f]{3}|[\da-f]{6})$/i) + if (hex) { + const digits = hex[1].length === 3 ? hex[1].replace(/./g, c => c + c) : hex[1] + return { + r: parseInt(digits.slice(0, 2), 16), + g: parseInt(digits.slice(2, 4), 16), + b: parseInt(digits.slice(4, 6), 16) + } + } + return null } @@ -167,7 +178,7 @@ function updateNavbar () { const targetTheme = defaultTheme ? defaultTheme : storedTheme if (targetTheme) { - navbar.setAttribute('data-bs-theme', defaultTheme) + navbar.setAttribute('data-bs-theme', targetTheme) } } } @@ -214,12 +225,9 @@ if (navbar !== null && togglers !== null) { attributeFilter: ['data-bs-theme'] } const Observer = new MutationObserver(() => { - if (fixed) { - // wait for the theme animation to finish - sleep(600).then(() => { - updateNavbar() - }) - } + // switch along with the body rather than after it; the colors sampled by updateNavbar + // come from custom properties, which hold their target value from the outset + fixed && updateNavbar() }) Observer.observe(html, config) diff --git a/assets/scss/common/_styles.scss b/assets/scss/common/_styles.scss index 299125e4..d73fecac 100644 --- a/assets/scss/common/_styles.scss +++ b/assets/scss/common/_styles.scss @@ -1,6 +1,6 @@ @if $enable-dark-mode { [data-bs-theme-animate="true"] body { - transition: background-color 0.5s, color 0.5s; + transition: $theme-mode-transition; } } diff --git a/assets/scss/common/_variables-dart.scss b/assets/scss/common/_variables-dart.scss index 149c3781..2ee5e584 100644 --- a/assets/scss/common/_variables-dart.scss +++ b/assets/scss/common/_variables-dart.scss @@ -51,6 +51,9 @@ $link-color-dark: mix(white, h.$primary, h.$dark-mode-tint) !d $primary-bg-subtle-dark: mix(black, h.$primary, h.$dark-mode-shade) !default; $primary-border-subtle-dark: mix(black, h.$primary, calc(h.$dark-mode-shade / 2)) !default; +// Shared by every element that repaints on a color mode switch, so they stay in sync. +$theme-mode-transition: background-color .5s ease-in-out, color .5s ease-in-out, border-color .5s ease-in-out !default; + $dropdown-transition: opacity .15s ease-in-out !default; $dropdown-horizontal-margin-top: calc((-1.5 * 1rem) - 2px); $dropdown-horizontal-padding-y: calc(1rem + 2px); diff --git a/assets/scss/common/_variables.scss b/assets/scss/common/_variables.scss index 75aad7a6..1830b6f0 100644 --- a/assets/scss/common/_variables.scss +++ b/assets/scss/common/_variables.scss @@ -45,6 +45,11 @@ $primary-bg-subtle-dark: mix(black, $primary, $dark-mode-shade) !defa $primary-border-subtle-dark: mix(black, $primary, $dark-mode-shade / 2) !default; // scss-docs-end color-mode +// scss-docs-start color-mode-transition +// Shared by every element that repaints on a color mode switch, so they stay in sync. +$theme-mode-transition: background-color .5s ease-in-out, color .5s ease-in-out, border-color .5s ease-in-out !default; +// scss-docs-end color-mode-transition + // scss-docs-start horizontal-nav $dropdown-transition: opacity .15s ease-in-out !default; $dropdown-horizontal-margin-top: calc((-1.5 * 1rem) - 2px); diff --git a/assets/scss/components/_navbar.scss b/assets/scss/components/_navbar.scss index 02a09c0b..d592ea65 100644 --- a/assets/scss/components/_navbar.scss +++ b/assets/scss/components/_navbar.scss @@ -85,7 +85,7 @@ @if $enable-dark-mode { [data-bs-theme-animate="true"] .navbar { - transition: 0.5s ease-in-out; + transition: $theme-mode-transition; } } @@ -102,7 +102,10 @@ .navbar[data-transparent="true"] { backdrop-filter: none; - transition: all 0.3s ease; + + // this rule would otherwise shadow the color mode transition, as it shares its + // specificity but comes later; keep the blur on its own, faster curve + transition: $theme-mode-transition, backdrop-filter 0.3s ease; &.navbar-scrolled { backdrop-filter: blur(10px);