diff --git a/themes/hexo/components/Catalog.js b/themes/hexo/components/Catalog.js index 880b05d5..3252f2ec 100644 --- a/themes/hexo/components/Catalog.js +++ b/themes/hexo/components/Catalog.js @@ -1,4 +1,5 @@ import React, { useRef } from 'react' +import throttle from 'lodash.throttle' import { uuidToId } from 'notion-utils' import Progress from './Progress' import { useGlobal } from '@/lib/global' @@ -27,34 +28,33 @@ const Catalog = ({ toc }) => { // 同步选中目录事件 const [activeSection, setActiveSection] = React.useState(null) - const actionSectionScrollSpy = () => { - requestAnimationFrame(() => { - const sections = document.getElementsByClassName('notion-h') - let prevBBox = null - let currentSectionId = activeSection - for (let i = 0; i < sections.length; ++i) { - const section = sections[i] - if (!section || !(section instanceof Element)) continue - if (!currentSectionId) { - currentSectionId = section.getAttribute('data-id') - } - const bbox = section.getBoundingClientRect() - const prevHeight = prevBBox ? bbox.top - prevBBox.bottom : 0 - const offset = Math.max(150, prevHeight / 4) - // GetBoundingClientRect returns values relative to viewport - if (bbox.top - offset < 0) { - currentSectionId = section.getAttribute('data-id') - prevBBox = bbox - continue - } - // No need to continue loop, if last element has been detected - break + const throttleMs = 100 + const actionSectionScrollSpy = React.useCallback(throttle(() => { + const sections = document.getElementsByClassName('notion-h') + let prevBBox = null + let currentSectionId = activeSection + for (let i = 0; i < sections.length; ++i) { + const section = sections[i] + if (!section || !(section instanceof Element)) continue + if (!currentSectionId) { + currentSectionId = section.getAttribute('data-id') } - setActiveSection(currentSectionId) - const index = tocIds.indexOf(currentSectionId) || 0 - tRef?.current?.scrollTo({ top: 28 * index, behavior: 'smooth' }) - }) - } + const bbox = section.getBoundingClientRect() + const prevHeight = prevBBox ? bbox.top - prevBBox.bottom : 0 + const offset = Math.max(150, prevHeight / 4) + // GetBoundingClientRect returns values relative to viewport + if (bbox.top - offset < 0) { + currentSectionId = section.getAttribute('data-id') + prevBBox = bbox + continue + } + // No need to continue loop, if last element has been detected + break + } + setActiveSection(currentSectionId) + const index = tocIds.indexOf(currentSectionId) || 0 + tRef?.current?.scrollTo({ top: 28 * index, behavior: 'smooth' }) + }, throttleMs)) // 无目录就直接返回空 if (!toc || toc.length < 1) { diff --git a/themes/matery/components/Catalog.js b/themes/matery/components/Catalog.js index 33bebb0a..a86326e3 100644 --- a/themes/matery/components/Catalog.js +++ b/themes/matery/components/Catalog.js @@ -1,4 +1,5 @@ import React, { useRef } from 'react' +import throttle from 'lodash.throttle' import { uuidToId } from 'notion-utils' import Progress from './Progress' import { useGlobal } from '@/lib/global' @@ -13,8 +14,8 @@ const Catalog = ({ toc }) => { const { locale } = useGlobal() // 监听滚动事件 React.useEffect(() => { - actionSectionScrollSpy() window.addEventListener('scroll', actionSectionScrollSpy) + actionSectionScrollSpy() return () => { window.removeEventListener('scroll', actionSectionScrollSpy) } @@ -26,34 +27,33 @@ const Catalog = ({ toc }) => { // 同步选中目录事件 const [activeSection, setActiveSection] = React.useState(null) - const actionSectionScrollSpy = () => { - requestAnimationFrame(() => { - const sections = document.getElementsByClassName('notion-h') - let prevBBox = null - let currentSectionId = activeSection - for (let i = 0; i < sections.length; ++i) { - const section = sections[i] - if (!section || !(section instanceof Element)) continue - if (!currentSectionId) { - currentSectionId = section.getAttribute('data-id') - } - const bbox = section.getBoundingClientRect() - const prevHeight = prevBBox ? bbox.top - prevBBox.bottom : 0 - const offset = Math.max(150, prevHeight / 4) - // GetBoundingClientRect returns values relative to viewport - if (bbox.top - offset < 0) { - currentSectionId = section.getAttribute('data-id') - prevBBox = bbox - continue - } - // No need to continue loop, if last element has been detected - break + const throttleMs = 100 + const actionSectionScrollSpy = React.useCallback(throttle(() => { + const sections = document.getElementsByClassName('notion-h') + let prevBBox = null + let currentSectionId = activeSection + for (let i = 0; i < sections.length; ++i) { + const section = sections[i] + if (!section || !(section instanceof Element)) continue + if (!currentSectionId) { + currentSectionId = section.getAttribute('data-id') } - setActiveSection(currentSectionId) - const index = tocIds.indexOf(currentSectionId) || 0 - tRef?.current?.scrollTo({ top: 28 * index, behavior: 'smooth' }) - }) - } + const bbox = section.getBoundingClientRect() + const prevHeight = prevBBox ? bbox.top - prevBBox.bottom : 0 + const offset = Math.max(150, prevHeight / 4) + // GetBoundingClientRect returns values relative to viewport + if (bbox.top - offset < 0) { + currentSectionId = section.getAttribute('data-id') + prevBBox = bbox + continue + } + // No need to continue loop, if last element has been detected + break + } + setActiveSection(currentSectionId) + const index = tocIds.indexOf(currentSectionId) || 0 + tRef?.current?.scrollTo({ top: 28 * index, behavior: 'smooth' }) + }, throttleMs)) // 无目录就直接返回空 if (!toc || toc.length < 1) {