UNPKG

valaxy-theme-sakura

Version:

<h1 align="center">valaxy-theme-sakura</h1> <pre align="center"> 一个简单、个性化、可爱的动漫风格博客主题 ❥(ゝω・✿ฺ) </pre>

144 lines (114 loc) 3.74 kB
import type { Ref } from 'vue' import { throttleAndDebounce } from 'valaxy' import { onMounted, onUnmounted, onUpdated } from 'vue' import { useAside } from './aside' // magic number to avoid repeated retrieval const PAGE_OFFSET = 56 const topOffset = 33 export function useActiveAnchor( container: Ref<HTMLElement>, marker: Ref<HTMLElement>, enableScrollIntoView = false, ) { const { isAsideEnabled } = useAside() const onScroll = throttleAndDebounce(setActiveLink, 100) let prevActiveLink: HTMLAnchorElement | null = null onMounted(() => { requestAnimationFrame(setActiveLink) window.addEventListener('scroll', onScroll) }) onUpdated(() => { // sidebar update means a route change activateLink(location.hash) }) onUnmounted(() => { window.removeEventListener('scroll', onScroll) }) function setActiveLink() { if (!isAsideEnabled.value) return const links = [].slice.call( container.value.querySelectorAll('.outline-link'), ) as HTMLAnchorElement[] const anchors = [].slice.call(document.querySelectorAll('.content .header-anchor')).filter((anchor: HTMLAnchorElement) => { return links.some((link) => { return link.hash === anchor.hash && anchor.offsetParent !== null }) }) as HTMLAnchorElement[] const scrollY = window.scrollY const innerHeight = window.innerHeight const offsetHeight = container.value.offsetHeight const isBottom = Math.abs(scrollY + innerHeight - offsetHeight) < 1 // page bottom - highlight last one if (anchors.length && isBottom) { activateLink(anchors[anchors.length - 1].hash) // activateLink(null) return } // isTop // if (anchors.length && scrollY === 0) // activateLink('#') for (let i = 0; i < anchors.length; i++) { const anchor = anchors[i] const nextAnchor = anchors[i + 1] const [isActive, hash] = isAnchorActive(i, anchor, nextAnchor) if (isActive) { activateLink(hash) return } } } const checkActiveLinkInViewport = () => { const activeLink = prevActiveLink if (!activeLink) { // container.value.scrollIntoView({ behavior: 'smooth', block: 'start' }) return } if (enableScrollIntoView) return const top = activeLink.getBoundingClientRect().top const bottom = activeLink.getBoundingClientRect().bottom if (top < topOffset || bottom > window.innerHeight - topOffset) activeLink.scrollIntoView() } function activateLink(hash: string | null) { if (prevActiveLink) prevActiveLink.classList.remove('active') if (hash == null) { prevActiveLink = null } else { prevActiveLink = container.value.querySelector( `a[href="${decodeURIComponent(hash)}"]`, ) } const activeLink = prevActiveLink checkActiveLinkInViewport() if (activeLink) { activeLink.classList.add('active') marker.value.style.top = `${activeLink.offsetTop + topOffset}px` marker.value.style.opacity = '1' } else { marker.value.style.top = `${topOffset}px` marker.value.style.opacity = '0' } } } function getAnchorTop(anchor: HTMLAnchorElement): number { return anchor.parentElement!.offsetTop - PAGE_OFFSET - 15 } function isAnchorActive( index: number, anchor: HTMLAnchorElement, nextAnchor: HTMLAnchorElement | undefined, ): [boolean, string | null] { const scrollTop = window.scrollY if (index === 0 && scrollTop === 0) return [true, null] if (scrollTop < getAnchorTop(anchor)) return [false, null] if (!nextAnchor || scrollTop < getAnchorTop(nextAnchor)) return [true, anchor.hash] return [false, null] }