Spaces:
Running
Running
| /** | |
| * Resize处理器 | |
| * 负责监听容器大小变化并智能更新SVG位置 | |
| */ | |
| // todo: 接口设计评审改进 | |
| export interface ResizeHandlerOptions { | |
| /** 快速变化阈值(毫秒) */ | |
| rapidResizeThresholdMs?: number; | |
| /** 快速变化计数阈值 */ | |
| rapidResizeCountThreshold?: number; | |
| /** 防抖时间(毫秒) */ | |
| resizeDebounceMs?: number; | |
| /** 位置更新回调 */ | |
| onPositionUpdate: () => void; | |
| /** 获取当前SVG元素 */ | |
| getCurrentSvg: () => SVGSVGElement | undefined; | |
| /** 过渡开始回调(快速resize时调用) */ | |
| onTransitionStart?: () => void; | |
| } | |
| export class ResizeHandler { | |
| private resizeObserver?: ResizeObserver; | |
| private baseNode: HTMLElement; | |
| private options: Required<Omit<ResizeHandlerOptions, 'onPositionUpdate' | 'getCurrentSvg'>> & Pick<ResizeHandlerOptions, 'onPositionUpdate' | 'getCurrentSvg'>; | |
| // 智能检测相关状态 | |
| private lastResizeTime = 0; | |
| private resizeEventCount = 0; | |
| private resizeEndTimer?: number; | |
| private positionUpdateTimer?: number; | |
| constructor(baseNode: HTMLElement, options: ResizeHandlerOptions) { | |
| this.baseNode = baseNode; | |
| this.options = { | |
| rapidResizeThresholdMs: options.rapidResizeThresholdMs ?? 100, | |
| rapidResizeCountThreshold: options.rapidResizeCountThreshold ?? 3, | |
| resizeDebounceMs: options.resizeDebounceMs ?? 100, | |
| onPositionUpdate: options.onPositionUpdate, | |
| getCurrentSvg: options.getCurrentSvg, | |
| onTransitionStart: options.onTransitionStart, | |
| }; | |
| } | |
| /** | |
| * 设置ResizeObserver,监听容器大小变化并更新SVG rect位置 | |
| */ | |
| setup(): void { | |
| // 如果已经设置了,就不重复设置 | |
| if (this.resizeObserver) { | |
| return; | |
| } | |
| // 创建ResizeObserver,使用智能检测 | |
| this.resizeObserver = new ResizeObserver((entries) => { | |
| const now = Date.now(); | |
| const timeSinceLastResize = now - this.lastResizeTime; | |
| // 检测是否是快速连续变化(过渡中) | |
| const isRapidChange = timeSinceLastResize < this.options.rapidResizeThresholdMs; | |
| if (isRapidChange) { | |
| // 快速连续变化,增加计数 | |
| this.resizeEventCount++; | |
| } else { | |
| // 不是快速连续变化,重置计数 | |
| this.resizeEventCount = 1; | |
| } | |
| this.lastResizeTime = now; | |
| // 判断是否是"过渡中"(快速连续变化) | |
| const isInTransition = this.resizeEventCount >= this.options.rapidResizeCountThreshold; | |
| if (isInTransition) { | |
| this.handleRapidResize(); | |
| } else { | |
| this.handleSingleResize(); | |
| } | |
| }); | |
| // 开始观察容器 | |
| this.resizeObserver.observe(this.baseNode); | |
| } | |
| /** | |
| * 处理快速连续变化(过渡中) | |
| */ | |
| private handleRapidResize(): void { | |
| const svg = this.options.getCurrentSvg(); | |
| // 过渡中:隐藏SVG,等待稳定后更新 | |
| if (svg && svg.style.opacity !== '0') { | |
| svg.style.opacity = '0'; | |
| svg.style.pointerEvents = 'none'; | |
| } | |
| // 调用过渡开始回调(隐藏minimap等) | |
| if (this.options.onTransitionStart) { | |
| this.options.onTransitionStart(); | |
| } | |
| // 取消待处理的位置更新 | |
| if (this.positionUpdateTimer !== undefined) { | |
| cancelAnimationFrame(this.positionUpdateTimer); | |
| this.positionUpdateTimer = undefined; | |
| } | |
| // 取消之前的结束检测 | |
| if (this.resizeEndTimer !== undefined) { | |
| clearTimeout(this.resizeEndTimer); | |
| } | |
| // 设置结束检测:RESIZE_DEBOUNCE_MS 没有新事件则认为结束 | |
| this.resizeEndTimer = window.setTimeout(() => { | |
| this.resizeEventCount = 0; // 重置计数 | |
| // 立即更新位置 | |
| this.options.onPositionUpdate(); | |
| // 显示SVG | |
| const svg = this.options.getCurrentSvg(); | |
| if (svg) { | |
| svg.style.opacity = '1'; | |
| svg.style.pointerEvents = ''; | |
| } | |
| this.resizeEndTimer = undefined; | |
| }, this.options.resizeDebounceMs); | |
| } | |
| /** | |
| * 处理单次变化(如字体改变) | |
| */ | |
| private handleSingleResize(): void { | |
| // 单次变化:直接更新,不隐藏 | |
| // 取消之前的结束检测(如果有) | |
| if (this.resizeEndTimer !== undefined) { | |
| clearTimeout(this.resizeEndTimer); | |
| this.resizeEndTimer = undefined; | |
| } | |
| // 取消待处理的位置更新 | |
| if (this.positionUpdateTimer !== undefined) { | |
| cancelAnimationFrame(this.positionUpdateTimer); | |
| } | |
| // 立即更新位置(使用 requestAnimationFrame 确保不阻塞) | |
| this.positionUpdateTimer = requestAnimationFrame(() => { | |
| this.options.onPositionUpdate(); | |
| this.positionUpdateTimer = undefined; | |
| }); | |
| } | |
| /** | |
| * 清理资源:停止ResizeObserver并清理定时器 | |
| */ | |
| destroy(): void { | |
| // 清理ResizeObserver | |
| if (this.resizeObserver) { | |
| this.resizeObserver.disconnect(); | |
| this.resizeObserver = undefined; | |
| } | |
| // 取消待处理的位置更新 | |
| if (this.positionUpdateTimer !== undefined) { | |
| cancelAnimationFrame(this.positionUpdateTimer); | |
| this.positionUpdateTimer = undefined; | |
| } | |
| // 取消resize结束检测定时器 | |
| if (this.resizeEndTimer !== undefined) { | |
| clearTimeout(this.resizeEndTimer); | |
| this.resizeEndTimer = undefined; | |
| } | |
| } | |
| } | |