From d43664c9458f4e6a5a9b2ade0342fef0e02f847f Mon Sep 17 00:00:00 2001 From: Azure <983547216@qq.com> Date: Sun, 7 Sep 2025 15:23:04 +0800 Subject: [PATCH] =?UTF-8?q?=E5=AE=8C=E5=96=84=E5=92=8C=E4=BF=AE=E5=A4=8D?= =?UTF-8?q?=E4=B8=80=E4=B8=8BDraggableResizableWindow=E7=9A=84bug?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/core/utils/DraggableResizableWindow.ts | 141 ++++++++++++++++----- src/core/window/impl/WindowFormImpl.ts | 12 ++ 2 files changed, 120 insertions(+), 33 deletions(-) diff --git a/src/core/utils/DraggableResizableWindow.ts b/src/core/utils/DraggableResizableWindow.ts index 4e3e022..ebc686b 100644 --- a/src/core/utils/DraggableResizableWindow.ts +++ b/src/core/utils/DraggableResizableWindow.ts @@ -17,7 +17,7 @@ type TResizeDirection = | 'bottom-right'; /** 窗口状态 */ -type WindowState = 'default' | 'minimized' | 'maximized'; +type TWindowState = 'default' | 'minimized' | 'maximized'; /** 元素边界 */ interface IElementRect { @@ -152,9 +152,13 @@ export class DraggableResizableWindow { private mutationObserver: MutationObserver; private animationFrame?: number; - private state: WindowState = 'default'; - private targetDefaultBounds: IElementRect; - private maximizedBounds?: IElementRect; + private state: TWindowState = 'default'; + /** 元素信息 */ + private targetBounds: IElementRect; + /** 最小化前的元素信息 */ + private targetPreMinimizeBounds?: IElementRect; + /** 最大化前的元素信息 */ + private targetPreMaximizedBounds?: IElementRect; private taskbarElementId: string; constructor(options: IDraggableResizableOptions) { @@ -178,17 +182,20 @@ 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, - }; + requestAnimationFrame(() => { + this.targetBounds = { + width: this.target.offsetWidth, + height: this.target.offsetHeight, + top: this.target.offsetTop, + left: this.target.offsetLeft, + }; + }); + this.taskbarElementId = options.taskbarElementId; this.target.style.position = "absolute"; - this.target.style.left = `${this.target.offsetLeft}px`; - this.target.style.top = `${this.target.offsetTop}px`; + this.target.style.left = '0px'; + this.target.style.top = '0px'; this.target.style.transform = "translate(0px, 0px)"; this.init(); @@ -216,11 +223,21 @@ export class DraggableResizableWindow { } } - /** ---------------- 拖拽 ---------------- */ private onMouseDownDrag = (e: MouseEvent) => { - if (this.getResizeDirection(e)) return; e.preventDefault(); + e.stopPropagation(); + if (e.target !== this.handle) return; + if (this.getResizeDirection(e)) return; + + if (this.state === 'maximized') { + this.restore(() => this.startDrag(e)); + } else { + this.startDrag(e); + } + } + + private startDrag = (e: MouseEvent) => { this.isDragging = true; this.startX = e.clientX; this.startY = e.clientY; @@ -237,6 +254,7 @@ export class DraggableResizableWindow { }; private onMouseMoveDragRAF = (e: MouseEvent) => { + e.stopPropagation(); this.dragDX = e.clientX - this.startX; this.dragDY = e.clientY - this.startY; if (!this.pendingDrag) { @@ -263,7 +281,8 @@ export class DraggableResizableWindow { this.onDragMove?.(newX, newY); } - private onMouseUpDrag = () => { + private onMouseUpDrag = (e: MouseEvent) => { + e.stopPropagation(); if (!this.isDragging) return; this.isDragging = false; @@ -331,7 +350,8 @@ export class DraggableResizableWindow { 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); } @@ -360,14 +380,20 @@ export class DraggableResizableWindow { return snapPoints; } - /** ---------------- 缩放 ---------------- */ private onMouseDownResize = (e: MouseEvent) => { + e.preventDefault(); + e.stopPropagation(); const dir = this.getResizeDirection(e); if (!dir) return; - e.preventDefault(); + this.startResize(e, dir); }; + private onMouseLeave = (e: MouseEvent) => { + e.stopPropagation(); + this.updateCursor(null); + }; + private startResize(e: MouseEvent, dir: TResizeDirection) { this.currentDirection = dir; const rect = this.target.getBoundingClientRect(); @@ -388,6 +414,7 @@ export class DraggableResizableWindow { } private onResizeDragRAF = (e: MouseEvent) => { + e.stopPropagation(); this.resizeDX = e.clientX - this.startX; this.resizeDY = e.clientY - this.startY; if (!this.pendingResize) { @@ -424,9 +451,7 @@ export class DraggableResizableWindow { newWidth = Math.max(this.minWidth, Math.min(this.maxWidth, newWidth)); newHeight = Math.max(this.minHeight, Math.min(this.maxHeight, newHeight)); - this.target.style.width = `${newWidth}px`; - this.target.style.height = `${newHeight}px`; - this.applyPosition(newX, newY, false); + this.applyResizeBounds(newX, newY, newWidth, newHeight); this.updateCursor(this.currentDirection); @@ -439,7 +464,41 @@ export class DraggableResizableWindow { }); } - private onResizeEndHandler = () => { + // 应用尺寸调整边界 + private applyResizeBounds(newX: number, newY: number, newWidth: number, newHeight: number) { + // 最小/最大宽高限制 + newWidth = Math.max(this.minWidth, Math.min(this.maxWidth, newWidth)); + newHeight = Math.max(this.minHeight, Math.min(this.maxHeight, newHeight)); + + // 边界限制 + if (!this.boundary || this.allowOverflow) { + this.currentX = newX; + this.currentY = newY; + this.target.style.width = `${newWidth}px`; + this.target.style.height = `${newHeight}px`; + this.applyPosition(newX, newY, false); + return; + } + + if (this.boundary instanceof HTMLElement && this.containerRect) { + newX = Math.min(Math.max(0, newX), this.containerRect.width - newWidth); + newY = Math.min(Math.max(0, newY), this.containerRect.height - newHeight); + } else if (!(this.boundary instanceof HTMLElement) && this.boundary) { + if (this.boundary.minX !== undefined) newX = Math.max(newX, this.boundary.minX); + if (this.boundary.maxX !== undefined) newX = Math.min(newX, this.boundary.maxX); + if (this.boundary.minY !== undefined) newY = Math.max(newY, this.boundary.minY); + if (this.boundary.maxY !== undefined) newY = Math.min(newY, this.boundary.maxY); + } + + this.currentX = newX; + this.currentY = newY; + this.target.style.width = `${newWidth}px`; + this.target.style.height = `${newHeight}px`; + this.applyPosition(newX, newY, false); + } + + private onResizeEndHandler = (e?: MouseEvent) => { + e?.stopPropagation(); if (!this.currentDirection) return; this.onResizeEnd?.({ width: this.target.offsetWidth, @@ -457,7 +516,7 @@ export class DraggableResizableWindow { private getResizeDirection(e: MouseEvent): TResizeDirection | null { const rect = this.target.getBoundingClientRect(); - const offset = 8; + const offset = 4; const x = e.clientX; const y = e.clientY; const top = y >= rect.top && y <= rect.top + offset; @@ -487,6 +546,7 @@ export class DraggableResizableWindow { } private onDocumentMouseMoveCursor = (e: MouseEvent) => { + e.stopPropagation(); if (this.currentDirection || this.isDragging) return; const dir = this.getResizeDirection(e); this.updateCursor(dir); @@ -495,6 +555,7 @@ export class DraggableResizableWindow { // 最小化到任务栏 public minimize() { if (this.state === 'minimized') return; + this.targetPreMinimizeBounds = { ...this.targetBounds } this.state = 'minimized'; const taskbar = document.querySelector(this.taskbarElementId); @@ -506,16 +567,19 @@ export class DraggableResizableWindow { const startW = this.target.offsetWidth; const startH = this.target.offsetHeight; - this.animateWindow(startX, startY, startW, startH, rect.left, rect.top, rect.width, rect.height, 400); + this.animateWindow(startX, startY, startW, startH, rect.left, rect.top, rect.width, rect.height, 400, () => { + this.target.style.display = 'none'; + }); } /** 最大化 */ public maximize() { if (this.state === 'maximized') return; + this.targetPreMinimizeBounds = { ...this.targetBounds } this.state = 'maximized'; const rect = this.target.getBoundingClientRect(); - this.targetDefaultBounds = { width: rect.width, height: rect.height, left: rect.left, top: rect.top }; + this.targetBounds = { width: rect.width, height: rect.height, left: rect.left, top: rect.top }; const startX = this.currentX; const startY = this.currentY; @@ -531,17 +595,28 @@ export class DraggableResizableWindow { } /** 恢复到默认窗体状态 */ - public restore() { + public restore(onComplete?: () => void) { if (this.state === 'default') return; this.state = 'default'; - const b = this.targetDefaultBounds; + let b: IElementRect; + if ((this.state as TWindowState) === 'minimized' && this.targetPreMinimizeBounds) { + // 最小化恢复,恢复到最小化前的状态 + b = this.targetPreMinimizeBounds; + } else if ((this.state as TWindowState) === 'maximized' && this.targetPreMaximizedBounds) { + // 最大化恢复,恢复到最大化前的默认状态 + b = this.targetPreMaximizedBounds; + } else { + b = this.targetBounds; + } + + this.target.style.display = 'block'; const startX = this.currentX; const startY = this.currentY; const startW = this.target.offsetWidth; const startH = this.target.offsetHeight; - this.animateWindow(startX, startY, startW, startH, b.left, b.top, b.width, b.height, 300); + this.animateWindow(startX, startY, startW, startH, b.left, b.top, b.width, b.height, 300, onComplete); } /** @@ -598,14 +673,14 @@ export class DraggableResizableWindow { } private updateDefaultBounds(left: number, top: number, width?: number, height?: number) { - this.targetDefaultBounds = { + this.targetBounds = { 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(); @@ -613,7 +688,9 @@ export class DraggableResizableWindow { this.resizeObserver.observe(element); } - /** ---------------- 销毁 ---------------- */ + /** + * 销毁实例 + */ public destroy() { if (this.handle) this.handle.removeEventListener('mousedown', this.onMouseDownDrag); this.target.removeEventListener('mousedown', this.onMouseDownResize); @@ -626,6 +703,4 @@ export class DraggableResizableWindow { 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 cebe59b..c15028d 100644 --- a/src/core/window/impl/WindowFormImpl.ts +++ b/src/core/window/impl/WindowFormImpl.ts @@ -56,6 +56,9 @@ export default class WindowFormImpl implements IWindowForm { bt1.innerText = '最小化'; bt1.addEventListener('click', () => { win.minimize(); + setTimeout(() => { + win.restore(); + }, 2000) }) div.appendChild(bt1) const bt2 = document.createElement('button'); @@ -73,6 +76,15 @@ export default class WindowFormImpl implements IWindowForm { processManager.removeProcess(this.proc!) }) div.appendChild(bt3) + const bt4 = document.createElement('button'); + bt4.innerText = '恢复'; + bt4.addEventListener('click', (e) => { + e.preventDefault(); + e.stopPropagation() + win.restore(); + }) + div.appendChild(bt4) + const win = new DraggableResizableWindow({ target: dom,