test02/node_modules/@vuepress/plugin-active-header-links/lib/client/composables/useActiveHeaderLinks.js
罗佳鸿 6aa1ebe342
Some checks are pending
部署文档 / deploy-gh-pages (push) Waiting to run
first commit
2024-08-13 10:11:19 +08:00

76 lines
3.8 KiB
JavaScript

import { useDebounceFn, useEventListener } from '@vueuse/core';
import { useRouter } from 'vuepress/client';
/**
* Update current hash and do not trigger `scrollBehavior`
*/
const updateHash = async (router, hash) => {
const { path, query } = router.currentRoute.value;
const { scrollBehavior } = router.options;
// temporarily disable `scrollBehavior`
router.options.scrollBehavior = undefined;
await router.replace({ path, query, hash });
// restore it after navigation
router.options.scrollBehavior = scrollBehavior;
};
export const useActiveHeaderLinks = ({ headerLinkSelector, headerAnchorSelector, delay, offset = 5, }) => {
const router = useRouter();
const setActiveRouteHash = () => {
// get current scrollTop
const scrollTop = Math.max(window.scrollY, document.documentElement.scrollTop, document.body.scrollTop);
// check if we are at page top
const isAtPageTop = Math.abs(scrollTop - 0) < offset;
// replace current route hash with empty string when scrolling back to the top
if (isAtPageTop) {
updateHash(router, '');
return;
}
// get current scrollBottom
const scrollBottom = window.innerHeight + scrollTop;
// get the total scroll length of current page
const scrollHeight = Math.max(document.documentElement.scrollHeight, document.body.scrollHeight);
// check if we have reached page bottom
// notice the `scrollBottom` might not be exactly equal to `scrollHeight`, so we add offset here
const isAtPageBottom = Math.abs(scrollHeight - scrollBottom) < offset;
// get all header links
const headerLinks = Array.from(document.querySelectorAll(headerLinkSelector));
// get all header anchors
const headerAnchors = Array.from(document.querySelectorAll(headerAnchorSelector));
// filter anchors that do not have corresponding links
const existedHeaderAnchors = headerAnchors.filter((anchor) => headerLinks.some((link) => link.hash === anchor.hash));
for (let i = 0; i < existedHeaderAnchors.length; i++) {
const anchor = existedHeaderAnchors[i];
const nextAnchor = existedHeaderAnchors[i + 1];
// notice the `scrollTop` might not be exactly equal to `offsetTop` after clicking the anchor
// so we add offset
// if has scrolled past this anchor
const hasPassedCurrentAnchor = scrollTop >= (anchor.parentElement?.offsetTop ?? 0) - offset;
// if has not scrolled past next anchor
const hasNotPassedNextAnchor = !nextAnchor ||
scrollTop < (nextAnchor.parentElement?.offsetTop ?? 0) - offset;
// if this anchor is the active anchor
const isActive = hasPassedCurrentAnchor && hasNotPassedNextAnchor;
// continue to find the active anchor
if (!isActive)
continue;
const routeHash = decodeURIComponent(router.currentRoute.value.hash);
const anchorHash = decodeURIComponent(anchor.hash);
// if the active anchor hash is current route hash, do nothing
if (routeHash === anchorHash)
return;
// check if anchor is at the bottom of the page to keep hash consistent
if (isAtPageBottom) {
for (let j = i + 1; j < existedHeaderAnchors.length; j++) {
// if current route hash is below the active hash, do nothing
if (routeHash === decodeURIComponent(existedHeaderAnchors[j].hash)) {
return;
}
}
}
// replace current route hash with the active anchor hash
updateHash(router, anchorHash);
return;
}
};
useEventListener('scroll', useDebounceFn(setActiveRouteHash, delay));
};