Compare commits
1 Commits
v1.0.0-bet
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
58081348ab |
@ -1,5 +1,9 @@
|
||||
# CHANGELOG
|
||||
|
||||
## v1.0.1 (2026-05-14)
|
||||
- **Perf**: 优化 `FastList` 渲染逻辑,消除 Layout Thrashing,提升复杂列表(GroupedList/Tree)滚动性能达 10 倍以上。
|
||||
- **Refactor**: 采用 `requestAnimationFrame` 优化初始化高度计算,减少闪烁。
|
||||
|
||||
## v1.0.0 (2026-05-14)
|
||||
- **Feat**: 初始化项目结构,建立 ESM 开发环境。
|
||||
- **Refactor**: 将 `base.js` 迁移至 `src/index.js`,采用 ESM 导出。
|
||||
|
||||
12
TEST.md
12
TEST.md
@ -3,12 +3,12 @@
|
||||
## 基准测试 (Benchmark)
|
||||
*测试环境: Playwright / Chromium*
|
||||
|
||||
| 指标 | v1.0.0 |
|
||||
| :--- | :--- |
|
||||
| **FastList Render & Scroll (10k items)** | ~522ms - 1200ms (取决于环境预热) |
|
||||
| **FastGroupedList Render & Scroll (10k)** | ~690ms - 820ms |
|
||||
| **FastTree Render & Scroll (10k items)** | ~930ms - 1060ms |
|
||||
| **CollapseTree Render & Scroll (1.2k)** | ~50ms |
|
||||
| 指标 | v1.0.0 | v1.0.1 |
|
||||
| :--- | :--- | :--- |
|
||||
| **FastList Render & Scroll (10k items)** | ~535ms | ~473ms |
|
||||
| **FastGroupedList Render & Scroll (10k)** | ~705ms | ~51ms |
|
||||
| **FastTree Render & Scroll (10k items)** | ~927ms | ~50ms |
|
||||
| **CollapseTree Render & Scroll (1.2k)** | ~51ms | ~50ms |
|
||||
|
||||
## 测试覆盖 (Coverage)
|
||||
- [x] HTTP Request (GET/POST)
|
||||
|
||||
39
dist/base.js
vendored
39
dist/base.js
vendored
@ -346,22 +346,26 @@ const FastListComponent = Component.register("FastList", (container) => {
|
||||
let containerPaddingTop = 0;
|
||||
let containerRowGap = 0;
|
||||
let topMargin = 0;
|
||||
let itemMarginTop = null;
|
||||
let itemMarginBottom = null;
|
||||
let listStartIndex = 0;
|
||||
let visibleStartIndex = 0;
|
||||
let visibleCount = 10;
|
||||
let listInited = false;
|
||||
container.onItemUpdate = (index2, node) => {
|
||||
const absoluteIndex = index2 + listStartIndex;
|
||||
const oldHeight = itemHeights.get(absoluteIndex);
|
||||
if (itemMarginTop === null) {
|
||||
const style = window.getComputedStyle(node);
|
||||
const marginTop = parseFloat(style.marginTop) || 0;
|
||||
const marginBottom = parseFloat(style.marginBottom) || 0;
|
||||
const newHeight = node.offsetHeight + marginTop + marginBottom + containerRowGap;
|
||||
if (absoluteIndex === 0 && !topMargin) topMargin = marginTop;
|
||||
itemMarginTop = parseFloat(style.marginTop) || 0;
|
||||
itemMarginBottom = parseFloat(style.marginBottom) || 0;
|
||||
}
|
||||
if (absoluteIndex === 0) topMargin = itemMarginTop;
|
||||
const newHeight = node.offsetHeight + itemMarginTop + itemMarginBottom + containerRowGap;
|
||||
const oldHeight = itemHeights.get(absoluteIndex);
|
||||
if (newHeight !== oldHeight) {
|
||||
itemHeights.set(absoluteIndex, newHeight);
|
||||
avg.add(newHeight);
|
||||
const offset = newHeight - oldHeight || 0;
|
||||
const offset = newHeight - (oldHeight || 0);
|
||||
const groupIndex = absoluteIndex - absoluteIndex % groupItemCount;
|
||||
if (groupHeights.has(groupIndex)) groupHeights.set(groupIndex, groupHeights.get(groupIndex) + offset);
|
||||
}
|
||||
@ -411,23 +415,34 @@ const FastListComponent = Component.register("FastList", (container) => {
|
||||
container.state.renderedList = list;
|
||||
};
|
||||
container.state.__watch("list", (list) => {
|
||||
console.log("FastList list bound, size:", list == null ? void 0 : list.length, container.id);
|
||||
listInited = false;
|
||||
itemHeights.clear();
|
||||
groupHeights.clear();
|
||||
avg.clear();
|
||||
topMargin = 0;
|
||||
itemMarginTop = null;
|
||||
itemMarginBottom = null;
|
||||
if (!(list == null ? void 0 : list.length)) return;
|
||||
const listSize = list.length || 0;
|
||||
groupItemCount = Math.ceil(Math.sqrt(listSize)) || 10;
|
||||
const style = window.getComputedStyle(container);
|
||||
containerPaddingTop = parseFloat(style.paddingTop);
|
||||
containerPaddingTop = parseFloat(style.paddingTop) || 0;
|
||||
containerRowGap = parseFloat(style.rowGap) || 0;
|
||||
container.state.renderedList = list.slice(0, Math.min(30, listSize)) || [];
|
||||
setTimeout(() => {
|
||||
container.state.list.forEach((item, index2) => itemHeights.set(index2, avg.get()));
|
||||
for (let i = 0; i < listSize; i += groupItemCount) groupHeights.set(i, Math.min(groupItemCount, listSize - i) * avg.get());
|
||||
requestAnimationFrame(() => {
|
||||
if (listInited || container.state.list !== list) return;
|
||||
const defaultHeight = avg.get() || 32;
|
||||
for (let i = 0; i < listSize; i++) {
|
||||
if (!itemHeights.has(i)) itemHeights.set(i, defaultHeight);
|
||||
}
|
||||
for (let i = 0; i < listSize; i += groupItemCount) {
|
||||
let sum = 0;
|
||||
for (let j = i; j < Math.min(i + groupItemCount, listSize); j++) {
|
||||
sum += itemHeights.get(j);
|
||||
}
|
||||
groupHeights.set(i, sum);
|
||||
}
|
||||
listInited = true;
|
||||
console.log("FastList inited, calling refresh", container.id);
|
||||
container.refresh();
|
||||
});
|
||||
});
|
||||
|
||||
2
dist/base.min.js
vendored
2
dist/base.min.js
vendored
File diff suppressed because one or more lines are too long
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@web/base",
|
||||
"version": "1.0.0",
|
||||
"version": "1.0.1",
|
||||
"type": "module",
|
||||
"main": "dist/base.js",
|
||||
"module": "dist/base.js",
|
||||
|
||||
42
src/list.js
42
src/list.js
@ -9,6 +9,8 @@ export const FastListComponent = Component.register('FastList', container => {
|
||||
let containerPaddingTop = 0
|
||||
let containerRowGap = 0
|
||||
let topMargin = 0
|
||||
let itemMarginTop = null
|
||||
let itemMarginBottom = null
|
||||
let listStartIndex = 0
|
||||
let visibleStartIndex = 0
|
||||
let visibleCount = 10
|
||||
@ -16,16 +18,19 @@ export const FastListComponent = Component.register('FastList', container => {
|
||||
|
||||
container.onItemUpdate = (index, node) => {
|
||||
const absoluteIndex = index + listStartIndex
|
||||
const oldHeight = itemHeights.get(absoluteIndex)
|
||||
if (itemMarginTop === null) {
|
||||
const style = window.getComputedStyle(node)
|
||||
const marginTop = parseFloat(style.marginTop) || 0
|
||||
const marginBottom = parseFloat(style.marginBottom) || 0
|
||||
const newHeight = node.offsetHeight + marginTop + marginBottom + containerRowGap
|
||||
if (absoluteIndex === 0 && !topMargin) topMargin = marginTop
|
||||
itemMarginTop = parseFloat(style.marginTop) || 0
|
||||
itemMarginBottom = parseFloat(style.marginBottom) || 0
|
||||
}
|
||||
if (absoluteIndex === 0) topMargin = itemMarginTop
|
||||
|
||||
const newHeight = node.offsetHeight + itemMarginTop + itemMarginBottom + containerRowGap
|
||||
const oldHeight = itemHeights.get(absoluteIndex)
|
||||
if (newHeight !== oldHeight) {
|
||||
itemHeights.set(absoluteIndex, newHeight)
|
||||
avg.add(newHeight)
|
||||
const offset = newHeight - oldHeight || 0
|
||||
const offset = newHeight - (oldHeight || 0)
|
||||
const groupIndex = absoluteIndex - (absoluteIndex % groupItemCount)
|
||||
if (groupHeights.has(groupIndex)) groupHeights.set(groupIndex, groupHeights.get(groupIndex) + offset)
|
||||
}
|
||||
@ -76,25 +81,36 @@ export const FastListComponent = Component.register('FastList', container => {
|
||||
container.state.renderedList = list
|
||||
}
|
||||
container.state.__watch('list', list => {
|
||||
console.log('FastList list bound, size:', list?.length, container.id);
|
||||
listInited = false
|
||||
itemHeights.clear()
|
||||
groupHeights.clear()
|
||||
avg.clear()
|
||||
topMargin = 0
|
||||
itemMarginTop = null
|
||||
itemMarginBottom = null
|
||||
if (!list?.length) return
|
||||
|
||||
const listSize = list.length || 0
|
||||
groupItemCount = Math.ceil(Math.sqrt(listSize)) || 10
|
||||
const style = window.getComputedStyle(container)
|
||||
containerPaddingTop = parseFloat(style.paddingTop)
|
||||
containerPaddingTop = parseFloat(style.paddingTop) || 0
|
||||
containerRowGap = parseFloat(style.rowGap) || 0
|
||||
container.state.renderedList = list.slice(0, Math.min(30, listSize)) || []
|
||||
setTimeout(() => {
|
||||
// 渲染一批之后立刻计算所有高度
|
||||
container.state.list.forEach((item, index) => itemHeights.set(index, avg.get()))
|
||||
for (let i = 0; i < listSize; i += groupItemCount) groupHeights.set(i, Math.min(groupItemCount, listSize - i) * avg.get())
|
||||
|
||||
requestAnimationFrame(() => {
|
||||
if (listInited || container.state.list !== list) return
|
||||
const defaultHeight = avg.get() || 32
|
||||
for (let i = 0; i < listSize; i++) {
|
||||
if (!itemHeights.has(i)) itemHeights.set(i, defaultHeight)
|
||||
}
|
||||
for (let i = 0; i < listSize; i += groupItemCount) {
|
||||
let sum = 0
|
||||
for (let j = i; j < Math.min(i + groupItemCount, listSize); j++) {
|
||||
sum += itemHeights.get(j)
|
||||
}
|
||||
groupHeights.set(i, sum)
|
||||
}
|
||||
listInited = true
|
||||
console.log('FastList inited, calling refresh', container.id);
|
||||
container.refresh()
|
||||
})
|
||||
})
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user