Skip to content

Commit

Permalink
fix: use performance observer to ensure to scroll only called when ma…
Browse files Browse the repository at this point in the history
…in thread is not blocked

use PerformanceObserver to ensure that scrollIntoView() is only called when the main thread is not
blocked.

#1183
  • Loading branch information
acd02 committed Aug 8, 2023
1 parent 18fde84 commit e9b0039
Show file tree
Hide file tree
Showing 2 changed files with 52 additions and 0 deletions.
13 changes: 13 additions & 0 deletions documentation/helpers/ToC/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { cva, cx } from 'class-variance-authority'
import { useEffect, useState } from 'react'

import { useActiveAnchor } from './useActiveAnchor'
import { usePerformanceObserver } from './usePerformanceObserver'
import { scrollToAnchor } from './utils'

const itemStyle = cva(['block', 'py-sm', ['hover:bg-[#F2F6FF]']], {
Expand All @@ -28,6 +29,18 @@ export const ToC = () => {
setHeadings([...(document.querySelectorAll<HTMLHeadingElement>('h2, h3') || [])])
}, [])

usePerformanceObserver({
callback() {
const scrollTarget = headings.find(
({ id }) => id === window.top?.location.hash.replace('#', '')
)

scrollTarget?.scrollIntoView({
behavior: 'smooth',
})
},
})

const activeAnchor = useActiveAnchor(headings)
const activeIndex = headings.findIndex(heading => heading.id === activeAnchor?.id)

Expand Down
39 changes: 39 additions & 0 deletions documentation/helpers/ToC/usePerformanceObserver.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import { useEffect, useRef } from 'react'

interface Props {
callback: () => void
}

const CUSTOM_MARK = 'initial rendering'

export function usePerformanceObserver({ callback }: Props) {
const callbackRef = useRef(callback)
const performanceRef = useRef(performance)

useEffect(() => {
callbackRef.current = callback
}, [callback])

useEffect(() => {
const observer = new PerformanceObserver(list => {
for (const entry of list.getEntries()) {
if (entry.entryType === 'longtask' && list.getEntries().length === 1) {
setTimeout(() => callbackRef.current(), 0)
}

if (entry.name === CUSTOM_MARK) {
performanceRef.current.clearMarks(CUSTOM_MARK)
setTimeout(() => callbackRef.current(), 500)
}
}
})

observer.observe({ entryTypes: ['longtask', 'mark'] })

return () => observer.disconnect()
}, [])

useEffect(() => {
performanceRef.current.mark(CUSTOM_MARK)
}, [])
}

0 comments on commit e9b0039

Please sign in to comment.