Compare commits

...

2 Commits

Author SHA1 Message Date
67728c5c55 逻辑修改 2025-09-12 12:53:04 +08:00
27b70ac35f 样式+拖拽逻辑修改 2025-09-12 12:20:40 +08:00
3 changed files with 52 additions and 51 deletions

View File

@@ -51,8 +51,8 @@ interface IDraggableResizableOptions {
target: HTMLElement;
/** 拖拽句柄 */
handle?: HTMLElement;
/** 拖拽边界容器元素 */
boundary?: IBoundaryRect | HTMLElement;
/** 拖拽边界容器元素 */
boundaryElement?: HTMLElement;
/** 移动步进(网格吸附) */
snapGrid?: number;
/** 关键点吸附阈值 */
@@ -110,7 +110,7 @@ interface IBoundaryRect {
export class DraggableResizableWindow {
private handle?: HTMLElement;
private target: HTMLElement;
private boundary?: HTMLElement | IBoundaryRect;
private boundaryElement: HTMLElement;
private snapGrid: number;
private snapThreshold: number;
private snapAnimation: boolean;
@@ -153,7 +153,7 @@ export class DraggableResizableWindow {
private maxWidth: number;
private maxHeight: number;
private containerRect?: DOMRect;
private containerRect: DOMRect;
private resizeObserver?: ResizeObserver;
private mutationObserver: MutationObserver;
private animationFrame?: number;
@@ -174,7 +174,7 @@ export class DraggableResizableWindow {
constructor(options: IDraggableResizableOptions) {
this.handle = options.handle;
this.target = options.target;
this.boundary = options.boundary;
this.boundaryElement = options.boundaryElement ?? document.body;
this.snapGrid = options.snapGrid ?? 1;
this.snapThreshold = options.snapThreshold ?? 0;
this.snapAnimation = options.snapAnimation ?? false;
@@ -193,15 +193,6 @@ export class DraggableResizableWindow {
this.onResizeEnd = options.onResizeEnd;
this.onWindowStateChange = options.onWindowStateChange;
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";
@@ -210,6 +201,19 @@ export class DraggableResizableWindow {
this.target.style.transform = "translate(0px, 0px)";
this.init();
requestAnimationFrame(() => {
this.targetBounds = {
width: this.target.offsetWidth,
height: this.target.offsetHeight,
top: this.target.offsetTop,
left: this.target.offsetLeft,
};
this.containerRect = this.boundaryElement.getBoundingClientRect();
const x = this.containerRect.width / 2 - this.target.offsetWidth / 2;
const y = this.containerRect.height / 2 - this.target.offsetHeight / 2;
this.target.style.transform = `translate(${x}px, ${y}px)`;
});
}
private init() {
@@ -220,7 +224,7 @@ export class DraggableResizableWindow {
this.target.addEventListener('mouseleave', this.onMouseLeave);
document.addEventListener('mousemove', this.onDocumentMouseMoveCursor);
if (this.boundary instanceof HTMLElement) this.observeResize(this.boundary);
this.observeResize(this.boundaryElement);
this.mutationObserver = new MutationObserver(mutations => {
for (const mutation of mutations) {
@@ -236,7 +240,9 @@ export class DraggableResizableWindow {
private onMouseDownDrag = (e: MouseEvent) => {
e.preventDefault();
if (e.target !== this.handle) return;
if (!this.handle?.contains(e.target as Node)) return;
const target = e.target as HTMLElement;
if (target.classList.contains('btn')) return;
if (this.getResizeDirection(e)) return;
this.startX = e.clientX;
@@ -380,19 +386,12 @@ export class DraggableResizableWindow {
}
private applyBoundary() {
if (!this.boundary || this.allowOverflow) return;
if (this.allowOverflow) return;
let { x, y } = { x: this.currentX, y: this.currentY };
if (this.boundary instanceof HTMLElement && this.containerRect) {
const rect = this.target.getBoundingClientRect();
x = Math.min(Math.max(x, 0), this.containerRect.width - rect.width);
y = Math.min(Math.max(y, 0), this.containerRect.height - rect.height);
} else if (!(this.boundary instanceof HTMLElement) && this.boundary) {
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;
@@ -411,16 +410,9 @@ export class DraggableResizableWindow {
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 && !(this.boundary instanceof HTMLElement)) {
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;
}
@@ -515,7 +507,7 @@ export class DraggableResizableWindow {
newHeight = Math.max(this.minHeight, Math.min(this.maxHeight, newHeight));
// 边界限制
if (!this.boundary || this.allowOverflow) {
if (this.allowOverflow) {
this.currentX = newX;
this.currentY = newY;
this.target.style.width = `${newWidth}px`;
@@ -524,15 +516,8 @@ export class DraggableResizableWindow {
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;

View File

@@ -1,6 +1,9 @@
$titleBarHeight: 40px;
/* 窗体容器 */
.window {
width: 400px;
width: 100%;
height: 100%;
border: 1px solid #666;
box-shadow: 0 0 10px rgba(0,0,0,0.5);
background-color: #ffffff;
@@ -10,11 +13,23 @@
/* 标题栏 */
.title-bar {
color: white;
display: flex;
justify-content: space-between;
align-items: center;
user-select: none;
width: 100%;
height: $titleBarHeight;
.title {
display: block;
padding: 0 5px;
flex-grow: 1;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
font-size: 18px;
line-height: $titleBarHeight;
}
.window-controls {
display: flex;
@@ -42,6 +57,7 @@
/* 窗体内容 */
.window-content {
padding: 15px;
width: 100%;
height: calc(100% - #{$titleBarHeight});
background-color: #e0e0e0;
}

View File

@@ -53,7 +53,7 @@ export default class WindowFormImpl implements IWindowForm {
handle: header,
snapAnimation: true,
snapThreshold: 20,
boundary: document.body,
boundaryElement: document.body,
taskbarElementId: '#taskbar',
onWindowStateChange: (state) => {
if (state === 'maximized') {