完善和修复一下DraggableResizableWindow的bug

This commit is contained in:
2025-09-07 15:23:04 +08:00
parent fcf3c2933c
commit d43664c945
2 changed files with 120 additions and 33 deletions

View File

@@ -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); };
}

View File

@@ -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,