Skip to content

Commit

Permalink
改进目录滚动突出显示和活动状态
Browse files Browse the repository at this point in the history
  • Loading branch information
qianmoQ committed Feb 6, 2025
1 parent f22e08b commit e999f87
Show file tree
Hide file tree
Showing 2 changed files with 156 additions and 23 deletions.
156 changes: 155 additions & 1 deletion templates/assets/js/pageforge.js
Original file line number Diff line number Diff line change
Expand Up @@ -417,20 +417,174 @@ const FontSizeControl = {
}
};

const TOC = {
init() {
// 添加样式
if (!document.getElementById('toc-styles')) {
const style = document.createElement('style');
style.id = 'toc-styles';
style.textContent = `
.toc-link.active-toc-item {
background-color: rgb(239 246 255);
color: rgb(37 99 235);
position: relative;
}
.dark .toc-link.active-toc-item {
background-color: rgba(30 58 138 / 0.2);
color: rgb(96 165 250);
}
.toc-link.active-toc-item::before {
content: '';
position: absolute;
left: 0;
top: 50%;
transform: translateY(-50%);
width: 3px;
height: 16px;
background-color: rgb(37 99 235);
border-top-right-radius: 2px;
border-bottom-right-radius: 2px;
}
.dark .toc-link.active-toc-item::before {
background-color: rgb(96 165 250);
}
`;
document.head.appendChild(style);
}

// 获取所有 TOC 链接
const tocLinks = document.querySelectorAll('.toc-link');

// 处理点击事件
tocLinks.forEach(link => {
link.addEventListener('click', (e) => {
e.preventDefault();

// 移除所有活跃状态
tocLinks.forEach(l => l.classList.remove('active-toc-item'));

// 添加当前项的活跃状态
link.classList.add('active-toc-item');

// 获取目标元素和滚动位置
const targetId = link.getAttribute('data-slug');
const target = document.getElementById(targetId);
const header = document.querySelector('header nav');
const offset = header ? header.offsetHeight + 10 : 80;
const targetPosition = target.getBoundingClientRect().top + window.pageYOffset - offset;

// 滚动到目标位置
window.scrollTo({
top: targetPosition,
behavior: 'smooth'
});

// 如果是移动端,关闭 TOC 面板
const tocMobile = document.getElementById('toc-mobile');
if (tocMobile) {
tocMobile.classList.add('translate-y-full');
}
});
});

// 监听滚动,更新当前活跃项
const updateActiveItem = () => {
const header = document.querySelector('header nav');
const headerHeight = header ? header.offsetHeight : 0;

// 查找所有带 slug 的容器
const headings = Array.from(document.querySelectorAll('[id].inline-flex'));

// 找到当前视窗中最靠上的标题
let current = null;
let minDistance = Infinity;

headings.forEach(heading => {
const rect = heading.getBoundingClientRect();
const top = rect.top - headerHeight - 20;

// 计算到视窗顶部的距离
const distance = Math.abs(top);

// 如果元素在视窗上方或接近顶部,且距离比当前最小距离更小
if (top <= 10 && distance < minDistance) {
minDistance = distance;
current = heading;
}
});

// 更新 TOC 活跃状态
if (current) {
const slug = current.id;
const tocLinks = document.querySelectorAll('.toc-link');

tocLinks.forEach(link => {
if (link.getAttribute('data-slug') === slug) {
link.classList.add('active-toc-item');

// 确保活跃项在滚动区域内可见
const nav = link.closest('.overflow-y-auto');
if (nav) {
const navRect = nav.getBoundingClientRect();
const linkRect = link.getBoundingClientRect();

if (linkRect.top < navRect.top || linkRect.bottom > navRect.bottom) {
link.scrollIntoView({behavior: 'smooth', block: 'center'});
}
}
}
else {
link.classList.remove('active-toc-item');
}
});
}
};

// 使用 IntersectionObserver 来优化滚动监听
const observer = new IntersectionObserver((entries) => {
entries.forEach(() => {
requestAnimationFrame(updateActiveItem);
});
}, {
rootMargin: '-20% 0px -80% 0px',
threshold: [0, 1]
});

// 观察所有标题元素
document.querySelectorAll('[id].inline-flex').forEach(heading => {
observer.observe(heading);
});

// 仍然保留滚动监听作为备份
let scrollTimeout;
window.addEventListener('scroll', () => {
if (scrollTimeout) {
window.cancelAnimationFrame(scrollTimeout);
}
scrollTimeout = window.requestAnimationFrame(updateActiveItem);
}, {passive: true});

// 初始调用一次
updateActiveItem();
}
};

// 暴露到全局
window.PageForge = {
GitHubStats,
CodeCopy,
Header,
FontSizeControl,
Banner
Banner,
TOC
};

// DOM 加载完成后初始化
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', () => {
Header.init();
FontSizeControl.init();
TOC.init();
});
}
else {
Expand Down
23 changes: 1 addition & 22 deletions templates/includes/toc.ejs
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,6 @@
<% items?.forEach(item => { %>
<div class="pl-<%= (item.level - 1) * 4 %>">
<a href="#<%= item.slug %>"
onclick="event.preventDefault();
const target = document.getElementById('<%= item.slug %>');
const header = document.querySelector('header nav');
const offset = header ? header.offsetHeight + 10 : 80;
const targetPosition = target.getBoundingClientRect().top + window.pageYOffset - offset;
window.scrollTo({top: targetPosition, behavior: 'smooth'});"
class="block px-3 py-1 text-sm text-gray-600 dark:text-gray-400 hover:text-gray-900 dark:hover:text-gray-200 hover:bg-gray-50 dark:hover:bg-gray-800 rounded-md transition-colors duration-150 toc-link"
data-slug="<%= item.slug %>">
<%- item.text %>
Expand All @@ -29,15 +23,7 @@
</div>
</div>
<!-- 移动端目录按钮 -->
<button class="lg:hidden fixed right-4 bottom-4 z-20 bg-white dark:bg-gray-800 p-2 rounded-full shadow-lg"
onclick="document.getElementById('toc-mobile').classList.toggle('translate-y-full')">
<svg class="w-6 h-6 text-gray-600 dark:text-gray-300" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6h16M4 12h16M4 18h7"></path>
</svg>
</button>
<!-- 移动端底部目录面板 -->
<!-- 移动端的模板也需要同样的修改 -->
<div id="toc-mobile"
class="lg:hidden fixed bottom-0 inset-x-0 z-30 bg-white dark:bg-gray-800
transform translate-y-full transition duration-200 ease-in-out
Expand All @@ -57,13 +43,6 @@
<% items?.forEach(item => { %>
<div class="pl-<%= (item.level - 1) * 4 %>">
<a href="#<%= item.slug %>"
onclick="event.preventDefault();
const target = document.getElementById('<%= item.slug %>');
const header = document.querySelector('header nav');
const offset = header ? header.offsetHeight + 20 : 80;
const targetPosition = target.getBoundingClientRect().top + window.pageYOffset - offset;
window.scrollTo({top: targetPosition, behavior: 'smooth'});
document.getElementById('toc-mobile').classList.add('translate-y-full');"
class="block px-3 py-1 text-sm text-gray-600 dark:text-gray-400 hover:text-gray-900 dark:hover:text-gray-200 hover:bg-gray-50 dark:hover:bg-gray-800 rounded-md transition-colors duration-150 toc-link"
data-slug="<%= item.slug %>">
<%- item.text %>
Expand Down

0 comments on commit e999f87

Please sign in to comment.