mirror of https://github.com/vuejs/vitepress
parent
b1537a78ba
commit
d2ea9637ee
@ -0,0 +1,84 @@
|
||||
import { onMounted, onUnmounted, onUpdated } from 'vue'
|
||||
|
||||
export function useActiveSidebarLinks() {
|
||||
let rootActiveLink: HTMLAnchorElement | null = null
|
||||
let activeLink: HTMLAnchorElement | null = null
|
||||
const decode = decodeURIComponent
|
||||
|
||||
const deactiveLink = (link: HTMLAnchorElement | null) =>
|
||||
link && link.classList.remove('active')
|
||||
|
||||
const activateLink = (hash: string) => {
|
||||
deactiveLink(activeLink)
|
||||
deactiveLink(rootActiveLink)
|
||||
activeLink = document.querySelector(`.sidebar a[href="${hash}"]`)
|
||||
if (activeLink) {
|
||||
activeLink.classList.add('active')
|
||||
// also add active class to parent h2 anchors
|
||||
const rootLi = activeLink.closest('.sidebar > ul > li')
|
||||
if (rootLi && rootLi !== activeLink.parentElement) {
|
||||
rootActiveLink = rootLi.querySelector('a')
|
||||
rootActiveLink && rootActiveLink.classList.add('active')
|
||||
} else {
|
||||
rootActiveLink = null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const setActiveLink = () => {
|
||||
const sidebarLinks = [].slice.call(
|
||||
document.querySelectorAll('.sidebar a')
|
||||
) as HTMLAnchorElement[]
|
||||
|
||||
const anchors = [].slice
|
||||
.call(document.querySelectorAll('.header-anchor'))
|
||||
.filter((anchor: HTMLAnchorElement) =>
|
||||
sidebarLinks.some((sidebarLink) => sidebarLink.hash === anchor.hash)
|
||||
) as HTMLAnchorElement[]
|
||||
|
||||
const pageOffset = document.getElementById('app')!.offsetTop
|
||||
const scrollTop = window.scrollY
|
||||
|
||||
const getAnchorTop = (anchor: HTMLAnchorElement): number =>
|
||||
anchor.parentElement!.offsetTop - pageOffset - 15
|
||||
|
||||
for (let i = 0; i < anchors.length; i++) {
|
||||
const anchor = anchors[i]
|
||||
const nextAnchor = anchors[i + 1]
|
||||
const isActive =
|
||||
(i === 0 && scrollTop === 0) ||
|
||||
(scrollTop >= getAnchorTop(anchor) &&
|
||||
(!nextAnchor || scrollTop < getAnchorTop(nextAnchor)))
|
||||
|
||||
if (isActive) {
|
||||
const targetHash = decode(anchor.hash)
|
||||
history.replaceState(null, document.title, targetHash)
|
||||
activateLink(targetHash)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const onScroll = debounce(setActiveLink, 100)
|
||||
onMounted(() => {
|
||||
setActiveLink()
|
||||
window.addEventListener('scroll', onScroll)
|
||||
})
|
||||
|
||||
onUpdated(() => {
|
||||
// sidebar update means a route change
|
||||
activateLink(decode(location.hash))
|
||||
})
|
||||
|
||||
onUnmounted(() => {
|
||||
window.removeEventListener('scroll', onScroll)
|
||||
})
|
||||
}
|
||||
|
||||
function debounce(fn: () => void, delay: number): () => void {
|
||||
let timeout: number
|
||||
return () => {
|
||||
if (timeout) clearTimeout(timeout)
|
||||
timeout = setTimeout(fn, delay)
|
||||
}
|
||||
}
|
Loading…
Reference in new issue