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.
@@ -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;IAgHjB;;OAEG;IACH,OAAO,CAAC,MAAM;IAqHd;;OAEG;IACH,OAAO,CAAC,QAAQ;IAkEhB;;OAEG;IACH,OAAO,CAAC,iBAAiB;IAgCzB;;OAEG;IACH,OAAO,CAAC,WAAW;IAUnB;;OAEG;IACH,YAAY,IAAI,IAAI;IAmBpB;;OAEG;IACH,WAAW,IAAI,IAAI;IAmBnB;;OAEG;IACH,OAAO,CAAC,gBAAgB;IAqCxB;;OAEG;IACH,OAAO,CAAC,WAAW;CAmDpB"}
1
+ {"version":3,"file":"IconManager.d.ts","sourceRoot":"","sources":["../../src/core/IconManager.ts"],"names":[],"mappings":"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"}
@@ -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
- // 这可以处理鼠标移动到 iframe 上的情况
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
- }, 500); // 500ms 没有移动事件,自动停止拖动(处理鼠标移动到 iframe 上的情况)
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
- console.log('Drag timeout, stopping drag (likely mouse moved over iframe)');
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
- }, 500); // 500ms 没有移动事件,自动停止拖动
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
- const containerRect = container.getBoundingClientRect();
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.iconElement.style.left = `${newX}px`;
474
- this.iconElement.style.top = `${newY}px`;
475
- this.iconElement.style.right = 'auto';
476
- this.iconElement.style.bottom = 'auto';
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
  * 处理点击事件