保存一下
This commit is contained in:
@@ -22,9 +22,9 @@ interface IResizeCallbackData {
|
||||
width: number;
|
||||
/** 高度 */
|
||||
height: number;
|
||||
/** 顶点坐标 */
|
||||
/** 顶点坐标(相对 offsetParent) */
|
||||
top: number;
|
||||
/** 底点坐标 */
|
||||
/** 左点坐标(相对 offsetParent) */
|
||||
left: number;
|
||||
/** 拖拽调整尺寸的方向 */
|
||||
direction: TResizeDirection;
|
||||
@@ -52,11 +52,11 @@ interface IDraggableOptions {
|
||||
allowOverflow?: boolean;
|
||||
|
||||
/** 拖拽开始回调 */
|
||||
onStart?: TDragStartCallback;
|
||||
onDragStart?: TDragStartCallback;
|
||||
/** 拖拽移动中的回调 */
|
||||
onMove?: TDragMoveCallback;
|
||||
onDragMove?: TDragMoveCallback;
|
||||
/** 拖拽结束回调 */
|
||||
onEnd?: TDragEndCallback;
|
||||
onDragEnd?: TDragEndCallback;
|
||||
|
||||
/** 调整尺寸的最小宽度 */
|
||||
minWidth?: number;
|
||||
@@ -67,8 +67,8 @@ interface IDraggableOptions {
|
||||
/** 调整尺寸的最大高度 */
|
||||
maxHeight?: number;
|
||||
|
||||
/** 拖拽调整尺寸回调 */
|
||||
onResize?: (data: IResizeCallbackData) => void;
|
||||
/** 拖拽调整尺寸中的回调 */
|
||||
onResizeMove?: (data: IResizeCallbackData) => void;
|
||||
/** 拖拽调整尺寸结束回调 */
|
||||
onResizeEnd?: (data: IResizeCallbackData) => void;
|
||||
}
|
||||
@@ -89,8 +89,6 @@ interface IBoundaryRect {
|
||||
* 拖拽 + 调整尺寸通用类
|
||||
*/
|
||||
export class DraggableResizable {
|
||||
// ---------------- Drag 属性 ----------------
|
||||
|
||||
private handle?: HTMLElement;
|
||||
private target: HTMLElement;
|
||||
private boundary?: HTMLElement | IBoundaryRect;
|
||||
@@ -101,9 +99,9 @@ export class DraggableResizable {
|
||||
private snapAnimationDuration: number;
|
||||
private allowOverflow: boolean;
|
||||
|
||||
private onStart?: TDragStartCallback;
|
||||
private onMove?: TDragMoveCallback;
|
||||
private onEnd?: TDragEndCallback;
|
||||
private onDragStart?: TDragStartCallback;
|
||||
private onDragMove?: TDragMoveCallback;
|
||||
private onDragEnd?: TDragEndCallback;
|
||||
|
||||
private isDragging = false;
|
||||
private startX = 0;
|
||||
@@ -116,11 +114,8 @@ export class DraggableResizable {
|
||||
private containerRect?: DOMRect;
|
||||
private resizeObserver?: ResizeObserver;
|
||||
private mutationObserver: MutationObserver;
|
||||
|
||||
private animationFrame?: number;
|
||||
|
||||
// ---------------- Resize 属性 ----------------
|
||||
|
||||
private currentDirection: TResizeDirection | null = null;
|
||||
private startWidth = 0;
|
||||
private startHeight = 0;
|
||||
@@ -130,7 +125,7 @@ export class DraggableResizable {
|
||||
private minHeight: number;
|
||||
private maxWidth: number;
|
||||
private maxHeight: number;
|
||||
private onResize?: (data: IResizeCallbackData) => void;
|
||||
private onResizeMove?: (data: IResizeCallbackData) => void;
|
||||
private onResizeEnd?: (data: IResizeCallbackData) => void;
|
||||
|
||||
constructor(options: IDraggableOptions) {
|
||||
@@ -145,57 +140,54 @@ export class DraggableResizable {
|
||||
this.snapAnimationDuration = options.snapAnimationDuration ?? 200;
|
||||
this.allowOverflow = options.allowOverflow ?? true;
|
||||
|
||||
this.onStart = options.onStart;
|
||||
this.onMove = options.onMove;
|
||||
this.onEnd = options.onEnd;
|
||||
this.onDragStart = options.onDragStart;
|
||||
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;
|
||||
this.maxHeight = options.maxHeight ?? window.innerHeight;
|
||||
this.onResize = options.onResize;
|
||||
this.onResizeMove = options.onResizeMove;
|
||||
this.onResizeEnd = options.onResizeEnd;
|
||||
|
||||
// 自动监听 DOM 移除
|
||||
this.mutationObserver = new MutationObserver(() => {
|
||||
if (!document.body.contains(this.target)) this.destroy();
|
||||
});
|
||||
this.mutationObserver.observe(document.body, { childList: true, subtree: true });
|
||||
|
||||
this.init();
|
||||
}
|
||||
|
||||
/** 初始化事件 */
|
||||
private init() {
|
||||
if (this.handle) {
|
||||
this.handle.addEventListener('mousedown', this.onMouseDown);
|
||||
this.handle.addEventListener('mousedown', this.onMouseDownDrag);
|
||||
}
|
||||
this.target.addEventListener('mousedown', this.onMouseDown);
|
||||
this.target.addEventListener('mousemove', this.onMouseMoveCursor);
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
// 确保 target 是 absolute 或 relative
|
||||
if (getComputedStyle(this.target).position === 'static') {
|
||||
this.target.style.position = 'absolute';
|
||||
// 监听目标 DOM 是否被移除,自动销毁
|
||||
this.mutationObserver = new MutationObserver((mutations) => {
|
||||
for (const mutation of mutations) {
|
||||
mutation.removedNodes.forEach((node) => {
|
||||
if (node === this.target) {
|
||||
this.destroy();
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
if (this.target.parentElement) {
|
||||
this.mutationObserver.observe(this.target.parentElement, { childList: true });
|
||||
}
|
||||
}
|
||||
|
||||
private onMouseDown = (e: MouseEvent) => {
|
||||
const dir = this.getResizeDirection(e);
|
||||
if (dir) {
|
||||
// 开始 Resize
|
||||
e.preventDefault();
|
||||
this.startResize(e, dir);
|
||||
} else {
|
||||
// 开始 Drag
|
||||
private onMouseDownDrag = (e: MouseEvent) => {
|
||||
if (this.getResizeDirection(e)) return; // 避免和 resize 冲突
|
||||
e.preventDefault();
|
||||
this.startDrag(e);
|
||||
}
|
||||
};
|
||||
|
||||
private startDrag(e: MouseEvent) {
|
||||
@@ -205,20 +197,21 @@ export class DraggableResizable {
|
||||
|
||||
if (this.mode === 'position') {
|
||||
const rect = this.target.getBoundingClientRect();
|
||||
this.offsetX = rect.left;
|
||||
this.offsetY = rect.top;
|
||||
const parentRect = this.target.offsetParent?.getBoundingClientRect() ?? { left: 0, top: 0 };
|
||||
this.offsetX = rect.left - parentRect.left;
|
||||
this.offsetY = rect.top - parentRect.top;
|
||||
} else {
|
||||
this.offsetX = this.currentX;
|
||||
this.offsetY = this.currentY;
|
||||
}
|
||||
|
||||
document.addEventListener('mousemove', this.onDragMove);
|
||||
document.addEventListener('mouseup', this.onDragEnd);
|
||||
document.addEventListener('mousemove', this.onMouseMoveDrag);
|
||||
document.addEventListener('mouseup', this.onMouseUpDrag);
|
||||
|
||||
this.onStart?.(this.offsetX, this.offsetY);
|
||||
this.onDragStart?.(this.offsetX, this.offsetY);
|
||||
}
|
||||
|
||||
private onDragMove = (e: MouseEvent) => {
|
||||
private onMouseMoveDrag = (e: MouseEvent) => {
|
||||
if (!this.isDragging) return;
|
||||
|
||||
const dx = e.clientX - this.startX;
|
||||
@@ -233,10 +226,10 @@ export class DraggableResizable {
|
||||
}
|
||||
|
||||
this.applyPosition(newX, newY, false);
|
||||
this.onMove?.(newX, newY);
|
||||
this.onDragMove?.(newX, newY);
|
||||
};
|
||||
|
||||
private onDragEnd = () => {
|
||||
private onMouseUpDrag = () => {
|
||||
if (!this.isDragging) return;
|
||||
this.isDragging = false;
|
||||
|
||||
@@ -244,15 +237,15 @@ export class DraggableResizable {
|
||||
|
||||
if (this.snapAnimation) {
|
||||
this.animateTo(snapped.x, snapped.y, this.snapAnimationDuration, () => {
|
||||
this.onEnd?.(snapped.x, snapped.y);
|
||||
this.onDragEnd?.(snapped.x, snapped.y);
|
||||
});
|
||||
} else {
|
||||
this.applyPosition(snapped.x, snapped.y, true);
|
||||
this.onEnd?.(snapped.x, snapped.y);
|
||||
this.onDragEnd?.(snapped.x, snapped.y);
|
||||
}
|
||||
|
||||
document.removeEventListener('mousemove', this.onDragMove);
|
||||
document.removeEventListener('mouseup', this.onDragEnd);
|
||||
document.removeEventListener('mousemove', this.onMouseMoveDrag);
|
||||
document.removeEventListener('mouseup', this.onMouseUpDrag);
|
||||
};
|
||||
|
||||
private applyPosition(x: number, y: number, isFinal: boolean) {
|
||||
@@ -269,177 +262,24 @@ export class DraggableResizable {
|
||||
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;
|
||||
const deltaY = targetY - startY;
|
||||
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;
|
||||
|
||||
this.applyPosition(x, y, false);
|
||||
this.onMove?.(x, y);
|
||||
|
||||
if (progress < 1) {
|
||||
this.animationFrame = requestAnimationFrame(step);
|
||||
} else {
|
||||
this.applyPosition(targetX, targetY, true);
|
||||
this.onMove?.(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)) {
|
||||
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.applyPosition(x, y, false);
|
||||
}
|
||||
|
||||
private applySnapping(x: number, y: number) {
|
||||
const snapPoints = this.getSnapPoints();
|
||||
let snappedX = x;
|
||||
let snappedY = y;
|
||||
|
||||
if (this.snapThreshold > 0) {
|
||||
for (const sx of snapPoints.x) {
|
||||
if (Math.abs(x - sx) <= this.snapThreshold) {
|
||||
snappedX = sx;
|
||||
break;
|
||||
}
|
||||
}
|
||||
for (const sy of snapPoints.y) {
|
||||
if (Math.abs(y - sy) <= this.snapThreshold) {
|
||||
snappedY = sy;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return { x: snappedX, y: snappedY };
|
||||
}
|
||||
|
||||
private getSnapPoints() {
|
||||
const snapX: number[] = [];
|
||||
const snapY: number[] = [];
|
||||
|
||||
if (this.boundary instanceof HTMLElement && this.containerRect) {
|
||||
const containerRect = this.containerRect;
|
||||
const targetRect = this.target.getBoundingClientRect();
|
||||
|
||||
snapX.push(0, containerRect.width - targetRect.width);
|
||||
snapY.push(0, containerRect.height - targetRect.height);
|
||||
} else if (!(this.boundary instanceof HTMLElement)) {
|
||||
if (this.boundary?.minX !== undefined) snapX.push(this.boundary.minX);
|
||||
if (this.boundary?.maxX !== undefined) snapX.push(this.boundary.maxX);
|
||||
if (this.boundary?.minY !== undefined) snapY.push(this.boundary.minY);
|
||||
if (this.boundary?.maxY !== undefined) snapY.push(this.boundary.maxY);
|
||||
}
|
||||
|
||||
return { x: snapX, y: snapY };
|
||||
}
|
||||
|
||||
private observeResize(element: HTMLElement) {
|
||||
this.resizeObserver = new ResizeObserver(() => {
|
||||
this.containerRect = element.getBoundingClientRect();
|
||||
if (!this.allowOverflow) this.applyBoundary();
|
||||
});
|
||||
this.resizeObserver.observe(element);
|
||||
this.containerRect = element.getBoundingClientRect();
|
||||
}
|
||||
|
||||
private getResizeDirection(e: MouseEvent): TResizeDirection | null {
|
||||
const rect = this.target.getBoundingClientRect();
|
||||
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;
|
||||
const right = x >= rect.right - offset && x <= rect.right;
|
||||
// console.log('resize', top, bottom, left, right)
|
||||
|
||||
if (top && left) return 'top-left';
|
||||
if (top && right) return 'top-right';
|
||||
if (bottom && left) return 'bottom-left';
|
||||
if (bottom && right) return 'bottom-right';
|
||||
if (top) return 'top';
|
||||
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;
|
||||
}
|
||||
const cursorMap: Record<TResizeDirection, string> = {
|
||||
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];
|
||||
}
|
||||
|
||||
private onMouseMoveCursor = (e: MouseEvent) => {
|
||||
if (this.currentDirection || this.isDragging) return;
|
||||
private onMouseDownResize = (e: MouseEvent) => {
|
||||
const dir = this.getResizeDirection(e);
|
||||
this.updateCursor(dir);
|
||||
};
|
||||
|
||||
private onMouseLeave = () => {
|
||||
if (!this.currentDirection && !this.isDragging) this.updateCursor(null);
|
||||
if (!dir) return;
|
||||
e.preventDefault();
|
||||
this.startResize(e, dir);
|
||||
};
|
||||
|
||||
private startResize(e: MouseEvent, dir: TResizeDirection) {
|
||||
this.currentDirection = dir;
|
||||
const rect = this.target.getBoundingClientRect();
|
||||
const parentRect = this.target.offsetParent?.getBoundingClientRect() ?? { left: 0, top: 0 };
|
||||
|
||||
this.startX = e.clientX;
|
||||
this.startY = e.clientY;
|
||||
this.startWidth = rect.width;
|
||||
this.startHeight = rect.height;
|
||||
this.startTop = rect.top;
|
||||
this.startLeft = rect.left;
|
||||
this.startTop = rect.top - parentRect.top;
|
||||
this.startLeft = rect.left - parentRect.left;
|
||||
|
||||
document.addEventListener('mousemove', this.onResizeDrag);
|
||||
document.addEventListener('mouseup', this.onResizeEndHandler);
|
||||
@@ -501,7 +341,7 @@ export class DraggableResizable {
|
||||
this.target.style.top = `${newTop}px`;
|
||||
this.target.style.left = `${newLeft}px`;
|
||||
|
||||
this.onResize?.({
|
||||
this.onResizeMove?.({
|
||||
width: newWidth,
|
||||
height: newHeight,
|
||||
top: newTop,
|
||||
@@ -513,11 +353,12 @@ export class DraggableResizable {
|
||||
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,
|
||||
left: rect.left,
|
||||
top: rect.top - parentRect.top,
|
||||
left: rect.left - parentRect.left,
|
||||
direction: this.currentDirection,
|
||||
});
|
||||
}
|
||||
@@ -528,20 +369,191 @@ export class DraggableResizable {
|
||||
document.removeEventListener('mouseup', this.onResizeEndHandler);
|
||||
};
|
||||
|
||||
/** 销毁 */
|
||||
private getResizeDirection(e: MouseEvent): TResizeDirection | null {
|
||||
const rect = this.target.getBoundingClientRect();
|
||||
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;
|
||||
const right = x >= rect.right - offset && x <= rect.right;
|
||||
|
||||
if (top && left) return 'top-left';
|
||||
if (top && right) return 'top-right';
|
||||
if (bottom && left) return 'bottom-left';
|
||||
if (bottom && right) return 'bottom-right';
|
||||
if (top) return 'top';
|
||||
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;
|
||||
}
|
||||
const cursorMap: Record<TResizeDirection, string> = {
|
||||
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];
|
||||
}
|
||||
|
||||
private onDocumentMouseMoveCursor = (e: MouseEvent) => {
|
||||
if (this.currentDirection || this.isDragging) return;
|
||||
const dir = this.getResizeDirection(e);
|
||||
this.updateCursor(dir);
|
||||
};
|
||||
|
||||
private onMouseLeave = () => {
|
||||
if (!this.currentDirection && !this.isDragging) this.updateCursor(null);
|
||||
};
|
||||
|
||||
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;
|
||||
const deltaY = targetY - startY;
|
||||
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;
|
||||
|
||||
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?.();
|
||||
}
|
||||
};
|
||||
|
||||
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)) {
|
||||
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.applyPosition(x, y, false);
|
||||
}
|
||||
|
||||
private applySnapping(x: number, y: number) {
|
||||
let { x: snappedX, y: snappedY } = { x, y };
|
||||
|
||||
// 1. 容器吸附
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 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) {
|
||||
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 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() {
|
||||
if (this.handle) this.handle.removeEventListener('mousedown', this.onMouseDown);
|
||||
this.target.removeEventListener('mousedown', this.onMouseDown);
|
||||
this.target.removeEventListener('mousemove', this.onMouseMoveCursor);
|
||||
// 拖拽解绑:只在 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.onDragMove);
|
||||
document.removeEventListener('mouseup', this.onDragEnd);
|
||||
// 全局事件解绑
|
||||
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);
|
||||
|
||||
this.resizeObserver?.disconnect();
|
||||
this.mutationObserver.disconnect();
|
||||
// 观察器清理
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user