收起侧边栏
为 Starlight 侧边栏添加悬浮/常驻切换功能
Section titled “为 Starlight 侧边栏添加悬浮/常驻切换功能”本教程将介绍如何为 Astro Starlight 文档站点的侧边栏添加悬浮模式和常驻模式切换功能。实现效果包括:
- 悬浮模式:侧边栏默认收起,鼠标悬停时展开
- 常驻模式:侧边栏固定展开
- 切换按钮:位于侧边栏 “Welcome to Starlight” 导航项右侧,使用 ☰ 符号
- 主题适配:按钮颜色自动跟随深色/浅色主题变化
- 模式记忆:使用 localStorage 保存用户偏好
需要创建/修改以下文件:
├── public/│ └── scripts/│ └── sidebar.js # 侧边栏功能脚本└── src/ └── styles/ └── custom.css # 自定义样式步骤一:创建自定义 CSS 文件
Section titled “步骤一:创建自定义 CSS 文件”在 src/styles/custom.css 中添加以下样式:
/* 侧边栏收起/展开功能自定义样式 */
/* 侧边栏容器基础样式 */:root { --sidebar-width: 300px; --sidebar-collapsed-width: 0px; --sidebar-transition-duration: 0.3s;}
/* 侧边栏包装器 - 使用 Starlight 的 sidebar-pane 类 */.sidebar-pane { position: fixed; top: var(--header-height, 64px); left: 0; height: calc(100vh - var(--header-height, 64px)); width: var(--sidebar-width); z-index: 1000; transition: transform var(--sidebar-transition-duration) ease, width var(--sidebar-transition-duration) ease;}
/* 悬浮模式 - 默认收起 */body.sidebar-floating .sidebar-pane { width: var(--sidebar-width); transform: translateX(calc(-100% + 40px));}
/* 悬浮模式下 hover 时展开 */body.sidebar-floating .sidebar-pane:hover,body.sidebar-floating .sidebar-pane.sidebar-open { transform: translateX(0);}
/* 常驻模式 */body.sidebar-docked .sidebar-pane { transform: translateX(0);}
/* 侧边栏内容 - 禁用滚动 */.sidebar-content { height: 100%; background-color: var(--color-sidebarBackground); border-right: 1px solid var(--color-hairline); overflow-y: hidden; overflow-x: hidden;}
/* 导航项容器 - 为切换按钮提供定位上下文 */.sl-sidebar-nav li:first-child a,.sl-sidebar-nav a[href="/"],.sidebar-pane a[href="/"],nav a[href="/"] { position: relative !important;}
/* 切换按钮 - 使用 CSS 伪元素创建,与导航项一起加载 */.sl-sidebar-nav li:first-child a::after,.sl-sidebar-nav a[href="/"]::after,.sidebar-pane a[href="/"]::after,nav a[href="/"]::after { content: '☰'; position: absolute; top: 50%; transform: translateY(-50%); right: 8px; width: 28px; height: 28px; background-color: var(--color-accent); border: 1px solid var(--color-hairline); border-radius: 6px; cursor: pointer; display: flex; align-items: center; justify-content: center; z-index: 10; transition: all 0.2s ease; font-size: 18px; font-weight: 700; color: var(--color-sidebarItemText); line-height: 1;}
/* 悬停状态 */.sl-sidebar-nav li:first-child a:hover::after,.sl-sidebar-nav a[href="/"]:hover::after,.sidebar-pane a[href="/"]:hover::after,nav a[href="/"]:hover::after { background-color: var(--color-accent); transform: translateY(-50%) scale(1.05);}
/* 常驻模式:显示☰符号(水平) */body.sidebar-docked .sl-sidebar-nav li:first-child a::after,body.sidebar-docked .sl-sidebar-nav a[href="/"]::after,body.sidebar-docked .sidebar-pane a[href="/"]::after,body.sidebar-docked nav a[href="/"]::after { content: '☰'; transform: translateY(-50%) rotate(0deg);}
/* 悬浮模式:显示☰符号(旋转 90 度,垂直) */body.sidebar-floating .sl-sidebar-nav li:first-child a::after,body.sidebar-floating .sl-sidebar-nav a[href="/"]::after,body.sidebar-floating .sidebar-pane a[href="/"]::after,body.sidebar-floating nav a[href="/"]::after { content: '☰'; transform: translateY(-50%) rotate(90deg);}
/* 移动端适配 */@media (max-width: 768px) { .sidebar-pane { width: 100%; transform: translateX(-100%); }
body.sidebar-floating .sidebar-pane, body.sidebar-docked .sidebar-pane { transform: translateX(-100%); }
body.sidebar-floating .sidebar-pane.sidebar-open, body.sidebar-docked .sidebar-pane.sidebar-open { transform: translateX(0); }
/* 移动端隐藏切换按钮 */ .sl-sidebar-nav li:first-child a::after, .sl-sidebar-nav a[href="/"]::after, .sidebar-pane a[href="/"]::after, nav a[href="/"]::after { display: none; }}
/* 主内容区域适配 */.main-content-wrapper,.main-content { margin-left: var(--sidebar-width); transition: margin-left var(--sidebar-transition-duration) ease;}
/* 悬浮模式下主内容占满全屏 */body.sidebar-floating .main-content-wrapper,body.sidebar-floating .main-content { margin-left: 0;}
/* 完全隐藏滚动条但保持滚动功能 */.sidebar-content { scrollbar-width: none; -ms-overflow-style: none;}
.sidebar-content::-webkit-scrollbar { width: 0; height: 0;}
.sidebar-content::-webkit-scrollbar-track { background: transparent;}
.sidebar-content::-webkit-scrollbar-thumb { background: transparent;}
/* Starlight 侧边栏导航样式 */starlight-layout .sidebar,.sl-layout .sidebar { position: relative; height: 100%;}步骤二:创建 JavaScript 脚本
Section titled “步骤二:创建 JavaScript 脚本”在 public/scripts/sidebar.js 中添加以下代码:
/** * 侧边栏收起/展开功能脚本 * 实现侧边栏的悬浮模式和常驻模式切换 * 针对 Starlight 框架优化 */
(function () { 'use strict';
const STORAGE_KEY = 'starlight-sidebar-mode'; const MODE_FLOATING = 'floating'; const MODE_DOCKED = 'docked';
/** * 查找侧边栏元素 */ function findSidebarElement() { const selectors = [ '.sidebar-pane', '.sidebar', '.starlight-aside', 'aside', '[role="navigation"]' ];
for (const selector of selectors) { const element = document.querySelector(selector); if (element) { console.log('[Sidebar] Found sidebar using selector:', selector); return element; } }
return null; }
/** * 等待侧边栏元素出现 */ function waitForSidebarElement(maxAttempts = 10) { return new Promise((resolve) => { let attempts = 0;
const check = () => { const element = findSidebarElement(); if (element) { resolve(element); } else if (attempts++ < maxAttempts) { setTimeout(check, 100); } else { resolve(null); } };
check(); }); }
/** * 初始化侧边栏功能 */ function initSidebar() { if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', setupSidebar); } else { setupSidebar(); } }
/** * 设置侧边栏 */ async function setupSidebar() { console.log('[Sidebar] Setting up sidebar...');
const sidebarElement = await waitForSidebarElement(); if (!sidebarElement) { console.warn('[Sidebar] Sidebar element not found'); return; }
sidebarElement.classList.add('sl-sidebar-wrapper');
const savedMode = localStorage.getItem(STORAGE_KEY) || MODE_FLOATING; setSidebarMode(savedMode);
const welcomeNavItem = findWelcomeNavItem(); if (welcomeNavItem) { welcomeNavItem.addEventListener('click', function (e) { const rect = welcomeNavItem.getBoundingClientRect(); const clickX = e.clientX; const clickY = e.clientY;
const buttonRight = rect.right - 8; const buttonLeft = buttonRight - 28; const buttonTop = rect.top + (rect.height / 2) - 14; const buttonBottom = buttonTop + 28;
if (clickX >= buttonLeft && clickX <= buttonRight && clickY >= buttonTop && clickY <= buttonBottom) { e.preventDefault(); e.stopPropagation();
const mode = getCurrentMode(); const newMode = mode === MODE_FLOATING ? MODE_DOCKED : MODE_FLOATING;
setSidebarMode(newMode); localStorage.setItem(STORAGE_KEY, newMode);
console.log('[Sidebar] Toggle clicked, new mode:', newMode); } }); }
setupHoverBehavior(sidebarElement); console.log('[Sidebar] Setup complete, mode:', savedMode); }
/** * 查找导航项 */ function findWelcomeNavItem() { const selectors = [ '.sl-sidebar-nav li:first-child a', '.sl-sidebar-nav a[href="/"]', '.sidebar-pane a[href="/"]', 'nav a[href="/"]' ];
for (const selector of selectors) { const element = document.querySelector(selector); if (element) { console.log('[Sidebar] Found welcome nav item using selector:', selector); return element; } }
return null; }
/** * 获取当前模式 */ function getCurrentMode() { return document.body.classList.contains('sidebar-docked') ? MODE_DOCKED : MODE_FLOATING; }
/** * 设置侧边栏模式 */ function setSidebarMode(mode) { document.body.classList.remove('sidebar-floating', 'sidebar-docked'); document.body.classList.add('sidebar-' + mode); console.log('[Sidebar] Mode set to:', mode); }
/** * 设置悬浮模式下的鼠标行为 */ function setupHoverBehavior(wrapper) { const checkAndSetupHover = () => { if (document.body.classList.contains('sidebar-floating')) { wrapper.addEventListener('mouseenter', handleMouseEnter); wrapper.addEventListener('mouseleave', handleMouseLeave); } else { wrapper.removeEventListener('mouseenter', handleMouseEnter); wrapper.removeEventListener('mouseleave', handleMouseLeave); } };
function handleMouseEnter() { wrapper.classList.add('sidebar-open'); }
function handleMouseLeave() { wrapper.classList.remove('sidebar-open'); }
checkAndSetupHover();
const observer = new MutationObserver(checkAndSetupHover); observer.observe(document.body, { attributes: true, attributeFilter: ['class'] }); }
initSidebar();})();步骤三:配置 Astro 加载脚本
Section titled “步骤三:配置 Astro 加载脚本”在 astro.config.mjs 中,将脚本注入到页面头部:
import starlight from '@astrojs/starlight';import { defineConfig } from 'astro/config';
export default defineConfig({ integrations: [ starlight({ title: 'My Docs', // 注入自定义脚本 head: [ { tag: 'script', attrs: { src: '/scripts/sidebar.js' }, }, ], }), ],});步骤四:引入自定义 CSS
Section titled “步骤四:引入自定义 CSS”在 astro.config.mjs 的 Starlight 配置中添加自定义 CSS:
starlight({ title: 'My Docs', // 自定义 CSS 样式 customCss: [ '/src/styles/custom.css', ], // ... 其他配置})- 悬浮模式(默认):侧边栏收起,只露出 40px 边缘,鼠标悬停时展开
- 常驻模式:侧边栏固定展开,不自动收起
- 位置:侧边栏 “Welcome to Starlight” 导航项右侧
- 图标:☰ 符号
- 状态:
- 常驻模式:水平显示 ☰
- 悬浮模式:旋转 90 度垂直显示 ☰
- 按钮背景色使用
var(--color-accent) - 图标颜色使用
var(--color-sidebarItemText) - 自动跟随 Starlight 的深色/浅色主题变化
- 使用
localStorage保存用户选择的模式 - 刷新页面或切换路由后保持用户偏好
- 移动端适配:在屏幕宽度小于 768px 时,切换按钮会自动隐藏
- 浏览器兼容性:使用现代浏览器特性(CSS 变量、MutationObserver 等)