⚡ perf(vscode): 代码阅读预览的卡片位置量过就记着,内容或大小变了才重量:粘性标题、记滚动位置、上报可见行、跳转按顶层块二分找,滚动时不再每帧量全部卡片,滚动回调里也不读会逼排版的位置
This commit is contained in:
1 parent
b2e998665e
commit
ffb0f147e1
13 files changed
+380
-102
No files matched your search
@@ -0,0 +1,100 @@
|
||||
// @vitest-environment happy-dom
|
||||
|
||||
import type { BlockBox } from './scrollPosition'
|
||||
import { afterEach, describe, expect, it } from 'vitest'
|
||||
import { PageLayout } from './pageLayout'
|
||||
import { anchorAt, blockForLine } from './scrollPosition'
|
||||
|
||||
/** 量过几次位置 */
|
||||
let reads = 0
|
||||
|
||||
/** 元素的位置写死(happy-dom 不排版),每量一次记一次 */
|
||||
function place<T extends HTMLElement>(el: T, top: number, bottom: number, left = 0, right = 800): T {
|
||||
el.getBoundingClientRect = () => {
|
||||
reads++
|
||||
return { top, bottom, left, right, width: right - left, height: bottom - top, x: left, y: top, toJSON: () => ({}) } as DOMRect
|
||||
}
|
||||
return el
|
||||
}
|
||||
|
||||
function div(className: string, attrs: Record<string, string> = {}, ...children: HTMLElement[]): HTMLElement {
|
||||
const el = document.createElement('div')
|
||||
el.className = className
|
||||
for (const [name, value] of Object.entries(attrs))
|
||||
el.setAttribute(name, value)
|
||||
el.append(...children)
|
||||
return el
|
||||
}
|
||||
|
||||
/** 一层:左边竖线那格、右边一栏 */
|
||||
function level(owner: string | undefined, ...children: HTMLElement[]): HTMLElement {
|
||||
return div('cr-level', owner === undefined ? {} : { 'data-owner': owner }, div('guide'), div('column', {}, ...children))
|
||||
}
|
||||
|
||||
function card(id: string, line: number, top: number, bottom: number, editing = false): HTMLElement {
|
||||
return place(div('cr-block', { 'data-id': id, 'data-line': String(line), ...(editing && { 'data-editing': '' }) }), top, bottom)
|
||||
}
|
||||
|
||||
/**
|
||||
* 三段顶层块,每段 1000px:第 0 段一张卡片和它的两张子卡片(子层到 900 结束),第 1 段一张卡片(在编辑),第 2 段一张卡片。
|
||||
*/
|
||||
function page(): { root: HTMLElement, cards: BlockBox[] } {
|
||||
const root = document.createElement('div')
|
||||
root.append(level(
|
||||
undefined,
|
||||
place(div('cr-item', { 'data-line': '0' }, card('0', 0, 0, 100), place(level('0', card('0.0', 2, 120, 300), card('0.1', 8, 320, 500)), 110, 900)), 0, 1000),
|
||||
place(div('cr-item', { 'data-line': '20' }, card('1', 20, 1010, 1500, true)), 1010, 1500),
|
||||
place(div('cr-item', { 'data-line': '40' }, card('2', 40, 1510, 2000)), 1510, 2000),
|
||||
))
|
||||
document.body.append(root)
|
||||
const cards = [[0, 0, 100], [2, 120, 300], [8, 320, 500], [20, 1010, 1500], [40, 1510, 2000]].map(([line, top, bottom]) => ({ line, top, bottom }))
|
||||
return { root, cards }
|
||||
}
|
||||
|
||||
afterEach(() => {
|
||||
document.body.innerHTML = ''
|
||||
reads = 0
|
||||
})
|
||||
|
||||
describe('卡片位置量过就记着', () => {
|
||||
it('滚动位置、包含某一行的块:和把全部卡片量一遍算出来的一样', () => {
|
||||
const { root, cards } = page()
|
||||
const layout = new PageLayout()
|
||||
layout.attach(root, null)
|
||||
for (const y of [0, 50, 100, 310, 950, 1200, 1999, 2500]) {
|
||||
const found = layout.anchorAt(y)
|
||||
expect(found && { line: found.line, offset: found.offset }, `y ${y}`).toEqual((({ line, offset }) => ({ line, offset }))(anchorAt(cards, y)!))
|
||||
}
|
||||
for (const line of [0, 1, 5, 9, 25, 100])
|
||||
expect(layout.cardForLine(line)?.line, `line ${line}`).toBe(blockForLine(cards, line)?.line)
|
||||
expect(layout.topFor({ line: 8, offset: 5, scrollTop: 9999 })).toBe(325)
|
||||
expect(layout.topFor({ line: 9, offset: 5, scrollTop: 9999 })).toBe(9999)
|
||||
})
|
||||
|
||||
it('钉住的卡片:只算和顶上那一段相交的顶层块,在编辑的不钉;外层卡片的下沿算到它的子层', () => {
|
||||
const { root } = page()
|
||||
const layout = new PageLayout()
|
||||
layout.attach(root, null)
|
||||
window.scrollTo(0, 0)
|
||||
const scopes = layout.stickyScopes(48)
|
||||
expect(scopes.map(scope => [scope.id, scope.depth, scope.top, scope.bottom])).toEqual([['0', 0, 0, 900]])
|
||||
})
|
||||
|
||||
it('滚动时不再量:同一份内容问几次都只量一次;只量用到的那几段;内容变了才重量', async () => {
|
||||
const { root } = page()
|
||||
const layout = new PageLayout()
|
||||
layout.attach(root, null)
|
||||
layout.anchorAt(0)
|
||||
// 三段顶层块的外框 + 第 0、1 段里的卡片和子层
|
||||
const first = reads
|
||||
expect(first).toBe(3 + 4 + 1)
|
||||
for (const y of [0, 10, 20, 300, 600])
|
||||
layout.anchorAt(y)
|
||||
layout.stickyScopes(48)
|
||||
expect(reads).toBe(first)
|
||||
root.querySelector('.cr-item')!.append(div('cr-qa'))
|
||||
await Promise.resolve()
|
||||
layout.anchorAt(0)
|
||||
expect(reads).toBe(first * 2)
|
||||
})
|
||||
})
|
||||
Reference in new issue
Block a user