diff --git a/src/core/utils/DraggableResizableWindow.ts b/src/core/utils/DraggableResizableWindow.ts index f3092d2..98ed28b 100644 --- a/src/core/utils/DraggableResizableWindow.ts +++ b/src/core/utils/DraggableResizableWindow.ts @@ -51,8 +51,6 @@ interface IDraggableResizableOptions { target: HTMLElement; /** 拖拽句柄 */ handle?: HTMLElement; - /** 拖拽模式 */ - mode?: 'transform' | 'position'; /** 拖拽边界或容器元素 */ boundary?: IBoundaryRect | HTMLElement; /** 移动步进(网格吸附) */ @@ -104,12 +102,12 @@ interface IBoundaryRect { /** * 拖拽 + 调整尺寸 + 最大最小化 通用类 + * 统一使用 position: absolute + transform: translate 实现拖拽 */ export class DraggableResizableWindow { private handle?: HTMLElement; private target: HTMLElement; private boundary?: HTMLElement | IBoundaryRect; - private mode: 'transform' | 'position'; private snapGrid: number; private snapThreshold: number; private snapAnimation: boolean; @@ -120,45 +118,49 @@ export class DraggableResizableWindow { private onDragMove?: TDragMoveCallback; private onDragEnd?: TDragEndCallback; + private onResizeMove?: (data: IResizeCallbackData) => void; + private onResizeEnd?: (data: IResizeCallbackData) => void; + private isDragging = false; + private currentDirection: TResizeDirection | null = null; + private startX = 0; private startY = 0; + private startWidth = 0; + private startHeight = 0; + private startTop = 0; + private startLeft = 0; private offsetX = 0; private offsetY = 0; private currentX = 0; private currentY = 0; + private pendingDrag = false; + private pendingResize = false; + private dragDX = 0; + private dragDY = 0; + private resizeDX = 0; + private resizeDY = 0; + + private minWidth: number; + private minHeight: number; + private maxWidth: number; + private maxHeight: number; + private containerRect?: DOMRect; private resizeObserver?: ResizeObserver; private mutationObserver: MutationObserver; private animationFrame?: number; - private currentDirection: TResizeDirection | null = null; - private startWidth = 0; - private startHeight = 0; - private startTop = 0; - private startLeft = 0; - private minWidth: number; - private minHeight: number; - private maxWidth: number; - private maxHeight: number; - private onResizeMove?: (data: IResizeCallbackData) => void; - private onResizeEnd?: (data: IResizeCallbackData) => void; - private state: WindowState = 'default'; - /** 目标元素默认 bounds */ private targetDefaultBounds: IElementRect; - /** 最大化前保存 bounds */ private maximizedBounds?: IElementRect; - /** 最小化任务栏位置的元素ID */ private taskbarElementId: string; constructor(options: IDraggableResizableOptions) { - // Drag this.handle = options.handle; this.target = options.target; this.boundary = options.boundary; - this.mode = options.mode ?? 'transform'; this.snapGrid = options.snapGrid ?? 1; this.snapThreshold = options.snapThreshold ?? 0; this.snapAnimation = options.snapAnimation ?? false; @@ -169,7 +171,6 @@ export class DraggableResizableWindow { this.onDragMove = options.onDragMove; this.onDragEnd = options.onDragEnd; - // Resize this.minWidth = options.minWidth ?? 100; this.minHeight = options.minHeight ?? 50; this.maxWidth = options.maxWidth ?? window.innerWidth; @@ -177,35 +178,36 @@ export class DraggableResizableWindow { this.onResizeMove = options.onResizeMove; this.onResizeEnd = options.onResizeEnd; - this.targetDefaultBounds = { width: this.target.offsetWidth, height: this.target.offsetHeight, top: this.target.offsetTop, left: this.target.offsetLeft }; + this.targetDefaultBounds = { + width: this.target.offsetWidth, + height: this.target.offsetHeight, + top: this.target.offsetTop, + left: this.target.offsetLeft, + }; this.taskbarElementId = options.taskbarElementId; - this.init(); + this.target.style.position = "absolute"; + this.target.style.left = `${this.target.offsetLeft}px`; + this.target.style.top = `${this.target.offsetTop}px`; + this.target.style.transform = "translate(0px, 0px)"; - // this.setDefaultCenterPosition(); + this.init(); } - /** 初始化事件 */ private init() { if (this.handle) { this.handle.addEventListener('mousedown', this.onMouseDownDrag); } - this.target.addEventListener('mousedown', this.onMouseDownResize); this.target.addEventListener('mouseleave', this.onMouseLeave); document.addEventListener('mousemove', this.onDocumentMouseMoveCursor); - if (this.boundary instanceof HTMLElement) { - this.observeResize(this.boundary); - } + if (this.boundary instanceof HTMLElement) this.observeResize(this.boundary); - // 监听目标 DOM 是否被移除,自动销毁 - this.mutationObserver = new MutationObserver((mutations) => { + this.mutationObserver = new MutationObserver(mutations => { for (const mutation of mutations) { - mutation.removedNodes.forEach((node) => { - if (node === this.target) { - this.destroy(); - } + mutation.removedNodes.forEach(node => { + if (node === this.target) this.destroy(); }); } }); @@ -214,59 +216,43 @@ export class DraggableResizableWindow { } } - /** 将窗口设置在居中位置 */ - private setDefaultCenterPosition() { - console.log(JSON.parse(JSON.stringify(this.containerRect))) - const containerWidth = this.containerRect?.width ?? window.innerWidth; - const containerHeight = this.containerRect?.height ?? window.innerHeight; - const targetWidth = this.target.offsetWidth; - const targetHeight = this.target.offsetHeight; - - const x = (containerWidth - targetWidth) / 2; - const y = (containerHeight - targetHeight) / 2; - - if (this.mode === 'position') { - this.target.style.left = `${x}px`; - this.target.style.top = `${y}px`; - } else { - this.target.style.transform = `translate(${x}px, ${y}px)`; - } - - this.currentX = x; - this.currentY = y; - this.updateDefaultBounds(x, y, targetWidth, targetHeight); - } - + /** ---------------- 拖拽 ---------------- */ private onMouseDownDrag = (e: MouseEvent) => { - if (this.getResizeDirection(e)) return; // 避免和 resize 冲突 + if (this.getResizeDirection(e)) return; e.preventDefault(); - this.startDrag(e); - }; - private startDrag(e: MouseEvent) { this.isDragging = true; this.startX = e.clientX; this.startY = e.clientY; - const rect = this.target.getBoundingClientRect(); - const parentRect = this.target.offsetParent?.getBoundingClientRect() ?? { left: 0, top: 0 }; - this.offsetX = rect.left - parentRect.left; - this.offsetY = rect.top - parentRect.top; - - document.addEventListener('mousemove', this.onMouseMoveDrag); - document.addEventListener('mouseup', this.onMouseUpDrag); + const style = window.getComputedStyle(this.target); + const matrix = new DOMMatrixReadOnly(style.transform); + this.offsetX = matrix.m41; + this.offsetY = matrix.m42; this.onDragStart?.(this.offsetX, this.offsetY); - } - private onMouseMoveDrag = (e: MouseEvent) => { + document.addEventListener('mousemove', this.onMouseMoveDragRAF); + document.addEventListener('mouseup', this.onMouseUpDrag); + }; + + private onMouseMoveDragRAF = (e: MouseEvent) => { + this.dragDX = e.clientX - this.startX; + this.dragDY = e.clientY - this.startY; + if (!this.pendingDrag) { + this.pendingDrag = true; + requestAnimationFrame(() => { + this.pendingDrag = false; + this.applyDragFrame(); + }); + } + }; + + private applyDragFrame() { if (!this.isDragging) return; - const dx = e.clientX - this.startX; - const dy = e.clientY - this.startY; - - let newX = this.offsetX + dx; - let newY = this.offsetY + dy; + let newX = this.offsetX + this.dragDX; + let newY = this.offsetY + this.dragDY; if (this.snapGrid > 1) { newX = Math.round(newX / this.snapGrid) * this.snapGrid; @@ -275,9 +261,10 @@ export class DraggableResizableWindow { this.applyPosition(newX, newY, false); this.onDragMove?.(newX, newY); - }; + } private onMouseUpDrag = () => { + console.log(111) if (!this.isDragging) return; this.isDragging = false; @@ -294,27 +281,19 @@ export class DraggableResizableWindow { this.updateDefaultBounds(snapped.x, snapped.y); } - document.removeEventListener('mousemove', this.onMouseMoveDrag); + document.removeEventListener('mousemove', this.onMouseMoveDragRAF); document.removeEventListener('mouseup', this.onMouseUpDrag); }; private applyPosition(x: number, y: number, isFinal: boolean) { this.currentX = x; this.currentY = y; - - if (this.mode === 'position') { - this.target.style.left = `${x}px`; - this.target.style.top = `${y}px`; - } else { - this.target.style.transform = `translate(${x}px, ${y}px)`; - } - + this.target.style.transform = `translate(${x}px, ${y}px)`; if (isFinal) this.applyBoundary(); } private animateTo(targetX: number, targetY: number, duration: number, onComplete?: () => void) { if (this.animationFrame) cancelAnimationFrame(this.animationFrame); - const startX = this.currentX; const startY = this.currentY; const deltaX = targetX - startX; @@ -332,86 +311,57 @@ export class DraggableResizableWindow { this.applyPosition(x, y, false); this.onDragMove?.(x, y); - if (progress < 1) { - this.animationFrame = requestAnimationFrame(step); - } else { - this.applyPosition(targetX, targetY, true); - this.onDragMove?.(targetX, targetY); - onComplete?.(); - } + if (progress < 1) this.animationFrame = requestAnimationFrame(step); + else { this.applyPosition(targetX, targetY, true); this.onDragMove?.(targetX, targetY); onComplete?.(); } }; - this.animationFrame = requestAnimationFrame(step); } private applyBoundary() { if (!this.boundary || this.allowOverflow) return; - let { x, y } = { x: this.currentX, y: this.currentY }; if (this.boundary instanceof HTMLElement && this.containerRect) { const rect = this.target.getBoundingClientRect(); - const minX = 0; - const minY = 0; - const maxX = this.containerRect.width - rect.width; - const maxY = this.containerRect.height - rect.height; - - x = Math.min(Math.max(x, minX), maxX); - y = Math.min(Math.max(y, minY), maxY); - } else if (!(this.boundary instanceof HTMLElement)) { + x = Math.min(Math.max(x, 0), this.containerRect.width - rect.width); + y = Math.min(Math.max(y, 0), this.containerRect.height - rect.height); + } else if (!(this.boundary instanceof HTMLElement) && this.boundary) { if (this.boundary.minX !== undefined) x = Math.max(x, this.boundary.minX); if (this.boundary.maxX !== undefined) x = Math.min(x, this.boundary.maxX); if (this.boundary.minY !== undefined) y = Math.max(y, this.boundary.minY); if (this.boundary.maxY !== undefined) y = Math.min(y, this.boundary.maxY); } - this.currentX = x; - this.currentY = y; + this.currentX = x; this.currentY = y; this.applyPosition(x, y, false); } private applySnapping(x: number, y: number) { - let { x: snappedX, y: snappedY } = { x, y }; - - // 1. 容器吸附 + let snappedX = x, snappedY = y; const containerSnap = this.getSnapPoints(); if (this.snapThreshold > 0) { - for (const sx of containerSnap.x) { - if (Math.abs(x - sx) <= this.snapThreshold) { - snappedX = sx; - break; - } - } - for (const sy of containerSnap.y) { - if (Math.abs(y - sy) <= this.snapThreshold) { - snappedY = sy; - break; - } - } + for (const sx of containerSnap.x) if (Math.abs(x - sx) <= this.snapThreshold) { snappedX = sx; break; } + for (const sy of containerSnap.y) if (Math.abs(y - sy) <= this.snapThreshold) { snappedY = sy; break; } } - - // 2. 窗口吸附 TODO - return { x: snappedX, y: snappedY }; } private getSnapPoints() { const snapPoints = { x: [] as number[], y: [] as number[] }; - if (this.boundary instanceof HTMLElement && this.containerRect) { const rect = this.target.getBoundingClientRect(); snapPoints.x = [0, this.containerRect.width - rect.width]; snapPoints.y = [0, this.containerRect.height - rect.height]; - } else if (!(this.boundary instanceof HTMLElement) && this.boundary) { + } else if (this.boundary && !(this.boundary instanceof HTMLElement)) { if (this.boundary.minX !== undefined) snapPoints.x.push(this.boundary.minX); if (this.boundary.maxX !== undefined) snapPoints.x.push(this.boundary.maxX); if (this.boundary.minY !== undefined) snapPoints.y.push(this.boundary.minY); if (this.boundary.maxY !== undefined) snapPoints.y.push(this.boundary.maxY); } - return snapPoints; } + /** ---------------- 缩放 ---------------- */ private onMouseDownResize = (e: MouseEvent) => { const dir = this.getResizeDirection(e); if (!dir) return; @@ -422,65 +372,54 @@ export class DraggableResizableWindow { private startResize(e: MouseEvent, dir: TResizeDirection) { this.currentDirection = dir; const rect = this.target.getBoundingClientRect(); - const parentRect = this.target.offsetParent?.getBoundingClientRect() ?? { left: 0, top: 0 }; + const style = window.getComputedStyle(this.target); + const matrix = new DOMMatrixReadOnly(style.transform); + this.offsetX = matrix.m41; + this.offsetY = matrix.m42; this.startX = e.clientX; this.startY = e.clientY; this.startWidth = rect.width; this.startHeight = rect.height; - this.startTop = rect.top - parentRect.top; - this.startLeft = rect.left - parentRect.left; + this.startLeft = this.offsetX; + this.startTop = this.offsetY; - document.addEventListener('mousemove', this.onResizeDrag); + document.addEventListener('mousemove', this.onResizeDragRAF); document.addEventListener('mouseup', this.onResizeEndHandler); } - private onResizeDrag = (e: MouseEvent) => { - if (!this.currentDirection) return; + private onResizeDragRAF = (e: MouseEvent) => { + this.resizeDX = e.clientX - this.startX; + this.resizeDY = e.clientY - this.startY; + if (!this.pendingResize) { + this.pendingResize = true; + requestAnimationFrame(() => { + this.pendingResize = false; + this.applyResizeFrame(); + }); + } + }; - let deltaX = e.clientX - this.startX; - let deltaY = e.clientY - this.startY; + private applyResizeFrame() { + if (!this.currentDirection) return; let newWidth = this.startWidth; let newHeight = this.startHeight; - let newTop = this.startTop; - let newLeft = this.startLeft; + let newX = this.startLeft; + let newY = this.startTop; + + const dx = this.resizeDX; + const dy = this.resizeDY; switch (this.currentDirection) { - case 'right': - newWidth = this.startWidth + deltaX; - break; - case 'bottom': - newHeight = this.startHeight + deltaY; - break; - case 'bottom-right': - newWidth = this.startWidth + deltaX; - newHeight = this.startHeight + deltaY; - break; - case 'left': - newWidth = this.startWidth - deltaX; - newLeft = this.startLeft + deltaX; - break; - case 'top': - newHeight = this.startHeight - deltaY; - newTop = this.startTop + deltaY; - break; - case 'top-left': - newWidth = this.startWidth - deltaX; - newLeft = this.startLeft + deltaX; - newHeight = this.startHeight - deltaY; - newTop = this.startTop + deltaY; - break; - case 'top-right': - newWidth = this.startWidth + deltaX; - newHeight = this.startHeight - deltaY; - newTop = this.startTop + deltaY; - break; - case 'bottom-left': - newWidth = this.startWidth - deltaX; - newLeft = this.startLeft + deltaX; - newHeight = this.startHeight + deltaY; - break; + case 'right': newWidth += dx; break; + case 'bottom': newHeight += dy; break; + case 'bottom-right': newWidth += dx; newHeight += dy; break; + case 'left': newWidth -= dx; newX += dx; break; + case 'top': newHeight -= dy; newY += dy; break; + case 'top-left': newWidth -= dx; newX += dx; newHeight -= dy; newY += dy; break; + case 'top-right': newWidth += dx; newHeight -= dy; newY += dy; break; + case 'bottom-left': newWidth -= dx; newX += dx; newHeight += dy; break; } newWidth = Math.max(this.minWidth, Math.min(this.maxWidth, newWidth)); @@ -488,35 +427,32 @@ export class DraggableResizableWindow { this.target.style.width = `${newWidth}px`; this.target.style.height = `${newHeight}px`; - this.target.style.top = `${newTop}px`; - this.target.style.left = `${newLeft}px`; + this.applyPosition(newX, newY, false); + + this.updateCursor(this.currentDirection); this.onResizeMove?.({ width: newWidth, height: newHeight, - top: newTop, - left: newLeft, + left: newX, + top: newY, direction: this.currentDirection, }); - }; + } private onResizeEndHandler = () => { - if (this.currentDirection) { - const rect = this.target.getBoundingClientRect(); - const parentRect = this.target.offsetParent?.getBoundingClientRect() ?? { left: 0, top: 0 }; - this.onResizeEnd?.({ - width: rect.width, - height: rect.height, - top: rect.top - parentRect.top, - left: rect.left - parentRect.left, - direction: this.currentDirection, - }); - this.updateDefaultBounds(rect.left - parentRect.left, rect.top - parentRect.top, rect.width, rect.height); - } - + if (!this.currentDirection) return; + this.onResizeEnd?.({ + width: this.target.offsetWidth, + height: this.target.offsetHeight, + left: this.currentX, + top: this.currentY, + direction: this.currentDirection, + }); + this.updateDefaultBounds(this.currentX, this.currentY, this.target.offsetWidth, this.target.offsetHeight); this.currentDirection = null; this.updateCursor(null); - document.removeEventListener('mousemove', this.onResizeDrag); + document.removeEventListener('mousemove', this.onResizeDragRAF); document.removeEventListener('mouseup', this.onResizeEndHandler); }; @@ -525,7 +461,6 @@ export class DraggableResizableWindow { const offset = 8; const x = e.clientX; const y = e.clientY; - const top = y >= rect.top && y <= rect.top + offset; const bottom = y >= rect.bottom - offset && y <= rect.bottom; const left = x >= rect.left && x <= rect.left + offset; @@ -539,24 +474,15 @@ export class DraggableResizableWindow { if (bottom) return 'bottom'; if (left) return 'left'; if (right) return 'right'; - return null; } private updateCursor(direction: TResizeDirection | null) { - if (!direction) { - this.target.style.cursor = 'default'; - return; - } + if (!direction) { this.target.style.cursor = 'default'; return; } const cursorMap: Record = { - top: 'ns-resize', - bottom: 'ns-resize', - left: 'ew-resize', - right: 'ew-resize', - 'top-left': 'nwse-resize', - 'top-right': 'nesw-resize', - 'bottom-left': 'nesw-resize', - 'bottom-right': 'nwse-resize', + top: 'ns-resize', bottom: 'ns-resize', left: 'ew-resize', right: 'ew-resize', + 'top-left': 'nwse-resize', 'top-right': 'nesw-resize', + 'bottom-left': 'nesw-resize', 'bottom-right': 'nwse-resize' }; this.target.style.cursor = cursorMap[direction]; } @@ -567,223 +493,66 @@ export class DraggableResizableWindow { this.updateCursor(dir); }; - private onMouseLeave = () => { - if (!this.currentDirection && !this.isDragging) this.updateCursor(null); - }; - - private observeResize(container: HTMLElement) { - if (this.resizeObserver) this.resizeObserver.disconnect(); - this.resizeObserver = new ResizeObserver(() => { - this.containerRect = container.getBoundingClientRect(); - this.applyBoundary(); - }); - this.resizeObserver.observe(container); - this.containerRect = container.getBoundingClientRect(); - } - - /** 销毁实例 */ - public destroy() { - // 拖拽解绑:只在 handle 上解绑 - if (this.handle) { - this.handle.removeEventListener('mousedown', this.onMouseDownDrag); - } - - // 调整尺寸解绑 - this.target.removeEventListener('mousedown', this.onMouseDownResize); - this.target.removeEventListener('mouseleave', this.onMouseLeave); - - // 全局事件解绑 - document.removeEventListener('mousemove', this.onDocumentMouseMoveCursor); - document.removeEventListener('mousemove', this.onMouseMoveDrag); - document.removeEventListener('mouseup', this.onMouseUpDrag); - document.removeEventListener('mousemove', this.onResizeDrag); - document.removeEventListener('mouseup', this.onResizeEndHandler); - - // 观察器清理 - if (this.resizeObserver) this.resizeObserver.disconnect(); - if (this.mutationObserver) this.mutationObserver.disconnect(); - if (this.animationFrame) cancelAnimationFrame(this.animationFrame); - - // 所有属性置空,释放内存 - Object.keys(this).forEach(k => (this as any)[k] = null); - } - - public getState() { - return this.state; - } - - /** 最小化到任务栏 */ + /** ---------------- 最小化/最大化 ---------------- */ public minimize() { if (this.state === 'minimized') return; this.state = 'minimized'; - - // 获取任务栏位置 - const taskbarElement = document.querySelector(this.taskbarElementId) - if (!taskbarElement) throw new Error('任务栏元素未找到'); - const rect = taskbarElement.getBoundingClientRect() - const targetX = rect.left; - const targetY = rect.top; - const targetWidth = rect.width; - const targetHeight = rect.height; - - const startX = this.currentX; - const startY = this.currentY; - const startWidth = this.target.offsetWidth; - const startHeight = this.target.offsetHeight; - - const deltaX = targetX - startX; - const deltaY = targetY - startY; - const deltaW = targetWidth - startWidth; - const deltaH = targetHeight - startHeight; - const duration = 400; // Windows 风格稍慢 - const startTime = performance.now(); - - const step = (now: number) => { - const elapsed = now - startTime; - const progress = Math.min(elapsed / duration, 1); - const ease = 1 - Math.pow(1 - progress, 3); // 缓动 - - const x = startX + deltaX * ease; - const y = startY + deltaY * ease; - const w = startWidth + deltaW * ease; - const h = startHeight + deltaH * ease; - - this.target.style.width = `${w}px`; - this.target.style.height = `${h}px`; - this.applyPosition(x, y, false); - - if (progress < 1) { - requestAnimationFrame(step); - } else { - // 最终值 - this.target.style.width = `${targetWidth}px`; - this.target.style.height = `${targetHeight}px`; - this.applyPosition(targetX, targetY, true); - } - }; - - requestAnimationFrame(step); + const taskbar = document.getElementById(this.taskbarElementId); + if (!taskbar) return; + const rect = taskbar.getBoundingClientRect(); + this.animateTo(rect.left, rect.top, 200); + this.target.style.width = '0px'; + this.target.style.height = '0px'; } - /** 最大化窗口(带动画) */ - public maximize(withAnimation = true) { + public maximize() { if (this.state === 'maximized') return; this.state = 'maximized'; - - // 保存原始 bounds - const rect = this.target.getBoundingClientRect(); - this.targetDefaultBounds = { width: rect.width, height: rect.height, top: rect.top, left: rect.left }; - const startWidth = rect.width; - const startHeight = rect.height; - const startX = this.currentX; - const startY = this.currentY; - - const targetWidth = this.containerRect?.width ?? window.innerWidth; - const targetHeight = this.containerRect?.height ?? window.innerHeight; - const targetX = 0; - const targetY = 0; - - if (!withAnimation) { - this.target.style.width = `${targetWidth}px`; - this.target.style.height = `${targetHeight}px`; - this.applyPosition(targetX, targetY, true); - return; - } - - // 动画过渡 - const deltaX = targetX - startX; - const deltaY = targetY - startY; - const deltaW = targetWidth - startWidth; - const deltaH = targetHeight - startHeight; - const duration = 300; - const startTime = performance.now(); - - const step = (now: number) => { - const elapsed = now - startTime; - const progress = Math.min(elapsed / duration, 1); - const ease = 1 - Math.pow(1 - progress, 3); - - const x = startX + deltaX * ease; - const y = startY + deltaY * ease; - const w = startWidth + deltaW * ease; - const h = startHeight + deltaH * ease; - - this.target.style.width = `${w}px`; - this.target.style.height = `${h}px`; - this.applyPosition(x, y, false); - - if (progress < 1) { - requestAnimationFrame(step); - } else { - this.target.style.width = `${targetWidth}px`; - this.target.style.height = `${targetHeight}px`; - this.applyPosition(targetX, targetY, true); - } - }; - - requestAnimationFrame(step); + const bounds = { top: 0, left: 0, width: window.innerWidth, height: window.innerHeight }; + this.maximizedBounds = bounds; + this.animateTo(bounds.left, bounds.top, 200); + this.target.style.width = `${bounds.width}px`; + this.target.style.height = `${bounds.height}px`; } - /** 恢复窗口(带动画,从最大化或最小化) */ - public restore(withAnimation = true) { + public restore() { if (this.state === 'default') return; this.state = 'default'; const b = this.targetDefaultBounds; - - const startWidth = this.target.offsetWidth || 0; - const startHeight = this.target.offsetHeight || 0; - const startLeft = this.currentX; - const startTop = this.currentY; - - if (!withAnimation) { - this.target.style.width = `${b.width}px`; - this.target.style.height = `${b.height}px`; - this.applyPosition(b.left, b.top, true); - return; - } - - const deltaX = b.left - startLeft; - const deltaY = b.top - startTop; - const deltaW = b.width - startWidth; - const deltaH = b.height - startHeight; - const duration = 300; - const startTime = performance.now(); - - const step = (now: number) => { - const elapsed = now - startTime; - const progress = Math.min(elapsed / duration, 1); - const ease = 1 - Math.pow(1 - progress, 3); - - const x = startLeft + deltaX * ease; - const y = startTop + deltaY * ease; - const w = startWidth + deltaW * ease; - const h = startHeight + deltaH * ease; - - this.target.style.width = `${w}px`; - this.target.style.height = `${h}px`; - this.applyPosition(x, y, false); - - if (progress < 1) { - requestAnimationFrame(step); - } else { - this.target.style.width = `${b.width}px`; - this.target.style.height = `${b.height}px`; - this.applyPosition(b.left, b.top, true); - } - }; - - requestAnimationFrame(step); + this.animateTo(b.left, b.top, 200); + this.target.style.width = `${b.width}px`; + this.target.style.height = `${b.height}px`; } - - /** 更新默认 bounds */ - private updateDefaultBounds(x?: number, y?: number, width?: number, height?: number) { - const rect = this.target.getBoundingClientRect(); + private updateDefaultBounds(left: number, top: number, width?: number, height?: number) { this.targetDefaultBounds = { - left: x ?? rect.left, - top: y ?? rect.top, - width: width ?? rect.width, - height: height ?? rect.height, + left, top, + width: width ?? this.target.offsetWidth, + height: height ?? this.target.offsetHeight }; } + + /** ---------------- Resize Observer ---------------- */ + private observeResize(element: HTMLElement) { + this.resizeObserver = new ResizeObserver(() => { + this.containerRect = element.getBoundingClientRect(); + }); + this.resizeObserver.observe(element); + } + + /** ---------------- 销毁 ---------------- */ + public destroy() { + if (this.handle) this.handle.removeEventListener('mousedown', this.onMouseDownDrag); + this.target.removeEventListener('mousedown', this.onMouseDownResize); + document.removeEventListener('mousemove', this.onMouseMoveDragRAF); + document.removeEventListener('mouseup', this.onMouseUpDrag); + document.removeEventListener('mousemove', this.onResizeDragRAF); + document.removeEventListener('mouseup', this.onResizeEndHandler); + document.removeEventListener('mousemove', this.onDocumentMouseMoveCursor); + this.resizeObserver?.disconnect(); + this.mutationObserver.disconnect(); + cancelAnimationFrame(this.animationFrame ?? 0); + } + + private onMouseLeave = () => { this.updateCursor(null); }; } diff --git a/src/core/window/impl/WindowFormImpl.ts b/src/core/window/impl/WindowFormImpl.ts index 72e2038..cebe59b 100644 --- a/src/core/window/impl/WindowFormImpl.ts +++ b/src/core/window/impl/WindowFormImpl.ts @@ -77,7 +77,7 @@ export default class WindowFormImpl implements IWindowForm { const win = new DraggableResizableWindow({ target: dom, handle: div, - mode: 'transform', + snapAnimation: true, snapThreshold: 20, boundary: document.body, taskbarElementId: '#taskbar',