修改组件逻辑+优化代码

This commit is contained in:
2025-11-11 17:20:43 +08:00
parent ce688a6834
commit f2bb7f3196
2 changed files with 306 additions and 162 deletions

View File

@@ -45,7 +45,7 @@ body {
background-color: var(--color-light);
-webkit-font-smoothing: antialiased; /* 字体抗锯齿 */
text-rendering: optimizeLegibility;
overflow-x: hidden;
overflow: hidden;
}
/* ===== 排版元素 ===== */

View File

@@ -42,10 +42,10 @@ interface IElementRect {
width: number;
/** 高度 */
height: number;
/** 顶点坐标(相对 offsetParent */
top: number;
/** 左点坐标(相对 offsetParent */
left: number;
/** x坐标 */
x: number;
/** y坐标 */
y: number;
}
/** 窗口自定义事件 */
@@ -75,38 +75,50 @@ export class WindowFormElement extends LitElement {
@property({ type: Boolean }) closable = true
@property({ type: Boolean, reflect: true }) focused: boolean = true
@property({ type: String, reflect: true }) windowFormState: TWindowFormState = 'default'
@property({ type: Object }) dragContainer?: HTMLElement
@property({ type: Boolean }) allowOverflow = true // 允许窗口超出容器
@property({ type: Number }) snapDistance = 20 // 吸附距离
@property({ type: Boolean }) snapAnimation = true // 吸附动画
@property({ type: Object }) dragContainer?: HTMLElement // 元素的父容器
@property({ type: Number }) snapDistance = 0 // 吸附距离
@property({ type: Boolean }) snapAnimation = false // 吸附动画
@property({ type: Number }) snapAnimationDuration = 300 // 吸附动画时长 ms
@property({ type: Number }) maxWidth?: number = Infinity
@property({ type: Number }) minWidth?: number = 0
@property({ type: Number }) minWidth?: number = 200
@property({ type: Number }) maxHeight?: number = Infinity
@property({ type: Number }) minHeight?: number = 0
@property({ type: Number }) minHeight?: number = 200
@property({ type: String }) taskbarElementId?: string
@property({ type: Object }) wfData: any;
private _listeners: Array<{ type: string; original: Function; wrapped: EventListener }> = []
// ==== 拖拽/缩放状态(内部变量,不触发渲染) ====
private dragging = false
private resizeDir: TResizeDirection | null = null
private startX = 0
private startY = 0
private startWidth = 0
private startHeight = 0
private startX_host = 0
private startY_host = 0
// 自身的x坐标
private x = 0
// 自身的y坐标
private y = 0
private preX = 0
private preY = 0
// 自身的宽度
private width = 640
// 自身的高度
private height = 360
// 记录拖拽开始时自身x坐标
private originalX = 0
// 记录拖拽开始时自身y坐标
private originalY = 0
// 鼠标开始拖拽时自身宽度
private originalWidth = 640
// 鼠标开始拖拽时高度
private originalHeight = 360
// 鼠标开始拖拽时x坐标
private pointStartX = 0
// 鼠标开始拖拽时x坐标
private pointStartY = 0
private animationFrame?: number
// 是否拖拽状态
private dragging = false
// 是否缩放状态
private resizing = false
// 缩放方向
private resizeDir: TResizeDirection | null = null
// private get x() {
// return this.wfData.state.x
@@ -198,25 +210,28 @@ export class WindowFormElement extends LitElement {
}
override firstUpdated() {
console.log(this.wfData)
// wfem.addEventListener('windowFormFocus', this.windowFormFocusFun)
window.addEventListener('pointerup', this.onPointerUp)
window.addEventListener('pointermove', this.onPointerMove)
this.addEventListener('pointerdown', this.toggleFocus)
const { width, height } = this.getBoundingClientRect()
this.width = width || this.width
this.height = height || this.height
const container = this.dragContainer || document.body
const containerRect = container.getBoundingClientRect()
this.x = containerRect.width / 2 - this.width / 2
this.y = containerRect.height / 2 - this.height / 2
this.style.width = `${this.width}px`
this.style.height = `${this.height}px`
this.style.transform = `translate(${this.x}px, ${this.y}px)`
window.addEventListener('pointerup', this.onPointerUp)
window.addEventListener('pointermove', this.onPointerMove)
this.addEventListener('pointerdown', this.toggleFocus)
this.targetBounds = {
width: this.offsetWidth,
height: this.offsetHeight,
top: this.x,
left: this.y,
width: this.width,
height: this.height,
x: this.x,
y: this.y,
}
}
@@ -257,10 +272,10 @@ export class WindowFormElement extends LitElement {
e.preventDefault()
this.dragging = true
this.startX = e.clientX
this.startY = e.clientY
this.preX = this.x
this.preY = this.y
this.pointStartX = e.clientX
this.pointStartY = e.clientY
this.originalX = this.x
this.originalY = this.y
this.setPointerCapture?.(e.pointerId)
this.dispatchEvent(
@@ -272,15 +287,20 @@ export class WindowFormElement extends LitElement {
)
}
/**
* 鼠标指针移动
* @param e
*/
private onPointerMove = (e: PointerEvent) => {
if (this.dragging) {
const dx = e.clientX - this.startX
const dy = e.clientY - this.startY
const dx = e.clientX - this.pointStartX
const dy = e.clientY - this.pointStartY
const x = this.preX + dx
const y = this.preY + dy
const x = this.originalX + dx
const y = this.originalY + dy
this.applyPosition(x, y, false)
const pos = this.applyBoundary(x, y, e.clientX, e.clientY)
this.applyPosition(pos.x, pos.y)
this.dispatchEvent(
new CustomEvent('windowForm:dragMove', {
detail: { x, y },
@@ -293,6 +313,10 @@ export class WindowFormElement extends LitElement {
}
}
/**
* 鼠标指针抬起
* @param e
*/
private onPointerUp = (e: PointerEvent) => {
if (this.dragging) {
this.dragUp(e)
@@ -320,13 +344,14 @@ export class WindowFormElement extends LitElement {
}
/**
* 获取最近的吸附点
* @param x 左上角起始点x
* @param y 左上角起始点y
* 根据传入的坐标点位计算吸附距离最近的坐标位置
* @param x 坐标点 x
* @param y 坐标点 y
* @returns {x: number, y: number} 新的位置坐标
*/
private applySnapping(x: number, y: number) {
let snappedX = x,
snappedY = y
private calculateSnapping(x: number, y: number): { x: number, y: number} {
let snappedX = x
let snappedY = y
const containerSnap = this.getSnapPoints()
if (this.snapDistance > 0) {
for (const sx of containerSnap.x)
@@ -343,22 +368,28 @@ export class WindowFormElement extends LitElement {
return { x: snappedX, y: snappedY }
}
/**
* 拖拽结束
* @param e
* @private
*/
private dragUp(e: PointerEvent) {
const snapped = this.applySnapping(this.x, this.y)
const snapped = this.calculateSnapping(this.x, this.y)
if (this.snapAnimation) {
this.animateTo(snapped.x, snapped.y, this.snapAnimationDuration, () => {
this.updateTargetBounds(snapped.x, snapped.y)
this.dispatchEvent(
new CustomEvent('windowForm:dragEnd', {
this.animateTo(this.x, this.y, snapped.x, snapped.y, this.snapAnimationDuration,
(x, y) => {
this.applyPosition(x, y)
},
(x, y) => {
this.applyPosition(snapped.x, snapped.y)
this.dispatchEvent(new CustomEvent('windowForm:dragEnd', {
detail: { x: snapped.x, y: snapped.y },
bubbles: true,
composed: true,
}),
)
}))
})
} else {
this.applyPosition(snapped.x, snapped.y, true)
this.updateTargetBounds(snapped.x, snapped.y)
this.applyPosition(snapped.x, snapped.y)
this.dispatchEvent(
new CustomEvent('windowForm:dragEnd', {
detail: { x: snapped.x, y: snapped.y },
@@ -369,29 +400,65 @@ export class WindowFormElement extends LitElement {
}
}
private applyPosition(x: number, y: number, isFinal: boolean) {
/**
* 根据鼠标指针的位置是否在容器边界内来限制窗口坐标
* @param x 当前元素的左上角坐标 x
* @param y 当前元素的左上角坐标 y
* @param pointerX 当前鼠标在容器中的 X 坐标
* @param pointerY 当前鼠标在容器中的 Y 坐标
* @returns 限制后的坐标点 { x, y }
*/
private applyBoundary(x: number, y: number, pointerX: number, pointerY: number): { x: number; y: number } {
const containerRect = (this.dragContainer || document.body).getBoundingClientRect()
// 限制指针在容器内
const limitedPointerX = Math.min(Math.max(pointerX, containerRect.left), containerRect.right)
const limitedPointerY = Math.min(Math.max(pointerY, containerRect.top), containerRect.bottom)
// 计算指针被限制后的偏移量
const dx = limitedPointerX - pointerX
const dy = limitedPointerY - pointerY
// 根据指针偏移调整窗口位置
x += dx
y += dy
return { x, y }
}
/**
* 设置拖拽的窗口位置
* @param x 当前元素的左上角坐标点 x
* @param y 当前元素的左上角坐标点 y
* @private
*/
private applyPosition(x: number, y: number) {
this.x = x
this.y = y
this.style.transform = `translate(${x}px, ${y}px)`
if (isFinal) this.applyBoundary()
}
private applyBoundary() {
if (this.allowOverflow) return
let { x, y } = { x: this.x, y: this.y }
const rect = this.getBoundingClientRect()
const containerRect = (this.dragContainer || document.body).getBoundingClientRect()
x = Math.min(Math.max(x, 0), containerRect.width - rect.width)
y = Math.min(Math.max(y, 0), containerRect.height - rect.height)
this.applyPosition(x, y, false)
}
private animateTo(targetX: number, targetY: number, duration: number, onComplete?: () => void) {
/**
* 动画移动窗口
* @param startX 窗口起始点 x
* @param startY 窗口起始点 y
* @param targetX 目标点 x
* @param targetY 目标点 y
* @param duration 动画时长
* @param onMove 移动回调
* @param onComplete 完成回调
* @private
*/
private animateTo(
startX: number,
startY: number,
targetX: number,
targetY: number,
duration: number,
onMove?: (x: number, y: number) => void,
onComplete?: (x: number, y: number) => void
) {
if (this.animationFrame) cancelAnimationFrame(this.animationFrame)
const startX = this.x
const startY = this.y
const deltaX = targetX - startX
const deltaY = targetY - startY
const startTime = performance.now()
@@ -404,7 +471,7 @@ export class WindowFormElement extends LitElement {
const x = startX + deltaX * ease
const y = startY + deltaY * ease
this.applyPosition(x, y, false)
onMove?.(x, y)
this.dispatchEvent(
new CustomEvent('windowForm:dragMove', {
detail: { x, y },
@@ -416,8 +483,7 @@ export class WindowFormElement extends LitElement {
if (progress < 1) {
this.animationFrame = requestAnimationFrame(step)
} else {
this.applyPosition(targetX, targetY, true)
onComplete?.()
onComplete?.(targetX, targetY)
}
}
this.animationFrame = requestAnimationFrame(step)
@@ -432,14 +498,12 @@ export class WindowFormElement extends LitElement {
e.stopPropagation()
this.resizing = true
this.resizeDir = dir
this.startX = e.clientX
this.startY = e.clientY
const rect = this.getBoundingClientRect()
this.startWidth = rect.width
this.startHeight = rect.height
this.startX_host = rect.left
this.startY_host = rect.top
this.pointStartX = e.clientX
this.pointStartY = e.clientY
this.originalX = this.x
this.originalY = this.y
this.originalWidth = this.width
this.originalHeight = this.height
const target = e.target as HTMLElement
document.body.style.cursor = target.style.cursor || window.getComputedStyle(target).cursor
@@ -454,59 +518,72 @@ export class WindowFormElement extends LitElement {
)
}
/**
* 缩放
* @param e
* @private
*/
private performResize(e: PointerEvent) {
if (!this.resizeDir || !this.resizing) return
let newWidth = this.startWidth
let newHeight = this.startHeight
let newX = this.startX_host
let newY = this.startY_host
let newWidth = this.originalWidth
let newHeight = this.originalHeight
let newX = this.originalX
let newY = this.originalY
const dx = e.clientX - this.startX
const dy = e.clientY - this.startY
const dx = e.clientX - this.pointStartX
const dy = e.clientY - this.pointStartY
// ====== 根据方向计算临时尺寸与位置 ======
switch (this.resizeDir) {
case 'r':
case 'r': // 右侧
newWidth += dx
break
case 'b':
case 'b': // 下方
newHeight += dy
break
case 'l':
case 'l': // 左侧
newWidth -= dx
newX += dx
break
case 't':
case 't': // 上方
newHeight -= dy
newY += dy
break
case 'tl':
case 'tl': // 左上角
newWidth -= dx
newX += dx
newHeight -= dy
newY += dy
break
case 'tr':
case 'tr': // 右上角
newWidth += dx
newHeight -= dy
newY += dy
break
case 'bl':
case 'bl': // 左下角
newWidth -= dx
newX += dx
newHeight += dy
break
case 'br':
case 'br': // 右下角
newWidth += dx
newHeight += dy
break
}
const d = this.applyResizeBounds(newX, newY, newWidth, newHeight)
const { x, y, width, height } = this.applyResizeBounds(newX, newY, newWidth, newHeight, this.resizeDir)
this.x = x
this.y = y
this.width = width
this.height = height
this.style.width = `${this.width}px`
this.style.height = `${this.height}px`
this.style.transform = `translate(${this.x}px, ${this.y}px)`
this.dispatchEvent(
new CustomEvent('windowForm:resizeMove', {
detail: { dir: this.resizeDir, width: d.width, height: d.height, left: d.left, top: d.top },
detail: { dir: this.resizeDir, width: width, height: height, left: x, top: y },
bubbles: true,
composed: true,
}),
@@ -514,68 +591,107 @@ export class WindowFormElement extends LitElement {
}
/**
* 应用尺寸调整边界
* @param newX 新的X坐标
* @param newY 新的Y坐标
* @param newWidth 新的宽度
* @param newHeight 新的高度
* 计算尺寸调整约束逻辑,返回约束后的尺寸
* @param x 坐标 x
* @param y 坐标 y
* @param width 宽度
* @param height 高度
* @private
* @returns { x: number, y: number, width: number, height: number } 约束后的尺寸
*/
private applyResizeBounds(
newX: number,
newY: number,
newWidth: number,
newHeight: number,
x: number,
y: number,
width: number,
height: number,
resizeDir: TResizeDirection
): {
left: number
top: number
x: number
y: number
width: number
height: number
} {
// 最小/最大宽高限制
if (this.minWidth != null) newWidth = Math.max(this.minWidth, newWidth)
if (this.maxWidth != null) newWidth = Math.min(this.maxWidth, newWidth)
if (this.minHeight != null) newHeight = Math.max(this.minHeight, newHeight)
if (this.maxHeight != null) newHeight = Math.min(this.maxHeight, newHeight)
const { minWidth = 100, minHeight = 100, maxWidth = Infinity, maxHeight = Infinity } = this
// 边界限制
if (this.allowOverflow) {
this.x = newX
this.y = newY
this.width = newWidth
this.height = newHeight
this.style.width = `${newWidth}px`
this.style.height = `${newHeight}px`
this.applyPosition(newX, newY, false)
return {
left: newX,
top: newY,
width: newWidth,
height: newHeight,
}
//#region 限制最大/最小尺寸
// 限制宽度
if (width < minWidth) {
// 左缩时要修正X坐标否则会跳动
if (resizeDir.includes('l')) x -= minWidth - width
width = minWidth
} else if (width > maxWidth) {
if (resizeDir.includes('l')) x += width - maxWidth
width = maxWidth
}
// 限制高度
if (height < minHeight) {
if (resizeDir.includes('t')) y -= minHeight - height
height = minHeight
} else if (height > maxHeight) {
if (resizeDir.includes('t')) y += height - maxHeight
height = maxHeight
}
//#endregion
//#region 限制在容器边界内
const containerRect = (this.dragContainer || document.body).getBoundingClientRect()
newX = Math.min(Math.max(0, newX), containerRect.width - newWidth)
newY = Math.min(Math.max(0, newY), containerRect.height - newHeight)
const maxLeft = containerRect.width - width
const maxTop = containerRect.height - height
this.x = newX
this.y = newY
this.width = newWidth
this.height = newHeight
this.style.width = `${newWidth}px`
this.style.height = `${newHeight}px`
this.applyPosition(newX, newY, false)
// 左越界(从左侧缩放时)
if (x < 0) {
if (resizeDir.includes('l')) {
// 如果是往左拉出容器,锁定边界
width += x // 因为x是负数相当于减小宽度
}
x = 0
}
// 顶部越界(从上侧缩放时)
if (y < 0) {
if (resizeDir.includes('t')) {
height += y // y是负数相当于减小高度
}
y = 0
}
// 右越界(从右侧缩放时)
if (x + width > containerRect.width) {
if (resizeDir.includes('r')) {
width = containerRect.width - x
} else {
x = Math.min(x, maxLeft)
}
}
// 底部越界(从下侧缩放时)
if (y + height > containerRect.height) {
if (resizeDir.includes('b')) {
height = containerRect.height - y
} else {
y = Math.min(y, maxTop)
}
}
//#endregion
// 二次防护:确保不小于最小值
width = Math.max(width, minWidth)
height = Math.max(height, minHeight)
return {
left: newX,
top: newY,
width: newWidth,
height: newHeight,
x,
y,
width,
height
}
}
/**
* 缩放结束
* @param e
* @private
*/
private resizeUp(e: PointerEvent) {
if (!this.resizable) return
@@ -617,12 +733,16 @@ export class WindowFormElement extends LitElement {
startY,
startW,
startH,
rect.left,
rect.top,
rect.x,
rect.y,
rect.width,
rect.height,
400,
() => {
(x, y, w, h) => {
this.applyWindowStyle(x, y, w, h)
},
(x, y, w, h) => {
this.applyWindowStyle(x, y, w, h)
this.style.display = 'none'
this.dispatchEvent(
new CustomEvent('windowForm:stateChange:minimize', {
@@ -667,7 +787,11 @@ export class WindowFormElement extends LitElement {
targetW,
targetH,
300,
() => {
(x, y, w, h) => {
this.applyWindowStyle(x, y, w, h)
},
(x, y, w, h) => {
this.applyWindowStyle(x, y, w, h)
this.dispatchEvent(
new CustomEvent('windowForm:stateChange:maximize', {
detail: { state: this.windowFormState },
@@ -713,12 +837,16 @@ export class WindowFormElement extends LitElement {
startY,
startW,
startH,
b.left,
b.top,
b.x,
b.y,
b.width,
b.height,
300,
() => {
(x, y, w, h) => {
this.applyWindowStyle(x, y, w, h)
},
(x, y, w, h) => {
this.applyWindowStyle(x, y, w, h)
onComplete?.()
this.dispatchEvent(
new CustomEvent('windowForm:stateChange:restore', {
@@ -731,6 +859,24 @@ export class WindowFormElement extends LitElement {
)
}
/**
* 应用窗体样式
* @param x
* @param y
* @param w
* @param h
* @private
*/
private applyWindowStyle(x: number, y: number, w: number, h: number) {
this.width = w
this.height = h
this.x = x
this.y = y
this.style.width = `${w}px`
this.style.height = `${h}px`
this.style.transform = `translate(${x}px, ${y}px)`
}
private windowFormClose() {
this.dispatchEvent(
new CustomEvent('windowForm:close', {
@@ -764,7 +910,8 @@ export class WindowFormElement extends LitElement {
targetW: number,
targetH: number,
duration: number,
onComplete?: () => void,
onUpdating?: (x: number, y: number, w: number, h: number) => void,
onComplete?: (x: number, y: number, w: number, h: number) => void,
) {
const startTime = performance.now()
const step = (now: number) => {
@@ -777,17 +924,14 @@ export class WindowFormElement extends LitElement {
const w = startW + (targetW - startW) * ease
const h = startH + (targetH - startH) * ease
this.style.width = `${w}px`
this.style.height = `${h}px`
this.applyPosition(x, y, false)
onUpdating?.(x, y, w, h)
if (progress < 1) {
requestAnimationFrame(step)
} else {
this.style.width = `${targetW}px`
this.style.height = `${targetH}px`
this.applyPosition(targetX, targetY, true)
onComplete?.()
onComplete?.(x, y, w, h)
this.dispatchEvent(
new CustomEvent('windowForm:stateChange', {
detail: { state: this.windowFormState },
@@ -800,10 +944,10 @@ export class WindowFormElement extends LitElement {
requestAnimationFrame(step)
}
private updateTargetBounds(left: number, top: number, width?: number, height?: number) {
private updateTargetBounds(x: number, y: number, width?: number, height?: number) {
this.targetBounds = {
left,
top,
x,
y,
width: width ?? this.offsetWidth,
height: height ?? this.offsetHeight,
}