保存一下
This commit is contained in:
@@ -13,7 +13,9 @@
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="task-bar"></div>
|
<div class="task-bar">
|
||||||
|
<div id="taskbar" class="w-[80px] h-full flex justify-center items-center bg-blue">测试</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</n-config-provider>
|
</n-config-provider>
|
||||||
</template>
|
</template>
|
||||||
@@ -70,7 +72,7 @@ $taskBarHeight: 40px;
|
|||||||
}
|
}
|
||||||
|
|
||||||
.task-bar {
|
.task-bar {
|
||||||
@apply w-full bg-gray-200;
|
@apply w-full bg-gray-200 flex justify-center items-center;
|
||||||
height: $taskBarHeight;
|
height: $taskBarHeight;
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,12 +19,7 @@ type TResizeDirection =
|
|||||||
/** 窗口状态 */
|
/** 窗口状态 */
|
||||||
type WindowState = 'default' | 'minimized' | 'maximized';
|
type WindowState = 'default' | 'minimized' | 'maximized';
|
||||||
|
|
||||||
interface TaskbarOptions {
|
/** 元素边界 */
|
||||||
/** 任务栏图标 DOM 元素或目标位置 {x, y} */
|
|
||||||
element?: HTMLElement;
|
|
||||||
position?: { x: number; y: number };
|
|
||||||
}
|
|
||||||
|
|
||||||
interface IElementRect {
|
interface IElementRect {
|
||||||
/** 宽度 */
|
/** 宽度 */
|
||||||
width: number;
|
width: number;
|
||||||
@@ -70,6 +65,8 @@ interface IDraggableResizableOptions {
|
|||||||
snapAnimationDuration?: number;
|
snapAnimationDuration?: number;
|
||||||
/** 是否允许超出边界 */
|
/** 是否允许超出边界 */
|
||||||
allowOverflow?: boolean;
|
allowOverflow?: boolean;
|
||||||
|
/** 最小化任务栏位置的元素ID */
|
||||||
|
taskbarElementId: string;
|
||||||
|
|
||||||
/** 拖拽开始回调 */
|
/** 拖拽开始回调 */
|
||||||
onDragStart?: TDragStartCallback;
|
onDragStart?: TDragStartCallback;
|
||||||
@@ -106,7 +103,7 @@ interface IBoundaryRect {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 拖拽 + 调整尺寸通用类
|
* 拖拽 + 调整尺寸 + 最大最小化 通用类
|
||||||
*/
|
*/
|
||||||
export class DraggableResizableWindow {
|
export class DraggableResizableWindow {
|
||||||
private handle?: HTMLElement;
|
private handle?: HTMLElement;
|
||||||
@@ -153,10 +150,10 @@ export class DraggableResizableWindow {
|
|||||||
private targetDefaultBounds: IElementRect;
|
private targetDefaultBounds: IElementRect;
|
||||||
/** 最大化前保存 bounds */
|
/** 最大化前保存 bounds */
|
||||||
private maximizedBounds?: IElementRect;
|
private maximizedBounds?: IElementRect;
|
||||||
/** 任务栏相关配置 */
|
/** 最小化任务栏位置的元素ID */
|
||||||
private taskbar?: TaskbarOptions;
|
private taskbarElementId: string;
|
||||||
|
|
||||||
constructor(options: IDraggableResizableOptions & { taskbar?: TaskbarOptions }) {
|
constructor(options: IDraggableResizableOptions) {
|
||||||
// Drag
|
// Drag
|
||||||
this.handle = options.handle;
|
this.handle = options.handle;
|
||||||
this.target = options.target;
|
this.target = options.target;
|
||||||
@@ -181,7 +178,7 @@ export class DraggableResizableWindow {
|
|||||||
this.onResizeEnd = options.onResizeEnd;
|
this.onResizeEnd = options.onResizeEnd;
|
||||||
|
|
||||||
this.targetDefaultBounds = { width: this.target.offsetWidth, height: this.target.offsetHeight, top: this.target.offsetTop, left: this.target.offsetLeft };
|
this.targetDefaultBounds = { width: this.target.offsetWidth, height: this.target.offsetHeight, top: this.target.offsetTop, left: this.target.offsetLeft };
|
||||||
this.taskbar = options.taskbar;
|
this.taskbarElementId = options.taskbarElementId;
|
||||||
|
|
||||||
this.init();
|
this.init();
|
||||||
}
|
}
|
||||||
@@ -600,53 +597,131 @@ export class DraggableResizableWindow {
|
|||||||
if (this.state === 'minimized') return;
|
if (this.state === 'minimized') return;
|
||||||
this.state = 'minimized';
|
this.state = 'minimized';
|
||||||
|
|
||||||
// 获取目标任务栏位置
|
// 获取任务栏位置
|
||||||
let targetX = 50, targetY = window.innerHeight - 40;
|
const taskbarElement = document.querySelector(this.taskbarElementId)
|
||||||
if (this.taskbar?.element) {
|
if (!taskbarElement) throw new Error('任务栏元素未找到');
|
||||||
const rect = this.taskbar.element.getBoundingClientRect();
|
const rect = taskbarElement.getBoundingClientRect()
|
||||||
targetX = rect.left;
|
const targetX = rect.left;
|
||||||
targetY = rect.top;
|
const targetY = rect.top;
|
||||||
} else if (this.taskbar?.position) {
|
const targetWidth = rect.width;
|
||||||
targetX = this.taskbar.position.x;
|
const targetHeight = rect.height;
|
||||||
targetY = this.taskbar.position.y;
|
|
||||||
|
const startX = this.currentX;
|
||||||
|
const startY = this.currentY;
|
||||||
|
const startWidth = this.target.offsetWidth;
|
||||||
|
const startHeight = this.target.offsetHeight;
|
||||||
|
|
||||||
|
const deltaX = targetX - startX;
|
||||||
|
const deltaY = targetY - startY;
|
||||||
|
const deltaW = targetWidth - startWidth;
|
||||||
|
const deltaH = targetHeight - startHeight;
|
||||||
|
const duration = 400; // Windows 风格稍慢
|
||||||
|
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;
|
||||||
|
const w = startWidth + deltaW * ease;
|
||||||
|
const h = startHeight + deltaH * ease;
|
||||||
|
|
||||||
|
this.target.style.width = `${w}px`;
|
||||||
|
this.target.style.height = `${h}px`;
|
||||||
|
this.applyPosition(x, y, false);
|
||||||
|
|
||||||
|
if (progress < 1) {
|
||||||
|
requestAnimationFrame(step);
|
||||||
|
} else {
|
||||||
|
// 最终值
|
||||||
|
this.target.style.width = `${targetWidth}px`;
|
||||||
|
this.target.style.height = `${targetHeight}px`;
|
||||||
|
this.applyPosition(targetX, targetY, true);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
requestAnimationFrame(step);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.animateTo(targetX, targetY, 300, () => {
|
/** 最大化窗口(带动画) */
|
||||||
this.target.style.width = '0px';
|
public maximize(withAnimation = true) {
|
||||||
this.target.style.height = '0px';
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/** 最大化 */
|
|
||||||
public maximize() {
|
|
||||||
if (this.state === 'maximized') return;
|
if (this.state === 'maximized') return;
|
||||||
this.state = 'maximized';
|
this.state = 'maximized';
|
||||||
|
|
||||||
|
// 保存原始 bounds
|
||||||
const rect = this.target.getBoundingClientRect();
|
const rect = this.target.getBoundingClientRect();
|
||||||
this.targetDefaultBounds = { width: rect.width, height: rect.height, top: rect.top, left: rect.left };
|
this.targetDefaultBounds = { width: rect.width, height: rect.height, top: rect.top, left: rect.left };
|
||||||
this.maximizedBounds = { ...this.targetDefaultBounds };
|
const startWidth = rect.width;
|
||||||
|
const startHeight = rect.height;
|
||||||
|
const startX = this.currentX;
|
||||||
|
const startY = this.currentY;
|
||||||
|
|
||||||
const width = this.containerRect?.width ?? window.innerWidth;
|
const targetWidth = this.containerRect?.width ?? window.innerWidth;
|
||||||
const height = this.containerRect?.height ?? window.innerHeight;
|
const targetHeight = this.containerRect?.height ?? window.innerHeight;
|
||||||
|
const targetX = 0;
|
||||||
|
const targetY = 0;
|
||||||
|
|
||||||
this.target.style.width = `${width}px`;
|
if (!withAnimation) {
|
||||||
this.target.style.height = `${height}px`;
|
this.target.style.width = `${targetWidth}px`;
|
||||||
this.applyPosition(0, 0, true);
|
this.target.style.height = `${targetHeight}px`;
|
||||||
|
this.applyPosition(targetX, targetY, true);
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 恢复到默认状态 */
|
// 动画过渡
|
||||||
|
const deltaX = targetX - startX;
|
||||||
|
const deltaY = targetY - startY;
|
||||||
|
const deltaW = targetWidth - startWidth;
|
||||||
|
const deltaH = targetHeight - startHeight;
|
||||||
|
const duration = 300;
|
||||||
|
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;
|
||||||
|
const w = startWidth + deltaW * ease;
|
||||||
|
const h = startHeight + deltaH * ease;
|
||||||
|
|
||||||
|
this.target.style.width = `${w}px`;
|
||||||
|
this.target.style.height = `${h}px`;
|
||||||
|
this.applyPosition(x, y, false);
|
||||||
|
|
||||||
|
if (progress < 1) {
|
||||||
|
requestAnimationFrame(step);
|
||||||
|
} else {
|
||||||
|
this.target.style.width = `${targetWidth}px`;
|
||||||
|
this.target.style.height = `${targetHeight}px`;
|
||||||
|
this.applyPosition(targetX, targetY, true);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
requestAnimationFrame(step);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 恢复窗口(带动画,从最大化或最小化) */
|
||||||
public restore(withAnimation = true) {
|
public restore(withAnimation = true) {
|
||||||
if (this.state === 'default') return;
|
if (this.state === 'default') return;
|
||||||
this.state = 'default';
|
this.state = 'default';
|
||||||
const b = this.targetDefaultBounds;
|
const b = this.targetDefaultBounds;
|
||||||
|
|
||||||
if (withAnimation) {
|
|
||||||
// 从当前位置(可能是任务栏位置或 0 尺寸)动画过渡到 targetDefaultBounds
|
|
||||||
const startWidth = this.target.offsetWidth || 0;
|
const startWidth = this.target.offsetWidth || 0;
|
||||||
const startHeight = this.target.offsetHeight || 0;
|
const startHeight = this.target.offsetHeight || 0;
|
||||||
const startLeft = this.currentX;
|
const startLeft = this.currentX;
|
||||||
const startTop = this.currentY;
|
const startTop = this.currentY;
|
||||||
|
|
||||||
|
if (!withAnimation) {
|
||||||
|
this.target.style.width = `${b.width}px`;
|
||||||
|
this.target.style.height = `${b.height}px`;
|
||||||
|
this.applyPosition(b.left, b.top, true);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const deltaX = b.left - startLeft;
|
const deltaX = b.left - startLeft;
|
||||||
const deltaY = b.top - startTop;
|
const deltaY = b.top - startTop;
|
||||||
const deltaW = b.width - startWidth;
|
const deltaW = b.width - startWidth;
|
||||||
@@ -657,7 +732,7 @@ export class DraggableResizableWindow {
|
|||||||
const step = (now: number) => {
|
const step = (now: number) => {
|
||||||
const elapsed = now - startTime;
|
const elapsed = now - startTime;
|
||||||
const progress = Math.min(elapsed / duration, 1);
|
const progress = Math.min(elapsed / duration, 1);
|
||||||
const ease = 1 - Math.pow(1 - progress, 3); // 缓动函数
|
const ease = 1 - Math.pow(1 - progress, 3);
|
||||||
|
|
||||||
const x = startLeft + deltaX * ease;
|
const x = startLeft + deltaX * ease;
|
||||||
const y = startTop + deltaY * ease;
|
const y = startTop + deltaY * ease;
|
||||||
@@ -671,7 +746,6 @@ export class DraggableResizableWindow {
|
|||||||
if (progress < 1) {
|
if (progress < 1) {
|
||||||
requestAnimationFrame(step);
|
requestAnimationFrame(step);
|
||||||
} else {
|
} else {
|
||||||
// 最终值
|
|
||||||
this.target.style.width = `${b.width}px`;
|
this.target.style.width = `${b.width}px`;
|
||||||
this.target.style.height = `${b.height}px`;
|
this.target.style.height = `${b.height}px`;
|
||||||
this.applyPosition(b.left, b.top, true);
|
this.applyPosition(b.left, b.top, true);
|
||||||
@@ -679,14 +753,9 @@ export class DraggableResizableWindow {
|
|||||||
};
|
};
|
||||||
|
|
||||||
requestAnimationFrame(step);
|
requestAnimationFrame(step);
|
||||||
} else {
|
|
||||||
// 不动画直接恢复
|
|
||||||
this.target.style.width = `${b.width}px`;
|
|
||||||
this.target.style.height = `${b.height}px`;
|
|
||||||
this.applyPosition(b.left, b.top, true);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/** 更新默认 bounds */
|
/** 更新默认 bounds */
|
||||||
private updateDefaultBounds(x?: number, y?: number, width?: number, height?: number) {
|
private updateDefaultBounds(x?: number, y?: number, width?: number, height?: number) {
|
||||||
const rect = this.target.getBoundingClientRect();
|
const rect = this.target.getBoundingClientRect();
|
||||||
|
|||||||
@@ -80,7 +80,7 @@ export default class WindowFormImpl implements IWindowForm {
|
|||||||
mode: 'position',
|
mode: 'position',
|
||||||
snapThreshold: 20,
|
snapThreshold: 20,
|
||||||
boundary: document.body,
|
boundary: document.body,
|
||||||
taskbar: { position: { x: 50, y: window.innerHeight - 40 } },
|
taskbarElementId: '#taskbar',
|
||||||
})
|
})
|
||||||
|
|
||||||
this.desktopRootDom.appendChild(dom);
|
this.desktopRootDom.appendChild(dom);
|
||||||
|
|||||||
Reference in New Issue
Block a user