Files
blog/src/components/layout/Navbar.astro
2026-01-07 16:24:34 +00:00

262 lines
10 KiB
Plaintext
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

---
import { Icon } from "astro-icon/components";
import DisplaySettings from "@/components/interactive/DisplaySettings.svelte";
import LayoutSwitchButton from "@/components/interactive/LayoutSwitchButton.svelte";
import LightDarkSwitch from "@/components/interactive/LightDarkSwitch.svelte";
import Search from "@/components/interactive/Search.svelte";
import WallpaperSwitch from "@/components/interactive/WallpaperSwitch.svelte";
import {
backgroundWallpaper,
navBarConfig,
navBarSearchConfig,
siteConfig,
} from "@/config";
import { LinkPresets } from "@/constants/link-presets";
import {
LinkPreset,
type NavBarLink,
NavBarSearchMethod,
} from "@/types/config";
import { isHomePage } from "@/utils/layout-utils";
import { url } from "@/utils/url-utils";
import DropdownMenu from "./DropdownMenu.astro";
import NavMenuPanel from "./NavMenuPanel.astro";
const className = Astro.props.class;
// 检查是否允许切换壁纸模式
const isWallpaperSwitchable = backgroundWallpaper.switchable ?? true;
// 检查是否允许切换布局(双侧栏模式下也允许切换,但切换到网格时会自动隐藏右侧边栏)
const allowLayoutSwitch = siteConfig.postListLayout.allowSwitch;
// 获取导航栏透明模式配置
const navbarTransparentMode =
backgroundWallpaper.banner?.navbar?.transparentMode || "semi";
// 获取导航栏标题,如果没有设置则使用 siteConfig.title
const navbarTitle = siteConfig.navbar.title || siteConfig.title;
// 获取导航栏宽度配置
const navbarWidthFull = siteConfig.navbar.widthFull ?? false;
// 检查是否为首页
const isHomePageCheck = isHomePage(Astro.url.pathname);
let links: NavBarLink[] = navBarConfig.links.map(
(item: NavBarLink | LinkPreset): NavBarLink => {
if (typeof item === "number") {
return LinkPresets[item];
}
return item;
},
);
---
<div id="navbar" class="z-50 onload-animation" data-transparent-mode={navbarTransparentMode} data-is-home={isHomePageCheck} data-full-width={navbarWidthFull}>
<div class="absolute h-8 left-0 right-0 -top-8 bg-[var(--card-bg)] transition"></div> <!-- used for onload animation -->
<div class:list={[
className,
"!overflow-visible h-[4.5rem] mx-auto flex items-center px-4",
navbarWidthFull ? "" : "justify-between max-w-[var(--page-width)]"
]}>
<a href={url('/')} class="btn-plain scale-animation rounded-lg h-[3.25rem] px-5 font-bold active:scale-95">
<div class:list={[
"flex flex-row items-center text-md",
siteConfig.navbar.followTheme ? "text-[var(--primary)]" : "dark:text-white text-black"
]}>
{siteConfig.navbar.logo?.type === "icon" ? (
<Icon name={siteConfig.navbar.logo.value || "material-symbols:home-pin-outline"} class="text-[1.75rem] mb-1 mr-2" />
) : siteConfig.navbar.logo?.type === "image" ? (
<img src={url(siteConfig.navbar.logo.value)} alt={siteConfig.navbar.logo.alt || siteConfig.title} class="h-[1.75rem] w-[1.75rem] mb-1 mr-2 object-contain" loading="lazy" />
) : (
<Icon name="material-symbols:home-pin-outline" class="text-[1.75rem] mb-1 mr-2" />
)}
{navbarTitle}
</div>
</a>
<div class="hidden md:flex items-center space-x-1">
{links.map((l) => {
return <DropdownMenu link={l} />;
})}
</div>
<div class:list={["flex", navbarWidthFull ? "ml-auto" : ""]}>
<!--<SearchPanel client:load>-->
<Search
client:load
searchMethod={navBarSearchConfig.method}
meiliSearchConfig={navBarSearchConfig.meiliSearchConfig}
/>
{!siteConfig.themeColor.fixed && (
<button aria-label="Display Settings" class="btn-plain scale-animation rounded-lg h-11 w-11 active:scale-90" id="display-settings-switch">
<Icon name="material-symbols:palette-outline" class="text-[1.25rem]"></Icon>
</button>
)}
{/* @ts-ignore */}
{isWallpaperSwitchable && <WallpaperSwitch client:load></WallpaperSwitch>}
{allowLayoutSwitch && <LayoutSwitchButton client:load currentLayout={siteConfig.postListLayout.defaultMode}></LayoutSwitchButton>}
{/* @ts-ignore */}
<LightDarkSwitch client:load></LightDarkSwitch>
<button aria-label="Menu" name="Nav Menu" class="btn-plain scale-animation rounded-lg w-11 h-11 active:scale-90 md:!hidden" id="nav-menu-switch">
<Icon name="material-symbols:menu-rounded" class="text-[1.25rem]"></Icon>
</button>
</div>
<NavMenuPanel links={links}></NavMenuPanel>
<DisplaySettings client:load></DisplaySettings>
</div>
</div>
<script>
function loadButtonScript() {
let settingBtn = document.getElementById("display-settings-switch");
if (settingBtn) {
settingBtn.onclick = function () {
let settingPanel = document.getElementById("display-setting");
if (settingPanel) {
settingPanel.classList.toggle("float-panel-closed");
}
};
}
let menuBtn = document.getElementById("nav-menu-switch");
if (menuBtn) {
menuBtn.onclick = function () {
let menuPanel = document.getElementById("nav-menu-panel");
if (menuPanel) {
menuPanel.classList.toggle("float-panel-closed");
}
};
}
let wallpaperSwitchBtn = document.getElementById("wallpaper-mode-switch");
if (wallpaperSwitchBtn) {
wallpaperSwitchBtn.onclick = function () {
let wallpaperPanel = document.getElementById("wallpaper-mode-panel");
if (wallpaperPanel) {
wallpaperPanel.classList.toggle("float-panel-closed");
}
};
}
let themeSwitchBtn = document.getElementById("scheme-switch");
if (themeSwitchBtn) {
themeSwitchBtn.onclick = function () {
let themePanel = document.getElementById("theme-mode-panel");
if (themePanel) {
themePanel.classList.toggle("float-panel-closed");
}
};
}
}
loadButtonScript();
// 为semifull模式添加滚动检测逻辑
function initSemifullScrollDetection() {
const navbar = document.getElementById('navbar');
if (!navbar) return;
const transparentMode = navbar.getAttribute('data-transparent-mode');
if (transparentMode !== 'semifull') return;
const isHomePage = navbar.getAttribute('data-is-home') === 'true';
// 如果不是首页,移除滚动事件监听器并设置为半透明状态
if (!isHomePage) {
// 移除之前的滚动事件监听器(如果存在)
if (window.semifullScrollHandler) {
window.removeEventListener('scroll', window.semifullScrollHandler as () => void);
window.semifullScrollHandler = undefined;
}
// 设置为半透明状态
navbar.classList.add('scrolled');
return;
}
// 移除现有的scrolled类重置状态
navbar.classList.remove('scrolled');
let ticking = false;
function updateNavbarState() {
const scrollTop = window.pageYOffset || document.documentElement.scrollTop;
const threshold = 50; // 滚动阈值,可以根据需要调整
// 使用批量DOM操作优化性能
if (scrollTop > threshold) {
navbar!.classList.add('scrolled');
} else {
navbar!.classList.remove('scrolled');
}
ticking = false;
}
function requestTick() {
if (!ticking) {
requestAnimationFrame(updateNavbarState);
ticking = true;
}
}
// 移除之前的滚动事件监听器(如果存在)
if (window.semifullScrollHandler) {
window.removeEventListener('scroll', window.semifullScrollHandler as () => void);
}
// 保存新的事件处理器引用
window.semifullScrollHandler = requestTick as unknown as (() => void);
// 监听滚动事件
window.addEventListener('scroll', requestTick, { passive: true });
// 初始化状态
updateNavbarState();
}
// 将函数暴露到全局对象,供页面切换时调用
window.initSemifullScrollDetection = initSemifullScrollDetection;
// 页面加载完成后初始化滚动检测
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', initSemifullScrollDetection);
} else {
initSemifullScrollDetection();
}
</script>
{
navBarSearchConfig.method === NavBarSearchMethod.PageFind && import.meta.env.PROD && (
<script is:inline define:vars={{ scriptUrl: url('/pagefind/pagefind.js') }}>
{/* 你的 loadPagefind 函数的完整内容放在这里 */}
async function loadPagefind() {
try {
const response = await fetch(scriptUrl, { method: 'HEAD' });
if (!response.ok) {
throw new Error(`Pagefind script not found: ${response.status}`);
}
const pagefind = await import(scriptUrl);
await pagefind.options({
excerptLength: 20
});
window.pagefind = pagefind;
document.dispatchEvent(new CustomEvent('pagefindready'));
console.log('Pagefind loaded and initialized successfully, event dispatched.');
} catch (error) {
console.error('Failed to load Pagefind:', error);
window.pagefind = {
search: () => Promise.resolve({ results: [] }),
options: () => Promise.resolve(),
};
document.dispatchEvent(new CustomEvent('pagefindloaderror'));
console.log('Pagefind load error, event dispatched.');
}
}
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', loadPagefind);
} else {
loadPagefind();
}
</script>
)
}