customer-chat-sdk 1.1.10 → 1.1.12
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/core/IconManager.d.ts +21 -0
- package/dist/core/IconManager.d.ts.map +1 -1
- package/dist/customer-sdk.cjs.js +276 -17
- package/dist/customer-sdk.esm.js +276 -17
- package/dist/customer-sdk.min.js +2 -2
- package/package.json +1 -1
|
@@ -24,6 +24,18 @@ export declare class IconManager {
|
|
|
24
24
|
private lastTouchPosition;
|
|
25
25
|
private touchStartTime;
|
|
26
26
|
private clickThreshold;
|
|
27
|
+
private cachedContainer;
|
|
28
|
+
private cachedContainerRect;
|
|
29
|
+
private cachedIconSize;
|
|
30
|
+
private lastContainerUpdateTime;
|
|
31
|
+
private containerUpdateInterval;
|
|
32
|
+
private cachedIframes;
|
|
33
|
+
private lastIframeUpdateTime;
|
|
34
|
+
private iframeUpdateInterval;
|
|
35
|
+
private rafId;
|
|
36
|
+
private pendingPosition;
|
|
37
|
+
private activeDetectionRafId;
|
|
38
|
+
private lastMousePosition;
|
|
27
39
|
private onDragHandler;
|
|
28
40
|
private stopDragHandler;
|
|
29
41
|
private startDragHandler;
|
|
@@ -88,6 +100,15 @@ export declare class IconManager {
|
|
|
88
100
|
* 停止拖动
|
|
89
101
|
*/
|
|
90
102
|
private stopDrag;
|
|
103
|
+
/**
|
|
104
|
+
* 启动主动检测机制:使用 requestAnimationFrame 定期检查鼠标位置
|
|
105
|
+
* 即使没有 mousemove 事件,也能检测到鼠标是否进入 iframe
|
|
106
|
+
*/
|
|
107
|
+
private startActiveDetection;
|
|
108
|
+
/**
|
|
109
|
+
* 停止主动检测机制
|
|
110
|
+
*/
|
|
111
|
+
private stopActiveDetection;
|
|
91
112
|
/**
|
|
92
113
|
* 清理拖动事件
|
|
93
114
|
*/
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"IconManager.d.ts","sourceRoot":"","sources":["../../src/core/IconManager.ts"],"names":[],"mappings":"AAGA,UAAU,mBAAmB;IAC3B,SAAS,CAAC,EAAE,OAAO,CAAA;IACnB,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,KAAK,CAAC,EAAE,OAAO,CAAA;CAChB;AAED,UAAU,YAAY;IACpB,CAAC,CAAC,EAAE,MAAM,GAAG,MAAM,CAAA;IACnB,CAAC,CAAC,EAAE,MAAM,GAAG,MAAM,CAAA;CACpB;AAED,qBAAa,WAAW;IACtB,OAAO,CAAC,WAAW,CAA2B;IAC9C,OAAO,CAAC,YAAY,CAA2B;IAC/C,OAAO,CAAC,eAAe,CAA4B;IACnD,OAAO,CAAC,oBAAoB,CAAqC;IACjE,OAAO,CAAC,YAAY,CAA4B;IAChD,OAAO,CAAC,KAAK,CAAiB;IAC9B,OAAO,CAAC,cAAc,CAAgB;IACtC,OAAO,CAAC,MAAM,CAAoC;IAGlD,OAAO,CAAC,UAAU,CAAQ;IAC1B,OAAO,CAAC,WAAW,CAAQ;IAC3B,OAAO,CAAC,QAAQ,CAAQ;IACxB,OAAO,CAAC,UAAU,CAAiB;IACnC,OAAO,CAAC,iBAAiB,CAAiB;IAC1C,OAAO,CAAC,cAAc,CAAI;IAC1B,OAAO,CAAC,cAAc,CAAK;IAG3B,OAAO,CAAC,aAAa,CAAsD;IAC3E,OAAO,CAAC,eAAe,CAAsD;IAC7E,OAAO,CAAC,gBAAgB,CAAsD;IAC9E,OAAO,CAAC,iBAAiB,CAAyC;IAClE,OAAO,CAAC,mBAAmB,CAA2C;IACtE,OAAO,CAAC,WAAW,CAA4B;IAC/C,OAAO,CAAC,aAAa,CAAsB;gBAE/B,QAAQ,CAAC,EAAE,YAAY,EAAE,KAAK,GAAE,OAAe,EAAE,MAAM,CAAC,EAAE,WAAW,GAAG,MAAM;IAQ1F;;OAEG;IACG,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IAwH3B;;OAEG;IACH,sBAAsB,IAAI,IAAI;IAI9B;;OAEG;IACH,IAAI,IAAI,IAAI;IAoCZ;;OAEG;IACH,WAAW,CAAC,QAAQ,EAAE,GAAG,GAAG,IAAI;IAIhC;;OAEG;IACH,eAAe,CAAC,QAAQ,EAAE,YAAY,GAAG,IAAI;IAqB7C;;OAEG;IACH,QAAQ,CAAC,KAAK,EAAE,GAAG,GAAG,IAAI;IAI1B;;OAEG;IACH,OAAO,CAAC,QAAQ,EAAE,MAAM,IAAI,GAAG,IAAI;IAInC;;OAEG;IACH,cAAc,CAAC,QAAQ,EAAE,CAAC,IAAI,EAAE,GAAG,KAAK,IAAI,GAAG,IAAI;IAInD;;OAEG;IACH,gBAAgB,CAAC,OAAO,EAAE,mBAAmB,GAAG,IAAI;IA8BpD;;OAEG;IACH,iBAAiB,IAAI,IAAI;IAUzB;;OAEG;IACH,OAAO,CAAC,eAAe;IAavB;;OAEG;IACH,OAAO,CAAC,SAAS;
|
|
1
|
+
{"version":3,"file":"IconManager.d.ts","sourceRoot":"","sources":["../../src/core/IconManager.ts"],"names":[],"mappings":"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"}
|
package/dist/customer-sdk.cjs.js
CHANGED
|
@@ -22,6 +22,22 @@ class IconManager {
|
|
|
22
22
|
this.lastTouchPosition = { x: 0, y: 0 }; // 最后触摸位置
|
|
23
23
|
this.touchStartTime = 0; // 触摸开始时间
|
|
24
24
|
this.clickThreshold = 15; // 点击阈值(像素)
|
|
25
|
+
// 性能优化:缓存容器信息,避免频繁查询 DOM
|
|
26
|
+
this.cachedContainer = null;
|
|
27
|
+
this.cachedContainerRect = null;
|
|
28
|
+
this.cachedIconSize = { width: 0, height: 0 };
|
|
29
|
+
this.lastContainerUpdateTime = 0;
|
|
30
|
+
this.containerUpdateInterval = 100; // 每 100ms 更新一次容器信息(避免频繁重排)
|
|
31
|
+
// 性能优化:缓存所有 iframe 的位置信息(用于检测鼠标是否在 iframe 上)
|
|
32
|
+
this.cachedIframes = [];
|
|
33
|
+
this.lastIframeUpdateTime = 0;
|
|
34
|
+
this.iframeUpdateInterval = 16; // 每 16ms 更新一次 iframe 位置信息(约一帧,更频繁,因为 iframe 可能移动)
|
|
35
|
+
// 性能优化:使用 requestAnimationFrame 节流位置更新
|
|
36
|
+
this.rafId = null;
|
|
37
|
+
this.pendingPosition = { x: 0, y: 0, needsUpdate: false };
|
|
38
|
+
// 主动检测机制:使用 requestAnimationFrame 定期检查鼠标位置(即使没有 mousemove 事件)
|
|
39
|
+
this.activeDetectionRafId = null;
|
|
40
|
+
this.lastMousePosition = { x: 0, y: 0, timestamp: 0 }; // 最后记录的鼠标位置和时间戳
|
|
25
41
|
// 事件处理器引用(用于清理)
|
|
26
42
|
this.onDragHandler = null;
|
|
27
43
|
this.stopDragHandler = null;
|
|
@@ -321,6 +337,26 @@ class IconManager {
|
|
|
321
337
|
this.dragOffset.x = clientX - iconRect.left;
|
|
322
338
|
this.dragOffset.y = clientY - iconRect.top;
|
|
323
339
|
// 注意:不在这里转换位置,只在真正开始拖动时才转换(在 onDrag 中)
|
|
340
|
+
// 性能优化:在拖动开始时预加载所有 iframe 位置信息
|
|
341
|
+
// 这样可以避免在拖动过程中频繁查询 DOM
|
|
342
|
+
const allIframes = document.querySelectorAll('iframe');
|
|
343
|
+
this.cachedIframes = Array.from(allIframes).map(iframe => ({
|
|
344
|
+
element: iframe,
|
|
345
|
+
rect: iframe.getBoundingClientRect()
|
|
346
|
+
}));
|
|
347
|
+
this.lastIframeUpdateTime = Date.now();
|
|
348
|
+
if (this.debug) {
|
|
349
|
+
console.log(`[IconManager] Drag start - Found ${this.cachedIframes.length} iframe(s)`, {
|
|
350
|
+
iframes: this.cachedIframes.map(({ rect }) => ({
|
|
351
|
+
left: rect.left,
|
|
352
|
+
top: rect.top,
|
|
353
|
+
right: rect.right,
|
|
354
|
+
bottom: rect.bottom,
|
|
355
|
+
width: rect.width,
|
|
356
|
+
height: rect.height
|
|
357
|
+
}))
|
|
358
|
+
});
|
|
359
|
+
}
|
|
324
360
|
// 添加 document 事件监听器
|
|
325
361
|
if (this.onDragHandler) {
|
|
326
362
|
document.addEventListener('mousemove', this.onDragHandler);
|
|
@@ -363,7 +399,7 @@ class IconManager {
|
|
|
363
399
|
};
|
|
364
400
|
window.addEventListener('blur', this.blurHandler);
|
|
365
401
|
// 3. 添加超时机制(如果一段时间没有收到 mousemove 事件,自动停止拖动)
|
|
366
|
-
//
|
|
402
|
+
// 优化:缩短超时时间到 50ms,更快检测到事件丢失(特别是嵌套 iframe 场景)
|
|
367
403
|
this.dragTimeoutId = window.setTimeout(() => {
|
|
368
404
|
if (this.isDragging) {
|
|
369
405
|
if (this.debug) {
|
|
@@ -371,9 +407,21 @@ class IconManager {
|
|
|
371
407
|
}
|
|
372
408
|
this.stopDrag();
|
|
373
409
|
}
|
|
374
|
-
},
|
|
410
|
+
}, 50); // 缩短到 50ms,更快检测到事件丢失(特别是嵌套 iframe 场景)
|
|
411
|
+
// 4. 启动主动检测机制:使用 requestAnimationFrame 定期检查鼠标位置
|
|
412
|
+
// 即使没有 mousemove 事件,也能检测到鼠标是否进入 iframe
|
|
413
|
+
this.startActiveDetection();
|
|
375
414
|
if (this.debug) {
|
|
376
|
-
console.log('Drag start'
|
|
415
|
+
console.log('[IconManager] Drag start', {
|
|
416
|
+
startPosition: { x: clientX, y: clientY },
|
|
417
|
+
iconRect: {
|
|
418
|
+
left: iconRect.left,
|
|
419
|
+
top: iconRect.top,
|
|
420
|
+
width: iconRect.width,
|
|
421
|
+
height: iconRect.height
|
|
422
|
+
},
|
|
423
|
+
dragOffset: this.dragOffset
|
|
424
|
+
});
|
|
377
425
|
}
|
|
378
426
|
}
|
|
379
427
|
catch (error) {
|
|
@@ -389,20 +437,92 @@ class IconManager {
|
|
|
389
437
|
if (!this.iconElement)
|
|
390
438
|
return;
|
|
391
439
|
e.preventDefault();
|
|
440
|
+
const clientX = 'touches' in e ? e.touches[0].clientX : e.clientX;
|
|
441
|
+
const clientY = 'touches' in e ? e.touches[0].clientY : e.clientY;
|
|
442
|
+
// 检测鼠标是否在任何 iframe 上(通过坐标判断)
|
|
443
|
+
// 如果检测到鼠标在 iframe 区域,立即停止拖动
|
|
444
|
+
// 重要:需要检测所有 iframe(包括嵌套的),因为任何 iframe 都会导致事件丢失
|
|
445
|
+
if (this.isDragging) {
|
|
446
|
+
const now = Date.now();
|
|
447
|
+
// 性能优化:缓存 iframe 位置信息,避免频繁查询 DOM
|
|
448
|
+
if (this.cachedIframes.length === 0 ||
|
|
449
|
+
now - this.lastIframeUpdateTime > this.iframeUpdateInterval) {
|
|
450
|
+
// 更新 iframe 缓存
|
|
451
|
+
const allIframes = document.querySelectorAll('iframe');
|
|
452
|
+
const previousCount = this.cachedIframes.length;
|
|
453
|
+
this.cachedIframes = Array.from(allIframes).map(iframe => ({
|
|
454
|
+
element: iframe,
|
|
455
|
+
rect: iframe.getBoundingClientRect()
|
|
456
|
+
}));
|
|
457
|
+
this.lastIframeUpdateTime = now;
|
|
458
|
+
if (this.debug && this.cachedIframes.length !== previousCount) {
|
|
459
|
+
console.log(`[IconManager] Iframe cache updated - Found ${this.cachedIframes.length} iframe(s)`, {
|
|
460
|
+
iframes: this.cachedIframes.map(({ rect }) => ({
|
|
461
|
+
left: rect.left,
|
|
462
|
+
top: rect.top,
|
|
463
|
+
right: rect.right,
|
|
464
|
+
bottom: rect.bottom
|
|
465
|
+
}))
|
|
466
|
+
});
|
|
467
|
+
}
|
|
468
|
+
}
|
|
469
|
+
// 检查鼠标是否在任何 iframe 上
|
|
470
|
+
for (const { rect, element } of this.cachedIframes) {
|
|
471
|
+
if (clientX >= rect.left &&
|
|
472
|
+
clientX <= rect.right &&
|
|
473
|
+
clientY >= rect.top &&
|
|
474
|
+
clientY <= rect.bottom) {
|
|
475
|
+
// 鼠标在 iframe 上,立即停止拖动
|
|
476
|
+
if (this.debug) {
|
|
477
|
+
console.log('[IconManager] Mouse over iframe detected, stopping drag immediately', {
|
|
478
|
+
mousePosition: { x: clientX, y: clientY },
|
|
479
|
+
iframeRect: {
|
|
480
|
+
left: rect.left,
|
|
481
|
+
top: rect.top,
|
|
482
|
+
right: rect.right,
|
|
483
|
+
bottom: rect.bottom
|
|
484
|
+
},
|
|
485
|
+
iframeSrc: element.src || 'no src'
|
|
486
|
+
});
|
|
487
|
+
}
|
|
488
|
+
this.stopDrag();
|
|
489
|
+
return;
|
|
490
|
+
}
|
|
491
|
+
}
|
|
492
|
+
}
|
|
493
|
+
// 更新最后记录的鼠标位置和时间戳(用于主动检测)
|
|
494
|
+
const now = Date.now();
|
|
495
|
+
const timeSinceLastUpdate = now - this.lastMousePosition.timestamp;
|
|
496
|
+
this.lastMousePosition = {
|
|
497
|
+
x: clientX,
|
|
498
|
+
y: clientY,
|
|
499
|
+
timestamp: now
|
|
500
|
+
};
|
|
501
|
+
// 如果距离上次更新超过 50ms,记录警告(可能事件丢失)
|
|
502
|
+
if (this.debug && timeSinceLastUpdate > 50 && this.lastMousePosition.timestamp > 0) {
|
|
503
|
+
console.warn(`[IconManager] Long gap between mousemove events: ${timeSinceLastUpdate}ms`, {
|
|
504
|
+
lastPosition: { x: this.lastMousePosition.x, y: this.lastMousePosition.y },
|
|
505
|
+
currentPosition: { x: clientX, y: clientY }
|
|
506
|
+
});
|
|
507
|
+
}
|
|
392
508
|
// 重置超时定时器(每次移动都重置,确保只有真正停止移动时才触发超时)
|
|
509
|
+
// 优化:缩短超时时间到 50ms,更快检测到事件丢失(特别是嵌套 iframe 场景)
|
|
393
510
|
if (this.dragTimeoutId !== null) {
|
|
394
511
|
window.clearTimeout(this.dragTimeoutId);
|
|
395
512
|
this.dragTimeoutId = window.setTimeout(() => {
|
|
396
513
|
if (this.isDragging) {
|
|
397
514
|
if (this.debug) {
|
|
398
|
-
|
|
515
|
+
const timeSinceLastMove = Date.now() - this.lastMousePosition.timestamp;
|
|
516
|
+
console.warn('[IconManager] Drag timeout triggered, stopping drag (likely mouse moved over iframe)', {
|
|
517
|
+
timeSinceLastMove,
|
|
518
|
+
lastMousePosition: this.lastMousePosition,
|
|
519
|
+
iframeCount: this.cachedIframes.length
|
|
520
|
+
});
|
|
399
521
|
}
|
|
400
522
|
this.stopDrag();
|
|
401
523
|
}
|
|
402
|
-
},
|
|
524
|
+
}, 50); // 缩短到 50ms,更快检测到事件丢失(特别是嵌套 iframe 场景)
|
|
403
525
|
}
|
|
404
|
-
const clientX = 'touches' in e ? e.touches[0].clientX : e.clientX;
|
|
405
|
-
const clientY = 'touches' in e ? e.touches[0].clientY : e.clientY;
|
|
406
526
|
// 检查是否有足够的移动距离
|
|
407
527
|
const deltaX = Math.abs(clientX - this.lastTouchPosition.x);
|
|
408
528
|
const deltaY = Math.abs(clientY - this.lastTouchPosition.y);
|
|
@@ -446,17 +566,31 @@ class IconManager {
|
|
|
446
566
|
return;
|
|
447
567
|
}
|
|
448
568
|
try {
|
|
569
|
+
// 性能优化:缓存容器信息,避免频繁查询 DOM
|
|
570
|
+
const now = Date.now();
|
|
449
571
|
const container = this.getTargetElement();
|
|
450
572
|
if (!container) {
|
|
451
573
|
return;
|
|
452
574
|
}
|
|
453
|
-
|
|
575
|
+
// 只在必要时更新容器信息(避免频繁重排)
|
|
576
|
+
if (!this.cachedContainer ||
|
|
577
|
+
this.cachedContainer !== container ||
|
|
578
|
+
now - this.lastContainerUpdateTime > this.containerUpdateInterval) {
|
|
579
|
+
this.cachedContainer = container;
|
|
580
|
+
this.cachedContainerRect = container.getBoundingClientRect();
|
|
581
|
+
this.cachedIconSize = {
|
|
582
|
+
width: this.iconElement.offsetWidth,
|
|
583
|
+
height: this.iconElement.offsetHeight
|
|
584
|
+
};
|
|
585
|
+
this.lastContainerUpdateTime = now;
|
|
586
|
+
}
|
|
587
|
+
const containerRect = this.cachedContainerRect;
|
|
588
|
+
const iconWidth = this.cachedIconSize.width;
|
|
589
|
+
const iconHeight = this.cachedIconSize.height;
|
|
454
590
|
// 计算新位置
|
|
455
591
|
let newX = clientX - this.dragOffset.x - containerRect.left;
|
|
456
592
|
let newY = clientY - this.dragOffset.y - containerRect.top;
|
|
457
593
|
// 限制在容器内
|
|
458
|
-
const iconWidth = this.iconElement.offsetWidth;
|
|
459
|
-
const iconHeight = this.iconElement.offsetHeight;
|
|
460
594
|
if (container === document.body) {
|
|
461
595
|
// 限制在视口内
|
|
462
596
|
newX = Math.max(0, Math.min(newX, window.innerWidth - iconWidth));
|
|
@@ -469,11 +603,22 @@ class IconManager {
|
|
|
469
603
|
newX = Math.max(0, Math.min(newX, containerWidth - iconWidth));
|
|
470
604
|
newY = Math.max(0, Math.min(newY, containerHeight - iconHeight));
|
|
471
605
|
}
|
|
472
|
-
//
|
|
473
|
-
this.
|
|
474
|
-
this.
|
|
475
|
-
this.
|
|
476
|
-
this.
|
|
606
|
+
// 性能优化:使用 requestAnimationFrame 节流位置更新
|
|
607
|
+
this.pendingPosition.x = newX;
|
|
608
|
+
this.pendingPosition.y = newY;
|
|
609
|
+
this.pendingPosition.needsUpdate = true;
|
|
610
|
+
if (this.rafId === null) {
|
|
611
|
+
this.rafId = requestAnimationFrame(() => {
|
|
612
|
+
this.rafId = null;
|
|
613
|
+
if (this.pendingPosition.needsUpdate && this.iconElement && this.isDragging) {
|
|
614
|
+
this.iconElement.style.left = `${this.pendingPosition.x}px`;
|
|
615
|
+
this.iconElement.style.top = `${this.pendingPosition.y}px`;
|
|
616
|
+
this.iconElement.style.right = 'auto';
|
|
617
|
+
this.iconElement.style.bottom = 'auto';
|
|
618
|
+
this.pendingPosition.needsUpdate = false;
|
|
619
|
+
}
|
|
620
|
+
});
|
|
621
|
+
}
|
|
477
622
|
// 更新最后位置
|
|
478
623
|
this.lastTouchPosition.x = clientX;
|
|
479
624
|
this.lastTouchPosition.y = clientY;
|
|
@@ -488,6 +633,14 @@ class IconManager {
|
|
|
488
633
|
* 停止拖动
|
|
489
634
|
*/
|
|
490
635
|
stopDrag(_e) {
|
|
636
|
+
const stopReason = this.isDragging ? 'drag_stopped' : 'not_dragging';
|
|
637
|
+
const finalPosition = this.iconElement ? {
|
|
638
|
+
left: this.iconElement.style.left,
|
|
639
|
+
top: this.iconElement.style.top,
|
|
640
|
+
computed: window.getComputedStyle(this.iconElement).left !== 'auto'
|
|
641
|
+
? { left: window.getComputedStyle(this.iconElement).left, top: window.getComputedStyle(this.iconElement).top }
|
|
642
|
+
: null
|
|
643
|
+
} : null;
|
|
491
644
|
this.cleanupDragEvents();
|
|
492
645
|
if (!this.iconElement)
|
|
493
646
|
return;
|
|
@@ -498,11 +651,14 @@ class IconManager {
|
|
|
498
651
|
const touchDuration = Date.now() - this.touchStartTime;
|
|
499
652
|
const isValidClick = !this.hasMoved && touchDuration < 1000 && !this.dragStarted;
|
|
500
653
|
if (this.debug) {
|
|
501
|
-
console.log('Drag end', {
|
|
654
|
+
console.log('[IconManager] Drag end', {
|
|
655
|
+
stopReason,
|
|
502
656
|
hasMoved: this.hasMoved,
|
|
503
657
|
dragStarted: this.dragStarted,
|
|
504
658
|
touchDuration,
|
|
505
|
-
isValidClick
|
|
659
|
+
isValidClick,
|
|
660
|
+
finalPosition,
|
|
661
|
+
lastMousePosition: this.lastMousePosition
|
|
506
662
|
});
|
|
507
663
|
}
|
|
508
664
|
// 先保存点击状态,然后重置拖动状态
|
|
@@ -542,6 +698,97 @@ class IconManager {
|
|
|
542
698
|
}, 100); // 增加延迟到 100ms,确保拖动状态完全重置
|
|
543
699
|
}
|
|
544
700
|
}
|
|
701
|
+
/**
|
|
702
|
+
* 启动主动检测机制:使用 requestAnimationFrame 定期检查鼠标位置
|
|
703
|
+
* 即使没有 mousemove 事件,也能检测到鼠标是否进入 iframe
|
|
704
|
+
*/
|
|
705
|
+
startActiveDetection() {
|
|
706
|
+
if (this.activeDetectionRafId !== null) {
|
|
707
|
+
if (this.debug) {
|
|
708
|
+
console.log('[IconManager] Active detection already running');
|
|
709
|
+
}
|
|
710
|
+
return; // 已经在运行
|
|
711
|
+
}
|
|
712
|
+
if (this.debug) {
|
|
713
|
+
console.log('[IconManager] Starting active detection mechanism');
|
|
714
|
+
}
|
|
715
|
+
const checkMousePosition = () => {
|
|
716
|
+
if (!this.isDragging) {
|
|
717
|
+
if (this.debug) {
|
|
718
|
+
console.log('[IconManager] Active detection stopped (drag ended)');
|
|
719
|
+
}
|
|
720
|
+
this.activeDetectionRafId = null;
|
|
721
|
+
return;
|
|
722
|
+
}
|
|
723
|
+
// 检查是否长时间没有收到 mousemove 事件(超过 50ms)
|
|
724
|
+
const now = Date.now();
|
|
725
|
+
const timeSinceLastMove = now - this.lastMousePosition.timestamp;
|
|
726
|
+
if (timeSinceLastMove > 50) {
|
|
727
|
+
// 长时间没有收到事件,可能鼠标已经进入 iframe
|
|
728
|
+
// 使用最后记录的鼠标位置进行检测
|
|
729
|
+
const clientX = this.lastMousePosition.x;
|
|
730
|
+
const clientY = this.lastMousePosition.y;
|
|
731
|
+
if (this.debug) {
|
|
732
|
+
console.log(`[IconManager] Active detection: No mousemove event for ${timeSinceLastMove}ms`, {
|
|
733
|
+
lastMousePosition: { x: clientX, y: clientY },
|
|
734
|
+
timestamp: this.lastMousePosition.timestamp
|
|
735
|
+
});
|
|
736
|
+
}
|
|
737
|
+
// 更新 iframe 缓存(更频繁,每帧更新)
|
|
738
|
+
if (this.cachedIframes.length === 0 ||
|
|
739
|
+
now - this.lastIframeUpdateTime > this.iframeUpdateInterval) {
|
|
740
|
+
const allIframes = document.querySelectorAll('iframe');
|
|
741
|
+
this.cachedIframes = Array.from(allIframes).map(iframe => ({
|
|
742
|
+
element: iframe,
|
|
743
|
+
rect: iframe.getBoundingClientRect()
|
|
744
|
+
}));
|
|
745
|
+
this.lastIframeUpdateTime = now;
|
|
746
|
+
if (this.debug) {
|
|
747
|
+
console.log(`[IconManager] Active detection: Updated iframe cache (${this.cachedIframes.length} iframes)`);
|
|
748
|
+
}
|
|
749
|
+
}
|
|
750
|
+
// 检查最后记录的鼠标位置是否在任何 iframe 上
|
|
751
|
+
for (const { rect, element } of this.cachedIframes) {
|
|
752
|
+
if (clientX >= rect.left &&
|
|
753
|
+
clientX <= rect.right &&
|
|
754
|
+
clientY >= rect.top &&
|
|
755
|
+
clientY <= rect.bottom) {
|
|
756
|
+
// 鼠标在 iframe 上,立即停止拖动
|
|
757
|
+
if (this.debug) {
|
|
758
|
+
console.log('[IconManager] Active detection: Mouse over iframe detected, stopping drag immediately', {
|
|
759
|
+
mousePosition: { x: clientX, y: clientY },
|
|
760
|
+
iframeRect: {
|
|
761
|
+
left: rect.left,
|
|
762
|
+
top: rect.top,
|
|
763
|
+
right: rect.right,
|
|
764
|
+
bottom: rect.bottom
|
|
765
|
+
},
|
|
766
|
+
iframeSrc: element.src || 'no src',
|
|
767
|
+
timeSinceLastMove
|
|
768
|
+
});
|
|
769
|
+
}
|
|
770
|
+
this.stopDrag();
|
|
771
|
+
return;
|
|
772
|
+
}
|
|
773
|
+
}
|
|
774
|
+
}
|
|
775
|
+
// 继续检测
|
|
776
|
+
this.activeDetectionRafId = requestAnimationFrame(checkMousePosition);
|
|
777
|
+
};
|
|
778
|
+
this.activeDetectionRafId = requestAnimationFrame(checkMousePosition);
|
|
779
|
+
}
|
|
780
|
+
/**
|
|
781
|
+
* 停止主动检测机制
|
|
782
|
+
*/
|
|
783
|
+
stopActiveDetection() {
|
|
784
|
+
if (this.activeDetectionRafId !== null) {
|
|
785
|
+
if (this.debug) {
|
|
786
|
+
console.log('[IconManager] Stopping active detection mechanism');
|
|
787
|
+
}
|
|
788
|
+
cancelAnimationFrame(this.activeDetectionRafId);
|
|
789
|
+
this.activeDetectionRafId = null;
|
|
790
|
+
}
|
|
791
|
+
}
|
|
545
792
|
/**
|
|
546
793
|
* 清理拖动事件
|
|
547
794
|
*/
|
|
@@ -573,6 +820,18 @@ class IconManager {
|
|
|
573
820
|
window.clearTimeout(this.dragTimeoutId);
|
|
574
821
|
this.dragTimeoutId = null;
|
|
575
822
|
}
|
|
823
|
+
// 清理 requestAnimationFrame
|
|
824
|
+
if (this.rafId !== null) {
|
|
825
|
+
cancelAnimationFrame(this.rafId);
|
|
826
|
+
this.rafId = null;
|
|
827
|
+
}
|
|
828
|
+
// 清理主动检测机制
|
|
829
|
+
this.stopActiveDetection();
|
|
830
|
+
// 清理缓存
|
|
831
|
+
this.cachedContainer = null;
|
|
832
|
+
this.cachedContainerRect = null;
|
|
833
|
+
this.cachedIframes = []; // 清理 iframe 缓存
|
|
834
|
+
this.pendingPosition.needsUpdate = false;
|
|
576
835
|
}
|
|
577
836
|
/**
|
|
578
837
|
* 处理点击事件
|