123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121 |
- import { onMounted, onUnmounted, onUpdated } from 'vue'
- import { isClient } from '@vueuse/core'
- import { throttleAndDebounce } from '../utils'
- import type { Ref } from 'vue'
- export function useActiveSidebarLinks(
- container: Ref<HTMLElement>,
- marker: Ref<HTMLElement>
- ) {
- if (!isClient) return
- const onScroll = throttleAndDebounce(setActiveLink, 150)
- function setActiveLink() {
- const sidebarLinks = getSidebarLinks()
- const anchors = getAnchors(sidebarLinks)
- // Cancel the processing of the anchor point being forced to be the last one in the storefront
- // if (
- // anchors.length &&
- // scrollDom &&
- // scrollDom.scrollTop + scrollDom.clientHeight === scrollDom.scrollHeight
- // ) {
- // activateLink(anchors[anchors.length - 1].hash)
- // return
- // }
- 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) {
- history.replaceState(
- null,
- document.title,
- hash ? (hash as string) : ' '
- )
- activateLink(hash as string)
- return
- }
- }
- }
- let prevActiveLink: HTMLAnchorElement | null = null
- function activateLink(hash: string) {
- deactiveLink(prevActiveLink)
- const activeLink = (prevActiveLink =
- hash == null
- ? null
- : (container.value.querySelector(
- `.toc-item a[href="${decodeURIComponent(hash)}"]`
- ) as HTMLAnchorElement))
- if (activeLink) {
- activeLink.classList.add('active')
- marker.value.style.opacity = '1'
- marker.value.style.top = `${activeLink.offsetTop}px`
- } else {
- marker.value.style.opacity = '0'
- marker.value.style.top = '33px'
- }
- }
- function deactiveLink(link: HTMLElement | null) {
- link && link.classList.remove('active')
- }
- onMounted(() => {
- window.requestAnimationFrame(setActiveLink)
- window.addEventListener('scroll', onScroll)
- })
- onUpdated(() => {
- activateLink(location.hash)
- })
- onUnmounted(() => {
- window.removeEventListener('scroll', onScroll)
- })
- }
- function getSidebarLinks() {
- return Array.from(
- document.querySelectorAll('.toc-content .toc-link')
- ) as HTMLAnchorElement[]
- }
- function getAnchors(sidebarLinks: HTMLAnchorElement[]) {
- return (
- Array.from(
- document.querySelectorAll('.doc-content .header-anchor')
- ) as HTMLAnchorElement[]
- ).filter((anchor) =>
- sidebarLinks.some((sidebarLink) => sidebarLink.hash === anchor.hash)
- )
- }
- function getPageOffset() {
- return (document.querySelector('.navbar') as HTMLElement).offsetHeight
- }
- function getAnchorTop(anchor: HTMLAnchorElement) {
- const pageOffset = getPageOffset()
- try {
- return anchor.parentElement!.offsetTop - pageOffset - 15
- } catch {
- return 0
- }
- }
- function isAnchorActive(
- index: number,
- anchor: HTMLAnchorElement,
- nextAnchor: HTMLAnchorElement
- ) {
- 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, decodeURIComponent(anchor.hash)]
- }
- return [false, null]
- }
|