修复导航栏高亮问题

This commit is contained in:
lsy 2025-05-16 01:06:51 +08:00
parent 3c232a67cd
commit 2ade032160
4 changed files with 230 additions and 59 deletions

View File

@ -347,7 +347,7 @@ const navSelectorClassName = "mr-4";
// 内部状态管理
const state = {
isCleaningUp: false,
lastPathLogged: '',
lastActivePath: '',
originalPushState: window.history.pushState,
originalReplaceState: window.history.replaceState
};
@ -361,7 +361,7 @@ const navSelectorClassName = "mr-4";
// 添加事件监听器并记录,方便后续统一清理
function addListener(element, eventType, handler, options) {
if (!element) {
console.warn(`导航脚本尝试为不存在的元素添加事件:`, eventType);
console.warn(`导航元素不存在`);
return null;
}
@ -381,12 +381,13 @@ const navSelectorClassName = "mr-4";
if (state.isCleaningUp) return;
const currentPath = getCurrentPath();
if (currentPath !== state.lastPathLogged) {
if (currentPath !== state.lastActivePath) {
// 主动调用初始化和更新高亮
initActiveState();
updateHighlights(true);
updateHighlights(false);
state.lastPathLogged = currentPath;
state.lastActivePath = currentPath;
}
}
@ -462,7 +463,10 @@ const navSelectorClassName = "mr-4";
const primaryHighlight = document.getElementById('nav-primary-highlight');
const secondaryHighlight = document.getElementById('nav-secondary-highlight');
if (!primaryHighlight || !secondaryHighlight) return null;
if (!primaryHighlight || !secondaryHighlight) {
console.warn(`导航高亮元素未找到`);
return null;
}
// 获取标准高度
const getStandardHeight = () => {
@ -709,6 +713,17 @@ const navSelectorClassName = "mr-4";
toggle.classList.remove('menu-up', 'font-semibold', 'text-primary-700', 'dark:text-primary-300');
toggle.classList.add('text-gray-600', 'dark:text-gray-300', 'hover:text-primary-600', 'dark:hover:text-primary-400');
}
// 处理子菜单状态
const menuItems = group.querySelector('.nav-group-items');
if (menuItems) {
const wasVisible = menuItems.classList.contains('menu-visible');
if (wasVisible) {
menuItems.classList.remove('menu-visible');
menuItems.classList.add('hidden');
}
}
});
// 标记变量,用于跟踪是否找到匹配的导航项
@ -716,6 +731,7 @@ const navSelectorClassName = "mr-4";
// 先检查子菜单项
const subItems = document.querySelectorAll('.nav-subitem');
for (const subItem of subItems) {
const href = subItem.getAttribute('href');
if (!href) continue;
@ -757,6 +773,7 @@ const navSelectorClassName = "mr-4";
// 如果没找到匹配的子菜单项,检查主菜单项
if (!foundMatch) {
const items = document.querySelectorAll('.nav-item');
for (const item of items) {
const href = item.getAttribute('href');
if (!href) continue;
@ -775,11 +792,11 @@ const navSelectorClassName = "mr-4";
// 如果仍然没找到匹配项,记录警告
if (!foundMatch) {
console.warn(`导航: 找不到与当前路径匹配的导航项: ${currentPath}`);
console.warn(`找不到匹配当前路径的导航项: ${currentPath}`);
}
// 计算正确高亮位置
updateHighlights(true);
updateHighlights(false);
};
// DOM加载完成后执行初始化
@ -795,7 +812,7 @@ const navSelectorClassName = "mr-4";
// 主要设置函数
function setupNavigation() {
// 初始化路径记录
state.lastPathLogged = getCurrentPath();
state.lastActivePath = getCurrentPath();
// 设置桌面导航
setupNavSelector();
@ -838,6 +855,7 @@ const navSelectorClassName = "mr-4";
if (state.isCleaningUp) {
return state.originalPushState.apply(this, arguments);
}
state.originalPushState.apply(this, arguments);
handlePathChange();
};
@ -847,6 +865,7 @@ const navSelectorClassName = "mr-4";
if (state.isCleaningUp) {
return state.originalReplaceState.apply(this, arguments);
}
state.originalReplaceState.apply(this, arguments);
handlePathChange();
};
@ -865,7 +884,7 @@ const navSelectorClassName = "mr-4";
const navSubItems = document.querySelectorAll('.nav-subitem');
if (!primaryHighlight || !secondaryHighlight) {
console.warn(`导航脚本未找到高亮元素,导航功能可能不完整`);
console.warn(`导航高亮元素未找到`);
}
const activeClass = "font-medium";
@ -1066,6 +1085,7 @@ const navSelectorClassName = "mr-4";
function toggleNavGroup(groupId, forceState = null) {
const targetGroup = document.querySelector(`.nav-group[data-group-id="${groupId}"]`);
if (!targetGroup) {
console.error(`导航组未找到: ${groupId}`);
return;
}
@ -1073,6 +1093,7 @@ const navSelectorClassName = "mr-4";
const toggle = targetGroup.querySelector('.nav-group-toggle');
if (!items || !toggle) {
console.error(`导航组元素未找到: ${groupId}`);
return;
}
@ -1129,7 +1150,7 @@ const navSelectorClassName = "mr-4";
// 给DOM渲染一些时间然后更新高亮
setTimeout(() => {
updateHighlights(true);
updateHighlights(false);
}, 50);
}, duration);
}
@ -1172,7 +1193,7 @@ const navSelectorClassName = "mr-4";
// 当菜单展开后更新高亮背景
setTimeout(() => {
updateHighlights(true);
updateHighlights(false);
}, 50);
});
@ -1223,7 +1244,7 @@ const navSelectorClassName = "mr-4";
}
// 更新高亮
updateHighlights(true);
updateHighlights(false);
}
}
}
@ -1239,8 +1260,6 @@ const navSelectorClassName = "mr-4";
// 设置高亮
setActiveItem(itemId);
});
});
@ -1266,8 +1285,6 @@ const navSelectorClassName = "mr-4";
// 设置高亮
setActiveSubItem(subItemId);
});
});
@ -1289,7 +1306,7 @@ const navSelectorClassName = "mr-4";
// 再次调用初始化
initActiveState();
// 确保高亮背景位置正确
updateHighlights(true);
updateHighlights(false);
});
}
@ -1297,28 +1314,48 @@ const navSelectorClassName = "mr-4";
addListener(document, 'astro:after-swap', () => {
setTimeout(() => {
initActiveState();
updateHighlights(true);
updateHighlights(false);
}, 50);
});
// 添加Astro View Transitions事件监听
addListener(document, 'astro:page-load', () => {
setTimeout(initActiveState, 50);
updateHighlights(true);
updateHighlights(false);
});
// 添加popstate事件监听
addListener(window, 'popstate', () => {
addListener(window, 'popstate', (e) => {
setTimeout(() => {
initActiveState();
updateHighlights(true);
updateHighlights(false);
// 修复:确保激活的菜单组的子菜单是可见的
const activeGroupsAfter = document.querySelectorAll('.nav-group.active');
activeGroupsAfter.forEach(group => {
const groupId = group.dataset.groupId;
const items = group.querySelector('.nav-group-items');
if (items && !items.classList.contains('menu-visible')) {
// 移除隐藏状态并添加可见状态
items.classList.remove('hidden', 'menu-hidden');
items.classList.add('menu-visible');
// 确保切换按钮也具有正确的状态
const toggle = group.querySelector('.nav-group-toggle');
if (toggle) {
toggle.classList.add('menu-up', 'font-semibold', 'text-primary-700', 'dark:text-primary-300');
toggle.classList.remove('text-gray-600', 'dark:text-gray-300', 'hover:text-primary-600', 'dark:hover:text-primary-400');
}
}
});
}, 50);
});
// 窗口大小变化时重新计算高亮位置
addListener(window, 'resize', () => {
updateHighlights(true);
updateHighlights(false);
});
}
@ -1332,7 +1369,7 @@ const navSelectorClassName = "mr-4";
const menuCloseIcon = document.getElementById('menu-close-icon');
if (!mobileMenuButton || !mobileMenu) {
console.warn(`导航脚本未找到移动端菜单组件`);
console.warn(`移动端菜单组件未找到`);
return;
}

View File

@ -19,22 +19,6 @@ import {
} from "three";
import type { Side } from "three";
// 为requestIdleCallback添加类型声明
interface RequestIdleCallbackOptions {
timeout: number;
}
interface Window {
requestIdleCallback?: (
callback: (deadline: {
didTimeout: boolean;
timeRemaining: () => number;
}) => void,
opts?: RequestIdleCallbackOptions
) => number;
cancelIdleCallback?: (handle: number) => void;
}
// 需要懒加载的模块
const loadControlsAndRenderers = () => Promise.all([
import("three/examples/jsm/controls/OrbitControls.js"),

View File

@ -20,7 +20,7 @@ tags: []
  马来西亚第一站计划去粉红清真寺,从吉隆坡机场 1 楼乘坐巴士直接过去,但是购票的时候,公交车售票厅工作人员说没有到粉红清真寺的公交车只能打出租车前往,看了一下打车的价格,决定重新做攻略再挣扎一下,在休息长椅坐了半个小时终于找到新路线`地铁站->布城->公交站->粉红清真寺`,往机场 3 楼地铁站走的时候发现斜挎包不见了,听说国外酒店工作人员会翻包偷钱,就买了个斜挎包放护照,现金等重要的东西,惊慌了一会,还好头脑风暴了一会想起来了在做攻略的长椅忘拿了。
  布城的公交车站是始发站,在最后一个站台才找到`T523`,我想上车但是司机朝我摆手,我就在车子旁边的亭子研究如何打车,研究了一会司机叫了我一声,给我一个招揽的手势,上去了我给司机看我的谷歌地图,用翻译软件软件问司机可以去这里吗,司机用本地话说一大堆,我将谷歌同声翻译打开司机,告诉司机对着这个说我就可以听懂了,但是给司机一个字不说,拿开了手机司机又开始说本地话,僵持了一会司机不耐烦的打手势让我去旁边公交车,我以为上错车了,我将地图给旁边公交车司机看,说要去地图的地方,旁边公交车的司机指着`T523`告诉我那辆车可以去,回到 T523 后告诉司机就是这个车,车开了一会司机说 three ,我本以为司机完全不会英语呢,途中看到一个清真寺,打开谷歌地图显示现在要去的最后一个站,我指着清真寺问司机"is there?",司机说"yes,down",看着绿色的清真寺我觉得现在照骗太多了,看着别人拿着证件或者是手机给安保人员看了才能进去,我想网上不要预约和门票的说法看来是过时了,不过来都来了我要去试试,到了门口工作人员拦下我,我告诉工作人员我没有预约但是我想进去参观,工作人员反复询问我确定要进去吗,我告诉工作人员我专程过来参观这个清真寺,工作人员的话翻译过来是“这是政府办公的地方不允许外人靠近,但是你是第一次”,我打开地图重新导航显示距离粉红清真寺还有 1.2km。
  布城的公交车站是始发站,在最后一个站台才找到`T523`,我想上车但是司机朝我摆手,看来我来的时间不对,我就在车子旁边的亭子研究如何打车,研究了一会司机叫了我一声,给我一个招揽的手势,上去了我给司机看我的谷歌地图,用翻译软件软件问司机可以去这里吗,司机用本地话说一大堆,我将谷歌同声翻译打开司机,告诉司机对着这个说我就可以听懂了,但是给司机一个字不说,拿开了手机司机又开始说本地话,僵持了一会司机不耐烦的打手势让我去旁边公交车,我以为上错车了,我将地图给旁边公交车司机看,说要去地图的地方,旁边公交车的司机指着`T523`告诉我那辆车可以去,回到 T523 后告诉司机就是这个车,车开了一会司机说 three ,我本以为司机完全不会英语呢,途中看到一个清真寺,打开谷歌地图显示现在要去的最后一个站,我指着清真寺问司机"is there?",司机说"yes,down",看着绿色的清真寺我觉得现在照骗太多了,看着别人拿着证件或者是手机给安保人员看了才能进去,我想网上不要预约和门票的说法看来是过时了,不过来都来了我要去试试,到了门口工作人员拦下我,我告诉工作人员我没有预约但是我想进去参观,工作人员反复询问我确定要进去吗,我告诉工作人员我专程过来参观这个清真寺,工作人员的话翻译过来是“这是政府办公的地方不允许外人靠近,但是你是第一次”,我打开地图重新导航显示距离粉红清真寺还有 1.2km。
  粉红清真寺的穹顶真的是粉红色的!穹顶里面看更漂亮,由红色,浅粉色,白色构成的图案
@ -28,12 +28,25 @@ tags: []
  国外的公交车不适合 i 人,我打算从布城从地铁到市中心,需要先坐公交车到布城去,差 10 秒就赶上了,但是站台没人所以公交车司机没有停,第二次在休息区等了半个小时司机又没停,可能是司机没看到我吧,第三次我站在公交车站台等车的位置等待可是他还是没停,这次可能是没有给司机信号,第四次等公交车快到的时候我死死的看着司机,与他建立心灵链接,但他还是不停,浏览器查询原来要招手,用打车软件看了一下两公里,还是选择打车了
  在去酒店的路上看到了很多流浪汉,不过感觉他们的穿搭和我没有区别,一个包+拖鞋,历经大雨来到谷歌地图显示的位置,却找不到酒店,找了一个印度男人问路,他也找不到,他给酒店客服打电话后,告诉我不在这个区域,给我指路,往哪走,再往哪走,到一个塔下就快到了,再问问别人,我一点没记住好在用高德地图重新导航,竟然没问题。
  在去酒店的路上看到了很多流浪汉,不过感觉他们的穿搭和我没有区别,一个包+拖鞋,历经大雨来到谷歌地图显示的位置,却找不到酒店,找了一个印度男人问路,他虽然不知道,但是好心的帮我找了很久,最后他给酒店客服打电话后,告诉我不在这个区域,给我指路,往哪走,再往哪走,到一个塔下就快到了,再问问别人,我一点没记住好在用高德地图重新导航,竟然没问题。
  晚餐在酒店旁找了家本地人多的店,点了一个大虾饭,没想到是正宗印度菜,`米饭味道=70%八角+20%洗衣服+10%辣椒`
  晚餐在酒店旁找了家本地人多的店,点了一个大虾饭,没想到是正宗印度菜,`米饭味道=70%八角+20%洗衣服+10%辣椒
## 不带脑子旅游真好
  凌晨 3 点被炸街吵醒,没想到精神小伙也是全世界统一
---
  在飞往印度尼西亚待机的时候,看到两群中国人遇到了同胞很开心,我告诉他们我也是,王杰说行程规划的时候,我发现我们的行程规划一模一样!!!
  到了酒店放好行李,王杰就找好科摩多岛的旅行团,他和他的朋友打算去看日落,我不会开摩托,王杰帮我找了一个摩托车司机,什么都被安排好了,不带脑子旅游真好,摩托车司机带我们去了一个只有本地人并且视野极佳的位置
  找晚餐的时候遇到一个卖榴莲的价格14w印尼盾合人民币70块钱我感觉价格还不错王杰他们让我别买可能他们不太喜欢榴莲吧老板一直跟着想要卖给我以前不好意思所有没有过砍价不过现在人生地不熟我用计算器给老板按7w一直摇头no坚持7w没想到人生第一次砍价成功了
  印度尼西亚的海鲜真便宜,螃蟹/鱼 100人民币左右一只大龙虾150人民币左右一只大虾50人民币左右一盘空心菜味道太棒了吃过最棒的一次炒蔬菜
##
## 未完待写

View File

@ -116,6 +116,11 @@ function isArticlePage() {
return path.includes('/articles') || path.includes('/filtered');
}
// 检查DOM中是否存在指定的容器
function containerExists(selector) {
return document.querySelector(selector) !== null;
}
// 为元素设置过渡状态
function setElementTransition(element) {
if (!element) return;
@ -173,16 +178,48 @@ document.addEventListener('DOMContentLoaded', () => {
let contentReady = false;
let animationInProgress = false;
// 根据当前页面动态确定容器配置
const containers = ['main']; // 主容器始终存在
// 只有当文章内容容器存在时才添加
if (containerExists('#article-content')) {
containers.push('#article-content');
}
// 创建Swup实例
const swup = new Swup({
// Swup的基本配置
animationSelector: '[class*="transition-"], .swup-transition-article, #article-content',
cache: true,
containers: ['main'],
containers: containers, // 使用动态容器配置
animationScope: 'html', // 确保动画状态类添加到html元素
linkSelector: 'a[href^="/"]:not([data-no-swup]), a[href^="' + window.location.origin + '"]:not([data-no-swup])',
skipPopStateHandling: (event) => {
return event.state && event.state.source === 'swup';
// 使用默认的skipPopStateHandling设置只处理由swup创建的历史记录
skipPopStateHandling: (event) => event.state?.source !== 'swup',
// 修复resolveUrl实现确保返回URL字符串而不是对象
resolveUrl: function(url) {
// 直接返回URL字符串
return url;
},
// 增加自定义容器解析,解决容器不匹配的问题
resolveContainers: async function(visit) {
// 根据URL路径动态决定要使用哪些容器
const isFromArticlePage = visit?.from?.url.includes('/articles') || visit?.from?.url.includes('/filtered');
const isToArticlePage = visit?.to?.url.includes('/articles') || visit?.to?.url.includes('/filtered');
// 当从文章页到非文章页,或从非文章页到文章页时
if (isFromArticlePage !== isToArticlePage) {
return ['main'];
}
// 对于文章页面之间的导航,使用两个容器
if (isFromArticlePage && isToArticlePage) {
return ['main', '#article-content'];
}
// 默认情况使用main容器
return ['main'];
},
plugins: [] // 手动添加插件以控制顺序
});
@ -240,9 +277,10 @@ document.addEventListener('DOMContentLoaded', () => {
});
swup.use(scriptsPlugin);
// 创建Fragment插件 - 简化规则避免匹配问题
// 创建Fragment插件 - 只在需要的页面使用
const fragmentPlugin = new SwupFragmentPlugin({
debug: false, // 关闭调试模式
// 修改规则,增加更细致的配置
rules: [
{
name: 'article-pages',
@ -250,16 +288,58 @@ document.addEventListener('DOMContentLoaded', () => {
to: ['/articles', '/filtered'],
containers: ['#article-content']
}
]
],
// 默认情况下忽略URL片段只使用路径部分
considerFragment: false
});
// 添加Fragment插件到Swup
// 修改Fragment插件的加载逻辑 - 始终加载,但根据页面类型动态启用/禁用
swup.use(fragmentPlugin);
// 初始化后手动扫描并预加载带有data-swup-preload属性的链接
setTimeout(() => {
swup.preloadLinks();
}, 1000);
const preloadLinks = document.querySelectorAll('[data-swup-preload]');
if (preloadLinks.length > 0) {
preloadLinks.forEach(link => {
// 检查链接是否符合预加载条件
if (link.tagName.toLowerCase() === 'a' && link.href) {
// 调用预加载插件的方法
preloadPlugin.preloadPage(link.href);
}
});
}
// 初始化历史状态 - 确保第一个页面正确记录
if (window.history && window.history.replaceState) {
// 获取当前页面的关键信息
const pageData = {
url: window.location.pathname + window.location.search,
title: document.title,
scroll: {
x: window.scrollX,
y: window.scrollY
},
timestamp: Date.now()
};
// 记录初始状态,确保返回时能正确恢复
try {
const currentState = window.history.state || {};
// 保留所有现有状态添加swup需要的内容
const newState = {
...currentState,
url: pageData.url,
title: pageData.title,
scroll: pageData.scroll,
source: 'swup', // 修改为source标记以符合默认skipPopStateHandling
id: Math.random().toString(36).substring(2, 11) // 生成唯一ID
};
// 使用replaceState不增加历史记录
window.history.replaceState(newState, pageData.title, pageData.url);
} catch (e) {
console.error('Failed to initialize history state:', e);
}
}
// 重新设置过渡元素
function setupTransition() {
@ -279,7 +359,7 @@ document.addEventListener('DOMContentLoaded', () => {
// 初始化时设置
setupTransition();
// ===== 重新优化生命周期钩子 =====
// ===== 生命周期钩子 =====
// 1. 访问开始 - 显示加载动画,准备页面退出
swup.hooks.on('visit:start', (visit) => {
@ -318,6 +398,11 @@ document.addEventListener('DOMContentLoaded', () => {
// 2. 内容已加载但尚未替换 - 设置内容状态
swup.hooks.on('page:load', (visit) => {
contentReady = true;
// 如果是载入文章页面但Fragment插件未加载则加载它
if ((visit.to.url.includes('/articles') || visit.to.url.includes('/filtered')) &&
!swup.findPlugin('fragment')) {
swup.use(fragmentPlugin);
}
// 如果快速加载,先检查动画是否完成
if (!animationInProgress) {
@ -337,7 +422,6 @@ document.addEventListener('DOMContentLoaded', () => {
setElementOpacity(activeElement, 0);
});
swup.hooks.on('content:replace', () => {
// 重新设置过渡样式,但不要立即隐藏加载动画
setTimeout(() => {
@ -382,10 +466,60 @@ document.addEventListener('DOMContentLoaded', () => {
animationInProgress = false;
hideLoadingSpinner(spinner);
// 可以在这里添加错误提示UI
alert('页面加载失败,请重试或检查网络连接。');
console.error('Fetch error:', error);
// 在严重错误时回退到页面刷新
try {
const targetUrl = error?.visit?.to?.url || window.location.pathname;
window.location.href = targetUrl;
} catch (e) {
// 如果获取目标URL失败刷新当前页面
window.location.reload();
}
});
// 处理容器不匹配错误
const originalErrorHandler = window.console.error;
window.console.error = function(...args) {
// 调用原始错误处理器
originalErrorHandler.apply(this, args);
// 检查是否是容器不匹配错误
if (
args.length > 0 &&
typeof args[0] === 'string' &&
(args[0].includes('Container missing') || args[0].includes('Container mismatch'))
) {
// 尝试恢复
try {
// 隐藏加载动画
hideLoadingSpinner(spinner);
// 重置状态
isLoading = false;
contentReady = false;
animationInProgress = false;
// 检查是否可以使用备用容器
const mainContainer = document.querySelector('main');
if (mainContainer) {
// 强制使用main容器
swup.options.containers = ['main'];
// 手动为main容器添加过渡状态
mainContainer.classList.add('transition-fade');
setElementTransition(mainContainer);
setElementOpacity(mainContainer, 1);
}
// 发送页面转换事件
sendPageTransitionEvent();
} catch (e) {
console.error('Recovery failed:', e);
}
}
};
// 在页面内容替换后确保新内容动画正确显示
document.addEventListener('swup:contentReplaced', () => {
// 获取活跃元素
@ -426,7 +560,10 @@ document.addEventListener('DOMContentLoaded', () => {
sendPageTransitionEvent();
if (swup) {
swup.unuse(fragmentPlugin);
// 移除所有已使用的插件
if (swup.findPlugin('fragment')) {
swup.unuse(fragmentPlugin);
}
swup.unuse(headPlugin);
swup.unuse(preloadPlugin);
swup.unuse(scriptsPlugin);