Merge pull request #1607 from gethinode/develop

feat: enable transparent navbar
This commit is contained in:
Mark Dumay
2025-10-04 10:38:21 +02:00
committed by GitHub
10 changed files with 315 additions and 80 deletions

View File

@@ -38,10 +38,11 @@
setLocalStorage('theme', theme, 'functional')
if (theme === 'auto') {
document.documentElement.setAttribute('data-bs-theme', (getPreferredTheme()))
} else {
document.documentElement.setAttribute('data-bs-theme', theme)
theme = getPreferredTheme()
}
document.documentElement.setAttribute('data-bs-theme', theme)
// store main theme separately, to avoid the navbar mode icon uses a local variable
document.documentElement.setAttribute('data-bs-main-theme', theme)
updateSelectors()
}

View File

@@ -4,24 +4,161 @@ const togglers = document.querySelectorAll('.main-nav-toggler')
const modeSelectors = document.querySelectorAll('.switch-mode-collapsed')
const colorsBG = ['body', 'secondary', 'tertiary']
function updateNavbar () {
let storedTheme
if (typeof getLocalStorage === "function") {
storedTheme = getLocalStorage('theme', null, 'functional')
function sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms))
}
function getStyle(el, styleProp) {
let y
if (window.getComputedStyle) {
y = document.defaultView.getComputedStyle(el).getPropertyValue(styleProp)
} else if (el.currentStyle) {
y = el.currentStyle[styleProp]
}
return y
}
function updateNavbarColor () {
const scrollTop = window.pageYOffset
const scrollBottom = scrollTop + navbar.offsetHeight
// find which section is currently under the navbar
let currentSection = null
const sections = document.querySelectorAll('article,section,footer')
let currentIndex = -1
sections.forEach(section => {
const rect = section.getBoundingClientRect()
const sectionTop = scrollTop + rect.top
const sectionBottom = sectionTop + section.offsetHeight - 1
// check if navbar overlaps with this section
if (scrollTop <= sectionBottom && scrollBottom >= sectionTop) {
let index = getStyle(section, 'z-index')
if (index === 'auto') {
index = 1
}
if (index > currentIndex) {
currentSection = section
currentIndex = index
}
}
})
// use main part as backup (defined in baseof.html template)
if (!currentSection) {
currentSection = document.querySelector('main')
}
if (window.scrollY > 75) {
navbar.classList.add('nav-active')
if (storedTheme) {
navbar.setAttribute('data-bs-theme', storedTheme)
if (currentSection) {
adaptToSection(currentSection)
}
}
function getBackgroundColor (section) {
// get computed background color of the section
let color = window.getComputedStyle(section).backgroundColor
// 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')
}
return color
}
function adaptToSection (section) {
// retrieve the section background color, using body color as fallback
const color = getBackgroundColor(section)
// determine if the background is light or dark
const isLightBackground = isLightColor(section, color)
// set appropriate mode class
const nav = document.querySelector('.navbar')
if (isLightBackground) {
if (navbar.dataset.bsTheme !== 'light') {
navbar.dataset.bsTheme = 'light'
}
} else {
navbar.classList.remove('nav-active')
const defaultTheme = navbar.getAttribute('data-bs-overlay')
if (navbar.dataset.bsTheme !== 'dark') {
navbar.dataset.bsTheme = 'dark'
}
}
const targetTheme = defaultTheme ? defaultTheme : storedTheme
if (targetTheme) {
navbar.setAttribute('data-bs-theme', defaultTheme)
// update semi-transparent background color of navbar
const rgb = parseRGB(color)
if (rgb) {
navbar.style.backgroundColor = `rgba(${rgb.r},${rgb.g},${rgb.b},.4)`
}
}
function isLightColor (section, color) {
if (section.dataset.bsTheme === 'light') {
return true
}
if (section.dataset.bsTheme === 'dark') {
return false
}
// parse RGB color of the section backgroiund
const rgb = parseRGB(color)
if (!rgb) return true // Default to light if can't parse
// calculate relative luminance
const luminance = calculateLuminance(rgb.r, rgb.g, rgb.b)
// return true if light (luminance > 0.5)
return luminance > 0.5
}
function parseRGB (color) {
const match = color.match(/rgba?\((\d+),\s*(\d+),\s*(\d+)/)
if (match) {
return {
r: parseInt(match[1]),
g: parseInt(match[2]),
b: parseInt(match[3])
}
}
return null
}
function calculateLuminance (r, g, b) {
// convert RGB to relative luminance using sRGB formula
const [rs, gs, bs] = [r, g, b].map(c => {
c = c / 255
return c <= 0.03928 ? c / 12.92 : Math.pow((c + 0.055) / 1.055, 2.4)
})
return 0.2126 * rs + 0.7152 * gs + 0.0722 * bs
}
function updateNavbar () {
if (navbar.dataset.transparent) {
updateNavbarColor()
} else {
let storedTheme
if (typeof getLocalStorage === "function") {
storedTheme = getLocalStorage('theme', null, 'functional')
}
if (window.scrollY > 75) {
navbar.classList.add('nav-active')
if (storedTheme) {
navbar.setAttribute('data-bs-theme', storedTheme)
}
} else {
navbar.classList.remove('nav-active')
const defaultTheme = navbar.getAttribute('data-bs-overlay')
const targetTheme = defaultTheme ? defaultTheme : storedTheme
if (targetTheme) {
navbar.setAttribute('data-bs-theme', defaultTheme)
}
}
}
}
@@ -33,28 +170,35 @@ if ((navbar !== null) && (window.performance.getEntriesByType)) {
}
if (navbar !== null && togglers !== null) {
// initialize and update the navbar on load, on resize, and on scroll
document.addEventListener('DOMContentLoaded', () => { fixed && updateNavbar() })
document.addEventListener('resize', () => fixed && updateNavbar())
document.addEventListener('scroll', () => fixed && updateNavbar())
// observe state changes to the site's color mode
const html = document.querySelector('html')
const config = {
attributes: true,
attributeFilter: ['data-bs-theme']
}
const Observer = new MutationObserver((mutationrecords) => {
fixed && updateNavbar()
const Observer = new MutationObserver(() => {
if (fixed) {
// wait for the theme animation to finish
sleep(600).then(() => {
updateNavbar()
})
}
})
Observer.observe(html, config)
// initialize background color
const color = (navbar.getAttribute('data-navbar-color') || 'body')
const bg = colorsBG.includes(color) ? `var(--bs-${color}-bg)` : `var(--bs-navbar-color-${color})`
navbar.style.setProperty('--bs-navbar-expanded-color', bg)
// set the navbar background color to opaque when scrolling past a breakpoint
window.onscroll = () => {
fixed && updateNavbar()
if (!navbar.dataset.transparent) {
const color = (navbar.getAttribute('data-navbar-color') || 'body')
const bg = colorsBG.includes(color) ? `var(--bs-${color}-bg)` : `var(--bs-navbar-color-${color})`
navbar.style.setProperty('--bs-navbar-expanded-color', bg)
}
// set the navbar background color to opaque when expanded
// update the navbar background color when expanded
for (let i = 0; i < togglers.length; ++i) {
togglers[i].onclick = () => {
navbar.classList.toggle('navbar-expanded')

View File

@@ -95,6 +95,11 @@
}
}
.navbar[data-transparent="true"] {
backdrop-filter: blur(10px);
transition: all 0.3s ease;
}
.nav-active, .navbar-expanded {
background-color: var(--bs-navbar-expanded-color);
border-bottom: 1px solid var(--bs-secondary-bg);
@@ -393,6 +398,30 @@
border: 1px solid var(--bs-border-color) !important;
}
.d-none-main-light, .d-none-inline-main-light {
display: none !important;
}
.d-none-main-dark {
display: block !important;
}
.d-none-inline-main-dark {
display: inline !important;
}
[data-bs-main-theme="dark"] .d-none-main-light {
display: block !important;
}
[data-bs-main-theme="dark"] .d-none-inline-main-light {
display: inline !important;
}
[data-bs-main-theme="dark"] .d-none-main-dark, [data-bs-main-theme="dark"] .d-none-inline-main-dark {
display: none !important;
}
.inline-menu li {
display: inline-block;
padding: 0.5rem;

View File

@@ -96,7 +96,7 @@
overlay = false
overlayMode = "dark"
horizontal = false
offset = "5.5rem"
offset = "5.7rem"
breadcrumb = true
toc = true
sidebar = true

View File

@@ -23,7 +23,6 @@ arguments:
comment: Name of the menu configuration.
breakpoint:
release: v1.0.0
style:
type: select
optional: true
@@ -98,6 +97,17 @@ arguments:
navbar searches for images having a matching color-mode suffix
such as `-light` or `-dark`.
release: v1.15.0
transparent:
type: bool
default: false
optional: true
comment: >-
Flag indicating the navbar should be transparent. When set, the navbar
uses the current section's background color and applies a semi-
transparent blur filter. The site's body color is used as fallback. If
set, the color mode of the current section is used too. When no mode is
set, the navbar applies a mode with the best contrast.
release: v1.19.0
# deprecated arguments
size:
type: select

View File

@@ -48,9 +48,10 @@
fixed = true
overlay = false
overlayMode = "dark"
transparent = false
horizontal = false
offset = "5.5rem"
offsetXS = "5.5rem"
offset = "5.7rem"
offsetXS = "5.7rem"
breadcrumb = true
toc = true
sidebar = true

View File

@@ -29,6 +29,7 @@
"label",
"li",
"link",
"main",
"mark",
"math",
"meta",
@@ -212,6 +213,8 @@
"d-none",
"d-none-dark",
"d-none-light",
"d-none-main-dark",
"d-none-main-light",
"d-sm-block",
"d-sm-none",
"data-table",
@@ -676,11 +679,16 @@
"docs",
"documentation",
"dropdown-nav-0",
"dropdown-panel-1bc770c2a4522018a5d28ed335c8c66a",
"dropdown-panel-478805c6da766d19cb8529367880b5a0",
"dropdown-panel-6057bbdbb0f1e92046ca4cc546207938",
"dropdown-panel-7e3455997fcf483934b47b18978b5d36",
"dropdown-panel-dfd961725b28810e82ac3e47be3eb004",
"dropdown-panel-2b8de01298f17bfa4ef2f41a8b2c6435",
"dropdown-panel-2e5b2c8f1152552a22527c49478174ee",
"dropdown-panel-3a79089ba962a873cbebd26623a0ad40",
"dropdown-panel-3dd6f56b00612d3de3b125c1c360f592",
"dropdown-panel-a0bd455780b20f1f2bfda0050bfd2392",
"dropdown-panel-abfe30d02c0a523c8ec3648215b114b7",
"dropdown-panel-bbe2b50a448d584df2e182f25830b5af",
"dropdown-panel-cc10046cc5429efe0f01f5384daf9c94",
"dropdown-panel-f2d8c9c259691049ab9c2907caab7578",
"dropdown-panel-fc14dc77778d6aa0862cf6dc8cbfbd1d",
"eerste-artikel",
"elements-type",
"entity-relationship-diagram",
@@ -701,11 +709,16 @@
"fab-whatsapp",
"fab-x-twitter",
"faq",
"faq-72146768cde859a2886890cd67da8cc3",
"faq-72146768cde859a2886890cd67da8cc3-heading-faq-72146768cde859a2886890cd67da8cc3",
"faq-72146768cde859a2886890cd67da8cc3-item-0",
"faq-72146768cde859a2886890cd67da8cc3-item-1",
"faq-72146768cde859a2886890cd67da8cc3-item-2",
"faq-95cc6eedb4627fb738a7de6f99d7ad1b",
"faq-95cc6eedb4627fb738a7de6f99d7ad1b-heading-faq-95cc6eedb4627fb738a7de6f99d7ad1b",
"faq-95cc6eedb4627fb738a7de6f99d7ad1b-item-0",
"faq-95cc6eedb4627fb738a7de6f99d7ad1b-item-1",
"faq-95cc6eedb4627fb738a7de6f99d7ad1b-item-2",
"faq-c581a6286bf17cf9854fa7889afec9e6",
"faq-c581a6286bf17cf9854fa7889afec9e6-heading-faq-c581a6286bf17cf9854fa7889afec9e6",
"faq-c581a6286bf17cf9854fa7889afec9e6-item-0",
"faq-c581a6286bf17cf9854fa7889afec9e6-item-1",
"faq-c581a6286bf17cf9854fa7889afec9e6-item-2",
"fas-1",
"fas-2",
"fas-3",
@@ -801,11 +814,16 @@
"nav-0-btn-1",
"nav-0-btn-2",
"nav-nav-0",
"nav-panel-1bc770c2a4522018a5d28ed335c8c66a",
"nav-panel-478805c6da766d19cb8529367880b5a0",
"nav-panel-6057bbdbb0f1e92046ca4cc546207938",
"nav-panel-7e3455997fcf483934b47b18978b5d36",
"nav-panel-dfd961725b28810e82ac3e47be3eb004",
"nav-panel-2b8de01298f17bfa4ef2f41a8b2c6435",
"nav-panel-2e5b2c8f1152552a22527c49478174ee",
"nav-panel-3a79089ba962a873cbebd26623a0ad40",
"nav-panel-3dd6f56b00612d3de3b125c1c360f592",
"nav-panel-a0bd455780b20f1f2bfda0050bfd2392",
"nav-panel-abfe30d02c0a523c8ec3648215b114b7",
"nav-panel-bbe2b50a448d584df2e182f25830b5af",
"nav-panel-cc10046cc5429efe0f01f5384daf9c94",
"nav-panel-f2d8c9c259691049ab9c2907caab7578",
"nav-panel-fc14dc77778d6aa0862cf6dc8cbfbd1d",
"navbar",
"navbar-0-collapse",
"navbar-mode",
@@ -815,36 +833,66 @@
"notification",
"over-mij",
"overview",
"panel-1bc770c2a4522018a5d28ed335c8c66a-0",
"panel-1bc770c2a4522018a5d28ed335c8c66a-1",
"panel-1bc770c2a4522018a5d28ed335c8c66a-2",
"panel-1bc770c2a4522018a5d28ed335c8c66a-btn-0",
"panel-1bc770c2a4522018a5d28ed335c8c66a-btn-1",
"panel-1bc770c2a4522018a5d28ed335c8c66a-btn-2",
"panel-478805c6da766d19cb8529367880b5a0-0",
"panel-478805c6da766d19cb8529367880b5a0-1",
"panel-478805c6da766d19cb8529367880b5a0-2",
"panel-478805c6da766d19cb8529367880b5a0-btn-0",
"panel-478805c6da766d19cb8529367880b5a0-btn-1",
"panel-478805c6da766d19cb8529367880b5a0-btn-2",
"panel-6057bbdbb0f1e92046ca4cc546207938-0",
"panel-6057bbdbb0f1e92046ca4cc546207938-1",
"panel-6057bbdbb0f1e92046ca4cc546207938-2",
"panel-6057bbdbb0f1e92046ca4cc546207938-btn-0",
"panel-6057bbdbb0f1e92046ca4cc546207938-btn-1",
"panel-6057bbdbb0f1e92046ca4cc546207938-btn-2",
"panel-7e3455997fcf483934b47b18978b5d36-0",
"panel-7e3455997fcf483934b47b18978b5d36-1",
"panel-7e3455997fcf483934b47b18978b5d36-2",
"panel-7e3455997fcf483934b47b18978b5d36-btn-0",
"panel-7e3455997fcf483934b47b18978b5d36-btn-1",
"panel-7e3455997fcf483934b47b18978b5d36-btn-2",
"panel-dfd961725b28810e82ac3e47be3eb004-0",
"panel-dfd961725b28810e82ac3e47be3eb004-1",
"panel-dfd961725b28810e82ac3e47be3eb004-2",
"panel-dfd961725b28810e82ac3e47be3eb004-btn-0",
"panel-dfd961725b28810e82ac3e47be3eb004-btn-1",
"panel-dfd961725b28810e82ac3e47be3eb004-btn-2",
"panel-2b8de01298f17bfa4ef2f41a8b2c6435-0",
"panel-2b8de01298f17bfa4ef2f41a8b2c6435-1",
"panel-2b8de01298f17bfa4ef2f41a8b2c6435-2",
"panel-2b8de01298f17bfa4ef2f41a8b2c6435-btn-0",
"panel-2b8de01298f17bfa4ef2f41a8b2c6435-btn-1",
"panel-2b8de01298f17bfa4ef2f41a8b2c6435-btn-2",
"panel-2e5b2c8f1152552a22527c49478174ee-0",
"panel-2e5b2c8f1152552a22527c49478174ee-1",
"panel-2e5b2c8f1152552a22527c49478174ee-2",
"panel-2e5b2c8f1152552a22527c49478174ee-btn-0",
"panel-2e5b2c8f1152552a22527c49478174ee-btn-1",
"panel-2e5b2c8f1152552a22527c49478174ee-btn-2",
"panel-3a79089ba962a873cbebd26623a0ad40-0",
"panel-3a79089ba962a873cbebd26623a0ad40-1",
"panel-3a79089ba962a873cbebd26623a0ad40-2",
"panel-3a79089ba962a873cbebd26623a0ad40-btn-0",
"panel-3a79089ba962a873cbebd26623a0ad40-btn-1",
"panel-3a79089ba962a873cbebd26623a0ad40-btn-2",
"panel-3dd6f56b00612d3de3b125c1c360f592-0",
"panel-3dd6f56b00612d3de3b125c1c360f592-1",
"panel-3dd6f56b00612d3de3b125c1c360f592-2",
"panel-3dd6f56b00612d3de3b125c1c360f592-btn-0",
"panel-3dd6f56b00612d3de3b125c1c360f592-btn-1",
"panel-3dd6f56b00612d3de3b125c1c360f592-btn-2",
"panel-a0bd455780b20f1f2bfda0050bfd2392-0",
"panel-a0bd455780b20f1f2bfda0050bfd2392-1",
"panel-a0bd455780b20f1f2bfda0050bfd2392-2",
"panel-a0bd455780b20f1f2bfda0050bfd2392-btn-0",
"panel-a0bd455780b20f1f2bfda0050bfd2392-btn-1",
"panel-a0bd455780b20f1f2bfda0050bfd2392-btn-2",
"panel-abfe30d02c0a523c8ec3648215b114b7-0",
"panel-abfe30d02c0a523c8ec3648215b114b7-1",
"panel-abfe30d02c0a523c8ec3648215b114b7-2",
"panel-abfe30d02c0a523c8ec3648215b114b7-btn-0",
"panel-abfe30d02c0a523c8ec3648215b114b7-btn-1",
"panel-abfe30d02c0a523c8ec3648215b114b7-btn-2",
"panel-bbe2b50a448d584df2e182f25830b5af-0",
"panel-bbe2b50a448d584df2e182f25830b5af-1",
"panel-bbe2b50a448d584df2e182f25830b5af-2",
"panel-bbe2b50a448d584df2e182f25830b5af-btn-0",
"panel-bbe2b50a448d584df2e182f25830b5af-btn-1",
"panel-bbe2b50a448d584df2e182f25830b5af-btn-2",
"panel-cc10046cc5429efe0f01f5384daf9c94-0",
"panel-cc10046cc5429efe0f01f5384daf9c94-1",
"panel-cc10046cc5429efe0f01f5384daf9c94-2",
"panel-cc10046cc5429efe0f01f5384daf9c94-btn-0",
"panel-cc10046cc5429efe0f01f5384daf9c94-btn-1",
"panel-cc10046cc5429efe0f01f5384daf9c94-btn-2",
"panel-f2d8c9c259691049ab9c2907caab7578-0",
"panel-f2d8c9c259691049ab9c2907caab7578-1",
"panel-f2d8c9c259691049ab9c2907caab7578-2",
"panel-f2d8c9c259691049ab9c2907caab7578-btn-0",
"panel-f2d8c9c259691049ab9c2907caab7578-btn-1",
"panel-f2d8c9c259691049ab9c2907caab7578-btn-2",
"panel-fc14dc77778d6aa0862cf6dc8cbfbd1d-0",
"panel-fc14dc77778d6aa0862cf6dc8cbfbd1d-1",
"panel-fc14dc77778d6aa0862cf6dc8cbfbd1d-2",
"panel-fc14dc77778d6aa0862cf6dc8cbfbd1d-btn-0",
"panel-fc14dc77778d6aa0862cf6dc8cbfbd1d-btn-1",
"panel-fc14dc77778d6aa0862cf6dc8cbfbd1d-btn-2",
"panels",
"persona",
"pie-chart",

View File

@@ -9,11 +9,11 @@
<input type="checkbox" class="checkbox navbar-mode-selector" id="{{ $id }}-checkbox" aria-label="{{ T "colorMode" }}" />
<label class="label" for="{{ $id }}-checkbox">
{{ if $toggle }}
<div class="mode-item d-none-dark">
<div class="mode-item d-none-main-dark">
{{- partial "assets/icon.html" (dict "icon" (printf "%s fa-fw" $iconLight) "spacing" false) }}
<span class="d-{{ $breakpoint }}-none">{{ T "colorMode" }}</span>
</div>
<div class="mode-item d-none-light">
<div class="mode-item d-none-main-light">
{{- partial "assets/icon.html" (dict "icon" (printf "%s fa-fw" $iconDark) "spacing" false) }}
<span class="d-{{ $breakpoint }}-none">{{ T "colorMode" }}</span>
</div>

View File

@@ -124,7 +124,8 @@
data-bs-theme="{{ $overlayMode }}"
{{ if $args.fixed }}data-bs-overlay="{{ $overlayMode }}"{{ end }}
{{ if $color }}data-navbar-color="{{ $color }}"{{ end }}
{{ end }}
{{ end }}
{{ with $args.transparent }}data-transparent="{{ . }}"{{ end }}
>
<div class="container-xxl p-0">
<div class="d-flex navbar-container">

View File

@@ -46,14 +46,15 @@
"fixed" site.Params.navigation.fixed
"overlay" site.Params.navigation.overlay
"overlayMode" site.Params.navigation.overlayMode
"transparent" site.Params.navigation.transparent
"color" site.Params.navigation.color
"style" (default "light" site.Params.navigation.style)
"breakpoint" (default "md" site.Params.navigation.size))
-}}
<div id="container" class="main">
<main id="container" class="main">
{{ block "main" . }}{{ end -}}
</div>
</main>
{{- partial "footer/social.html" . -}}
{{- partial "footer/footer.html" . -}}