完善和修复一下DraggableResizableWindow的bug
This commit is contained in:
@@ -17,7 +17,7 @@ type TResizeDirection =
|
|||||||
| 'bottom-right';
|
| 'bottom-right';
|
||||||
|
|
||||||
/** 窗口状态 */
|
/** 窗口状态 */
|
||||||
type WindowState = 'default' | 'minimized' | 'maximized';
|
type TWindowState = 'default' | 'minimized' | 'maximized';
|
||||||
|
|
||||||
/** 元素边界 */
|
/** 元素边界 */
|
||||||
interface IElementRect {
|
interface IElementRect {
|
||||||
@@ -152,9 +152,13 @@ export class DraggableResizableWindow {
|
|||||||
private mutationObserver: MutationObserver;
|
private mutationObserver: MutationObserver;
|
||||||
private animationFrame?: number;
|
private animationFrame?: number;
|
||||||
|
|
||||||
private state: WindowState = 'default';
|
private state: TWindowState = 'default';
|
||||||
private targetDefaultBounds: IElementRect;
|
/** 元素信息 */
|
||||||
private maximizedBounds?: IElementRect;
|
private targetBounds: IElementRect;
|
||||||
|
/** 最小化前的元素信息 */
|
||||||
|
private targetPreMinimizeBounds?: IElementRect;
|
||||||
|
/** 最大化前的元素信息 */
|
||||||
|
private targetPreMaximizedBounds?: IElementRect;
|
||||||
private taskbarElementId: string;
|
private taskbarElementId: string;
|
||||||
|
|
||||||
constructor(options: IDraggableResizableOptions) {
|
constructor(options: IDraggableResizableOptions) {
|
||||||
@@ -178,17 +182,20 @@ export class DraggableResizableWindow {
|
|||||||
this.onResizeMove = options.onResizeMove;
|
this.onResizeMove = options.onResizeMove;
|
||||||
this.onResizeEnd = options.onResizeEnd;
|
this.onResizeEnd = options.onResizeEnd;
|
||||||
|
|
||||||
this.targetDefaultBounds = {
|
requestAnimationFrame(() => {
|
||||||
|
this.targetBounds = {
|
||||||
width: this.target.offsetWidth,
|
width: this.target.offsetWidth,
|
||||||
height: this.target.offsetHeight,
|
height: this.target.offsetHeight,
|
||||||
top: this.target.offsetTop,
|
top: this.target.offsetTop,
|
||||||
left: this.target.offsetLeft,
|
left: this.target.offsetLeft,
|
||||||
};
|
};
|
||||||
|
});
|
||||||
|
|
||||||
this.taskbarElementId = options.taskbarElementId;
|
this.taskbarElementId = options.taskbarElementId;
|
||||||
|
|
||||||
this.target.style.position = "absolute";
|
this.target.style.position = "absolute";
|
||||||
this.target.style.left = `${this.target.offsetLeft}px`;
|
this.target.style.left = '0px';
|
||||||
this.target.style.top = `${this.target.offsetTop}px`;
|
this.target.style.top = '0px';
|
||||||
this.target.style.transform = "translate(0px, 0px)";
|
this.target.style.transform = "translate(0px, 0px)";
|
||||||
|
|
||||||
this.init();
|
this.init();
|
||||||
@@ -216,11 +223,21 @@ export class DraggableResizableWindow {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** ---------------- 拖拽 ---------------- */
|
|
||||||
private onMouseDownDrag = (e: MouseEvent) => {
|
private onMouseDownDrag = (e: MouseEvent) => {
|
||||||
if (this.getResizeDirection(e)) return;
|
|
||||||
e.preventDefault();
|
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.isDragging = true;
|
||||||
this.startX = e.clientX;
|
this.startX = e.clientX;
|
||||||
this.startY = e.clientY;
|
this.startY = e.clientY;
|
||||||
@@ -237,6 +254,7 @@ export class DraggableResizableWindow {
|
|||||||
};
|
};
|
||||||
|
|
||||||
private onMouseMoveDragRAF = (e: MouseEvent) => {
|
private onMouseMoveDragRAF = (e: MouseEvent) => {
|
||||||
|
e.stopPropagation();
|
||||||
this.dragDX = e.clientX - this.startX;
|
this.dragDX = e.clientX - this.startX;
|
||||||
this.dragDY = e.clientY - this.startY;
|
this.dragDY = e.clientY - this.startY;
|
||||||
if (!this.pendingDrag) {
|
if (!this.pendingDrag) {
|
||||||
@@ -263,7 +281,8 @@ export class DraggableResizableWindow {
|
|||||||
this.onDragMove?.(newX, newY);
|
this.onDragMove?.(newX, newY);
|
||||||
}
|
}
|
||||||
|
|
||||||
private onMouseUpDrag = () => {
|
private onMouseUpDrag = (e: MouseEvent) => {
|
||||||
|
e.stopPropagation();
|
||||||
if (!this.isDragging) return;
|
if (!this.isDragging) return;
|
||||||
this.isDragging = false;
|
this.isDragging = false;
|
||||||
|
|
||||||
@@ -331,7 +350,8 @@ export class DraggableResizableWindow {
|
|||||||
if (this.boundary.maxY !== undefined) y = Math.min(y, this.boundary.maxY);
|
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);
|
this.applyPosition(x, y, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -360,14 +380,20 @@ export class DraggableResizableWindow {
|
|||||||
return snapPoints;
|
return snapPoints;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** ---------------- 缩放 ---------------- */
|
|
||||||
private onMouseDownResize = (e: MouseEvent) => {
|
private onMouseDownResize = (e: MouseEvent) => {
|
||||||
|
e.preventDefault();
|
||||||
|
e.stopPropagation();
|
||||||
const dir = this.getResizeDirection(e);
|
const dir = this.getResizeDirection(e);
|
||||||
if (!dir) return;
|
if (!dir) return;
|
||||||
e.preventDefault();
|
|
||||||
this.startResize(e, dir);
|
this.startResize(e, dir);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
private onMouseLeave = (e: MouseEvent) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
this.updateCursor(null);
|
||||||
|
};
|
||||||
|
|
||||||
private startResize(e: MouseEvent, dir: TResizeDirection) {
|
private startResize(e: MouseEvent, dir: TResizeDirection) {
|
||||||
this.currentDirection = dir;
|
this.currentDirection = dir;
|
||||||
const rect = this.target.getBoundingClientRect();
|
const rect = this.target.getBoundingClientRect();
|
||||||
@@ -388,6 +414,7 @@ export class DraggableResizableWindow {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private onResizeDragRAF = (e: MouseEvent) => {
|
private onResizeDragRAF = (e: MouseEvent) => {
|
||||||
|
e.stopPropagation();
|
||||||
this.resizeDX = e.clientX - this.startX;
|
this.resizeDX = e.clientX - this.startX;
|
||||||
this.resizeDY = e.clientY - this.startY;
|
this.resizeDY = e.clientY - this.startY;
|
||||||
if (!this.pendingResize) {
|
if (!this.pendingResize) {
|
||||||
@@ -424,9 +451,7 @@ export class DraggableResizableWindow {
|
|||||||
newWidth = Math.max(this.minWidth, Math.min(this.maxWidth, newWidth));
|
newWidth = Math.max(this.minWidth, Math.min(this.maxWidth, newWidth));
|
||||||
newHeight = Math.max(this.minHeight, Math.min(this.maxHeight, newHeight));
|
newHeight = Math.max(this.minHeight, Math.min(this.maxHeight, newHeight));
|
||||||
|
|
||||||
this.target.style.width = `${newWidth}px`;
|
this.applyResizeBounds(newX, newY, newWidth, newHeight);
|
||||||
this.target.style.height = `${newHeight}px`;
|
|
||||||
this.applyPosition(newX, newY, false);
|
|
||||||
|
|
||||||
this.updateCursor(this.currentDirection);
|
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;
|
if (!this.currentDirection) return;
|
||||||
this.onResizeEnd?.({
|
this.onResizeEnd?.({
|
||||||
width: this.target.offsetWidth,
|
width: this.target.offsetWidth,
|
||||||
@@ -457,7 +516,7 @@ export class DraggableResizableWindow {
|
|||||||
|
|
||||||
private getResizeDirection(e: MouseEvent): TResizeDirection | null {
|
private getResizeDirection(e: MouseEvent): TResizeDirection | null {
|
||||||
const rect = this.target.getBoundingClientRect();
|
const rect = this.target.getBoundingClientRect();
|
||||||
const offset = 8;
|
const offset = 4;
|
||||||
const x = e.clientX;
|
const x = e.clientX;
|
||||||
const y = e.clientY;
|
const y = e.clientY;
|
||||||
const top = y >= rect.top && y <= rect.top + offset;
|
const top = y >= rect.top && y <= rect.top + offset;
|
||||||
@@ -487,6 +546,7 @@ export class DraggableResizableWindow {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private onDocumentMouseMoveCursor = (e: MouseEvent) => {
|
private onDocumentMouseMoveCursor = (e: MouseEvent) => {
|
||||||
|
e.stopPropagation();
|
||||||
if (this.currentDirection || this.isDragging) return;
|
if (this.currentDirection || this.isDragging) return;
|
||||||
const dir = this.getResizeDirection(e);
|
const dir = this.getResizeDirection(e);
|
||||||
this.updateCursor(dir);
|
this.updateCursor(dir);
|
||||||
@@ -495,6 +555,7 @@ export class DraggableResizableWindow {
|
|||||||
// 最小化到任务栏
|
// 最小化到任务栏
|
||||||
public minimize() {
|
public minimize() {
|
||||||
if (this.state === 'minimized') return;
|
if (this.state === 'minimized') return;
|
||||||
|
this.targetPreMinimizeBounds = { ...this.targetBounds }
|
||||||
this.state = 'minimized';
|
this.state = 'minimized';
|
||||||
|
|
||||||
const taskbar = document.querySelector(this.taskbarElementId);
|
const taskbar = document.querySelector(this.taskbarElementId);
|
||||||
@@ -506,16 +567,19 @@ export class DraggableResizableWindow {
|
|||||||
const startW = this.target.offsetWidth;
|
const startW = this.target.offsetWidth;
|
||||||
const startH = this.target.offsetHeight;
|
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() {
|
public maximize() {
|
||||||
if (this.state === 'maximized') return;
|
if (this.state === 'maximized') return;
|
||||||
|
this.targetPreMinimizeBounds = { ...this.targetBounds }
|
||||||
this.state = 'maximized';
|
this.state = 'maximized';
|
||||||
|
|
||||||
const rect = this.target.getBoundingClientRect();
|
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 startX = this.currentX;
|
||||||
const startY = this.currentY;
|
const startY = this.currentY;
|
||||||
@@ -531,17 +595,28 @@ export class DraggableResizableWindow {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/** 恢复到默认窗体状态 */
|
/** 恢复到默认窗体状态 */
|
||||||
public restore() {
|
public restore(onComplete?: () => void) {
|
||||||
if (this.state === 'default') return;
|
if (this.state === 'default') return;
|
||||||
this.state = 'default';
|
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 startX = this.currentX;
|
||||||
const startY = this.currentY;
|
const startY = this.currentY;
|
||||||
const startW = this.target.offsetWidth;
|
const startW = this.target.offsetWidth;
|
||||||
const startH = this.target.offsetHeight;
|
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) {
|
private updateDefaultBounds(left: number, top: number, width?: number, height?: number) {
|
||||||
this.targetDefaultBounds = {
|
this.targetBounds = {
|
||||||
left, top,
|
left, top,
|
||||||
width: width ?? this.target.offsetWidth,
|
width: width ?? this.target.offsetWidth,
|
||||||
height: height ?? this.target.offsetHeight
|
height: height ?? this.target.offsetHeight
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/** ---------------- Resize Observer ---------------- */
|
/** 监听元素变化 */
|
||||||
private observeResize(element: HTMLElement) {
|
private observeResize(element: HTMLElement) {
|
||||||
this.resizeObserver = new ResizeObserver(() => {
|
this.resizeObserver = new ResizeObserver(() => {
|
||||||
this.containerRect = element.getBoundingClientRect();
|
this.containerRect = element.getBoundingClientRect();
|
||||||
@@ -613,7 +688,9 @@ export class DraggableResizableWindow {
|
|||||||
this.resizeObserver.observe(element);
|
this.resizeObserver.observe(element);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** ---------------- 销毁 ---------------- */
|
/**
|
||||||
|
* 销毁实例
|
||||||
|
*/
|
||||||
public destroy() {
|
public destroy() {
|
||||||
if (this.handle) this.handle.removeEventListener('mousedown', this.onMouseDownDrag);
|
if (this.handle) this.handle.removeEventListener('mousedown', this.onMouseDownDrag);
|
||||||
this.target.removeEventListener('mousedown', this.onMouseDownResize);
|
this.target.removeEventListener('mousedown', this.onMouseDownResize);
|
||||||
@@ -626,6 +703,4 @@ export class DraggableResizableWindow {
|
|||||||
this.mutationObserver.disconnect();
|
this.mutationObserver.disconnect();
|
||||||
cancelAnimationFrame(this.animationFrame ?? 0);
|
cancelAnimationFrame(this.animationFrame ?? 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
private onMouseLeave = () => { this.updateCursor(null); };
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -56,6 +56,9 @@ export default class WindowFormImpl implements IWindowForm {
|
|||||||
bt1.innerText = '最小化';
|
bt1.innerText = '最小化';
|
||||||
bt1.addEventListener('click', () => {
|
bt1.addEventListener('click', () => {
|
||||||
win.minimize();
|
win.minimize();
|
||||||
|
setTimeout(() => {
|
||||||
|
win.restore();
|
||||||
|
}, 2000)
|
||||||
})
|
})
|
||||||
div.appendChild(bt1)
|
div.appendChild(bt1)
|
||||||
const bt2 = document.createElement('button');
|
const bt2 = document.createElement('button');
|
||||||
@@ -73,6 +76,15 @@ export default class WindowFormImpl implements IWindowForm {
|
|||||||
processManager.removeProcess(this.proc!)
|
processManager.removeProcess(this.proc!)
|
||||||
})
|
})
|
||||||
div.appendChild(bt3)
|
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({
|
const win = new DraggableResizableWindow({
|
||||||
target: dom,
|
target: dom,
|
||||||
|
|||||||
Reference in New Issue
Block a user