⚡ perf(vscode): 代码阅读预览的卡片位置量过就记着,内容或大小变了才重量:粘性标题、记滚动位置、上报可见行、跳转按顶层块二分找,滚动时不再每帧量全部卡片,滚动回调里也不读会逼排版的位置
This commit is contained in:
1 parent
b2e998665e
commit
ffb0f147e1
13 files changed
+380
-102
No files matched your search
@@ -1,18 +1,15 @@
|
||||
import type { BlockBox } from './scrollPosition'
|
||||
import type { DocumentMessage } from './types'
|
||||
import { useEffect, useLayoutEffect, useRef, useState } from 'react'
|
||||
import { anchorAt, scrollTopFor } from './scrollPosition'
|
||||
import { pageLayout } from './pageLayout'
|
||||
import { postMessage, viewState } from './vscode'
|
||||
|
||||
/** 滚动时最多这么久记一次位置 */
|
||||
const RECORD_INTERVAL_MS = 100
|
||||
|
||||
/** 顶部钉住的筛选栏的高度:视口里露出块的部分从它下面开始,记位置、滚过去都按这部分算。 */
|
||||
export function pinnedHeight(): number {
|
||||
return document.querySelector<HTMLElement>('.cr-filter-bar')?.offsetHeight ?? 0
|
||||
}
|
||||
|
||||
/** 筛选栏有多高,跟着它变(换行、多出第二行时):钉在它下面的东西(总览条、钉住的卡片)从这儿开始。 */
|
||||
/**
|
||||
* 筛选栏有多高,跟着它变(换行、多出第二行时):钉在它下面的东西(总览条、钉住的卡片)从这儿开始。
|
||||
* 大小变了从 ResizeObserver 报来的尺寸里取,不回头读它的 offsetHeight(那要先排版)。
|
||||
*/
|
||||
export function usePinnedHeight(): number {
|
||||
const [height, setHeight] = useState(0)
|
||||
useLayoutEffect(() => {
|
||||
@@ -20,25 +17,16 @@ export function usePinnedHeight(): number {
|
||||
if (!bar)
|
||||
return
|
||||
setHeight(bar.offsetHeight)
|
||||
const observer = new ResizeObserver(() => setHeight(bar.offsetHeight))
|
||||
const observer = new ResizeObserver(([entry]) => setHeight(entry.borderBoxSize[0]?.blockSize ?? bar.offsetHeight))
|
||||
observer.observe(bar)
|
||||
return () => observer.disconnect()
|
||||
}, [])
|
||||
return height
|
||||
}
|
||||
|
||||
/** 页面上每块(BlockCard 带 data-line)的位置,按页面上的先后;被筛掉的块不在页面上,不算。 */
|
||||
export function measureBlocks(): BlockBox[] {
|
||||
const y = window.scrollY
|
||||
return Array.from(document.querySelectorAll<HTMLElement>('.cr-block[data-line]'), (el) => {
|
||||
const rect = el.getBoundingClientRect()
|
||||
return { line: Number(el.dataset.line), top: rect.top + y, bottom: rect.bottom + y }
|
||||
})
|
||||
}
|
||||
|
||||
/** 记下视口顶部那块;它的起始行变了才报给扩展端。 */
|
||||
function record(reportedLine: { current: number | undefined }): void {
|
||||
const anchor = anchorAt(measureBlocks(), window.scrollY + pinnedHeight())
|
||||
const anchor = pageLayout.anchorAt(window.scrollY + pageLayout.pinnedHeight())
|
||||
if (!anchor)
|
||||
return
|
||||
viewState.update({ scroll: anchor })
|
||||
@@ -53,6 +41,7 @@ function record(reportedLine: { current: number | undefined }): void {
|
||||
* ① 滚动时把视口顶部那块记进 webview 状态,页面重新加载后(VS Code 重启后恢复标签页、webview 进程崩了重建)照着滚回去;
|
||||
* ② 同时把那块的起始行报给扩展端,切回文本编辑器时把这行放到顶部。
|
||||
* 第一份文档带 reveal(刚从文本编辑器切过来、跳到定义)时不恢复,交给 useReveal 滚。
|
||||
* 块的位置从 pageLayout 取:滚动时用量过的,不每次量全部卡片。
|
||||
*/
|
||||
export function useScrollPosition(doc: DocumentMessage | null): void {
|
||||
const restored = useRef(false)
|
||||
@@ -64,9 +53,8 @@ export function useScrollPosition(doc: DocumentMessage | null): void {
|
||||
return
|
||||
restored.current = true
|
||||
const saved = viewState.get().scroll
|
||||
const top = doc.reveal === undefined && saved ? scrollTopFor(measureBlocks(), saved) : undefined
|
||||
if (top !== undefined)
|
||||
window.scrollTo(0, top - pinnedHeight())
|
||||
if (doc.reveal === undefined && saved)
|
||||
window.scrollTo(0, pageLayout.topFor(saved) - pageLayout.pinnedHeight())
|
||||
record(reportedLine)
|
||||
}, [doc])
|
||||
|
||||
|
||||
Reference in new issue
Block a user