完善和修复一下DraggableResizableWindow的bug
This commit is contained in:
@@ -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); };
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user