v1.4.3 - 垂直虚拟滚动优化

This commit is contained in:
LINING-PC\lining
2025-11-28 21:33:37 +08:00
parent e4f3cf507d
commit fc1799b4a3
3 changed files with 99 additions and 19 deletions
+30 -6
View File
@@ -746,12 +746,12 @@ const debounce = <T extends (...args: unknown[]) => void>(func: T, wait: number)
// 优化的滚动处理器(增加防抖时间到 50ms)
const debouncedUpdatePositions = debounce(() => {
computeAllMilestonesPositions()
}, 50)
}, 200)
// 虚拟渲染:防抖更新 Canvas 位置(滚动时触发)
const debouncedUpdateCanvasPosition = debounce(() => {
updateSvgSize() // 重新计算 Canvas 位置和尺寸
}, 50)
}, 200)
// 防抖更新纵向滚动位置
const debouncedUpdateVerticalScroll = debounce((scrollTop: number) => {
@@ -1972,17 +1972,20 @@ const svgHeight = ref(0)
const canvasWidth = ref(0)
const canvasHeight = ref(0)
const canvasOffsetLeft = ref(0) // Canvas 在全局坐标系中的偏移量
const canvasOffsetTop = ref(0)
// 虚拟渲染 Canvas 的安全宽度(防止超过浏览器限制)
// 可根据实际需求调整:
// - 5000: 最小内存 (~30MB),适合低端设备,但滚动时更频繁更新
// - 10000: 平衡选择 (~60MB),覆盖小时视图 10 天,周视图 2 年
const SAFE_CANVAS_WIDTH = 5000 // 平衡性能和覆盖范围
const SAFE_CANVAS_HEIGHT = 5000
function updateSvgSize() {
if (bodyContentRef.value) {
// 获取 bodyContent 的总宽度和可视区域宽度
const totalWidth = bodyContentRef.value.offsetWidth
const totalHeight = contentHeight.value
// 使用已经维护的 timelineScrollLeft,而不是从 DOM 重新读取
// 因为 handleTimelineScroll 已经实时更新了这个值
@@ -2009,9 +2012,23 @@ function updateSvgSize() {
canvasOffsetLeft.value = idealOffsetLeft
const clampedHeight = Math.min(totalHeight, SAFE_CANVAS_HEIGHT)
canvasHeight.value = clampedHeight
svgWidth.value = canvasWidth.value
svgHeight.value = contentHeight.value
canvasHeight.value = contentHeight.value
svgHeight.value = clampedHeight
const scrollTop = timelineBodyScrollTop.value
const bufferTop = clampedHeight / 3
let idealOffsetTop = Math.max(0, scrollTop - bufferTop)
if (totalHeight <= clampedHeight) {
idealOffsetTop = 0
} else {
const maxOffsetTop = totalHeight - clampedHeight
idealOffsetTop = Math.min(idealOffsetTop, maxOffsetTop)
}
canvasOffsetTop.value = idealOffsetTop
}
}
@@ -2035,7 +2052,9 @@ function handleBarMounted(payload: {
height: payload.height,
},
}
updateSvgSize()
setTimeout(() => {
updateSvgSize()
}, 200)
}
// 向上传递 TaskBar 拖拽/拉伸事件
@@ -2200,6 +2219,8 @@ const handleTaskListVerticalScroll = (event: CustomEvent) => {
// 立即更新纵向滚动位置(用于虚拟滚动计算)
timelineBodyScrollTop.value = scrollTop
debouncedUpdateCanvasPosition()
if (timelineBodyElement.value && Math.abs(timelineBodyElement.value.scrollTop - scrollTop) > 1) {
// 使用更精确的比较,避免1px以内的细微差异导致的循环触发
timelineBodyElement.value.scrollTop = scrollTop
@@ -2216,6 +2237,8 @@ const handleTimelineBodyScroll = (event: Event) => {
// 立即更新纵向滚动位置(用于虚拟滚动计算)
timelineBodyScrollTop.value = scrollTop
debouncedUpdateCanvasPosition()
// 拖拽时不同步滚动事件,避免性能问题
if (isDragging.value) return
@@ -2830,7 +2853,7 @@ watch([timelineData, timelineContainerWidth], () => {
nextTick(() => {
setTimeout(() => {
updateSvgSize()
}, 50)
}, 200)
})
taskBarRenderTimer = null
}, 100)
@@ -3368,6 +3391,7 @@ const handleAddSuccessor = (task: Task) => {
:width="canvasWidth"
:height="canvasHeight"
:offset-left="canvasOffsetLeft"
:offset-top="canvasOffsetTop"
:highlighted-task-id="highlightedTaskId"
:highlighted-task-ids="highlightedTaskIds"
:hovered-task-id="hoveredTaskId"