import { throttle } from 'lodash-es'
let links: HTMLAnchorElement[] = []
// 导航栏高度
const NAV_HEIGHT = 56
export function bindingAsideScroll() {
const marker = document.getElementById('aside-marker')
const aside = document.getElementById('aside-container')
const headers = Array.from(aside?.getElementsByTagName('a') || []).map(
(item) => decodeURIComponent(item.hash) // => hash=#head
)
if (!aside) {
return {
setActiveLink: () => {},
unbinding: () => {},
}
}
const activate = (links: HTMLAnchorElement[], index: number) => {
if (links[index]) {
const id = links[index].getAttribute('href')
const tocIndex = headers.findIndex((item) => item === id)
const currentLink = aside?.querySelector(`a[href="#${id.slice(1)}"]`)
if (currentLink) {
// 设置高亮样式
marker.style.top = `${33 + tocIndex * 28}px` // 33 为group的title高度,28为每个小标题的高度
marker.style.opacity = '1'
}
}
}
const setActiveLink = () => {
links = Array.from(
document.querySelectorAll('.island-doc .header-anchor')
).filter((item) => item.parentElement?.tagName !== 'H1')
const isBottom =
document.documentElement.scrollTop + window.innerHeight >=
document.documentElement.scrollHeight
// 1. 如果已经滑动到底部,我们将最后一个 link 高亮即可
if (isBottom) {
activate(links, links.length - 1)
return
}
// 2. 遍历 links,寻找对应锚点
for (let i = 0; i < links.length; i++) {
const currentAnchor = links[i]
const nextAnchor = links[i + 1]
const scrollTop = Math.ceil(window.scrollY)
const currentAnchorTop =
currentAnchor.parentElement.offsetTop - NAV_HEIGHT
// 高亮最后一个锚点
if (!nextAnchor) {
activate(links, i)
break
}
// 高亮第一个锚点的情况
if ((i === 0 && scrollTop < currentAnchorTop) || scrollTop == 0) {
activate(links, 0)
break
}
// 如果当前 scrollTop 在 i 和 i + 1 个锚点之间
const nextAnchorTop = nextAnchor.parentElement.offsetTop - NAV_HEIGHT
if (scrollTop >= currentAnchorTop && scrollTop < nextAnchorTop) {
activate(links, i)
break
}
}
}
const throttled = throttle(setActiveLink, 100)
window.addEventListener('scroll', throttled)
// 返回事件解绑逻辑,供 TOC 组件调用,避免内存泄露
return {
setActiveLink, // 立即执行,初始化高亮位置
unbinding: () => window.removeEventListener('scroll', throttled),
}
}
export function scrollToTarget(target: HTMLElement, isSmooth: boolean) {
const targetPadding = parseInt(window.getComputedStyle(target).paddingTop, 10)
const targetTop =
window.scrollY +
target.getBoundingClientRect().top +
targetPadding -
NAV_HEIGHT // 减去导航栏高度,防止被导航栏遮挡
window.scrollTo({
left: 0,
top: targetTop,
behavior: isSmooth ? 'smooth' : 'auto',
})
}