customer-chat-sdk 1.1.10 → 1.1.11

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,16 @@ 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;
27
37
  private onDragHandler;
28
38
  private stopDragHandler;
29
39
  private startDragHandler;
@@ -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,19 @@ 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 = 50; // 每 50ms 更新一次 iframe 位置信息(更频繁,因为 iframe 可能移动)
35
+ // 性能优化:使用 requestAnimationFrame 节流位置更新
36
+ this.rafId = null;
37
+ this.pendingPosition = { x: 0, y: 0, needsUpdate: false };
25
38
  // 事件处理器引用(用于清理)
26
39
  this.onDragHandler = null;
27
40
  this.stopDragHandler = null;
@@ -321,6 +334,14 @@ class IconManager {
321
334
  this.dragOffset.x = clientX - iconRect.left;
322
335
  this.dragOffset.y = clientY - iconRect.top;
323
336
  // 注意:不在这里转换位置,只在真正开始拖动时才转换(在 onDrag 中)
337
+ // 性能优化:在拖动开始时预加载所有 iframe 位置信息
338
+ // 这样可以避免在拖动过程中频繁查询 DOM
339
+ const allIframes = document.querySelectorAll('iframe');
340
+ this.cachedIframes = Array.from(allIframes).map(iframe => ({
341
+ element: iframe,
342
+ rect: iframe.getBoundingClientRect()
343
+ }));
344
+ this.lastIframeUpdateTime = Date.now();
324
345
  // 添加 document 事件监听器
325
346
  if (this.onDragHandler) {
326
347
  document.addEventListener('mousemove', this.onDragHandler);
@@ -363,7 +384,7 @@ class IconManager {
363
384
  };
364
385
  window.addEventListener('blur', this.blurHandler);
365
386
  // 3. 添加超时机制(如果一段时间没有收到 mousemove 事件,自动停止拖动)
366
- // 这可以处理鼠标移动到 iframe 上的情况
387
+ // 优化:缩短超时时间到 100ms,更快检测到事件丢失(特别是嵌套 iframe 场景)
367
388
  this.dragTimeoutId = window.setTimeout(() => {
368
389
  if (this.isDragging) {
369
390
  if (this.debug) {
@@ -371,7 +392,7 @@ class IconManager {
371
392
  }
372
393
  this.stopDrag();
373
394
  }
374
- }, 500); // 500ms 没有移动事件,自动停止拖动(处理鼠标移动到 iframe 上的情况)
395
+ }, 100); // 缩短到 100ms,更快检测到事件丢失(特别是嵌套 iframe 场景)
375
396
  if (this.debug) {
376
397
  console.log('Drag start');
377
398
  }
@@ -389,7 +410,41 @@ class IconManager {
389
410
  if (!this.iconElement)
390
411
  return;
391
412
  e.preventDefault();
413
+ const clientX = 'touches' in e ? e.touches[0].clientX : e.clientX;
414
+ const clientY = 'touches' in e ? e.touches[0].clientY : e.clientY;
415
+ // 检测鼠标是否在任何 iframe 上(通过坐标判断)
416
+ // 如果检测到鼠标在 iframe 区域,立即停止拖动
417
+ // 重要:需要检测所有 iframe(包括嵌套的),因为任何 iframe 都会导致事件丢失
418
+ if (this.isDragging) {
419
+ const now = Date.now();
420
+ // 性能优化:缓存 iframe 位置信息,避免频繁查询 DOM
421
+ if (this.cachedIframes.length === 0 ||
422
+ now - this.lastIframeUpdateTime > this.iframeUpdateInterval) {
423
+ // 更新 iframe 缓存
424
+ const allIframes = document.querySelectorAll('iframe');
425
+ this.cachedIframes = Array.from(allIframes).map(iframe => ({
426
+ element: iframe,
427
+ rect: iframe.getBoundingClientRect()
428
+ }));
429
+ this.lastIframeUpdateTime = now;
430
+ }
431
+ // 检查鼠标是否在任何 iframe 上
432
+ for (const { rect } of this.cachedIframes) {
433
+ if (clientX >= rect.left &&
434
+ clientX <= rect.right &&
435
+ clientY >= rect.top &&
436
+ clientY <= rect.bottom) {
437
+ // 鼠标在 iframe 上,立即停止拖动
438
+ if (this.debug) {
439
+ console.log('Mouse over iframe, stopping drag immediately');
440
+ }
441
+ this.stopDrag();
442
+ return;
443
+ }
444
+ }
445
+ }
392
446
  // 重置超时定时器(每次移动都重置,确保只有真正停止移动时才触发超时)
447
+ // 优化:缩短超时时间到 100ms,更快检测到事件丢失(特别是嵌套 iframe 场景)
393
448
  if (this.dragTimeoutId !== null) {
394
449
  window.clearTimeout(this.dragTimeoutId);
395
450
  this.dragTimeoutId = window.setTimeout(() => {
@@ -399,10 +454,8 @@ class IconManager {
399
454
  }
400
455
  this.stopDrag();
401
456
  }
402
- }, 500); // 500ms 没有移动事件,自动停止拖动
457
+ }, 100); // 缩短到 100ms,更快检测到事件丢失(特别是嵌套 iframe 场景)
403
458
  }
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
459
  // 检查是否有足够的移动距离
407
460
  const deltaX = Math.abs(clientX - this.lastTouchPosition.x);
408
461
  const deltaY = Math.abs(clientY - this.lastTouchPosition.y);
@@ -446,17 +499,31 @@ class IconManager {
446
499
  return;
447
500
  }
448
501
  try {
502
+ // 性能优化:缓存容器信息,避免频繁查询 DOM
503
+ const now = Date.now();
449
504
  const container = this.getTargetElement();
450
505
  if (!container) {
451
506
  return;
452
507
  }
453
- const containerRect = container.getBoundingClientRect();
508
+ // 只在必要时更新容器信息(避免频繁重排)
509
+ if (!this.cachedContainer ||
510
+ this.cachedContainer !== container ||
511
+ now - this.lastContainerUpdateTime > this.containerUpdateInterval) {
512
+ this.cachedContainer = container;
513
+ this.cachedContainerRect = container.getBoundingClientRect();
514
+ this.cachedIconSize = {
515
+ width: this.iconElement.offsetWidth,
516
+ height: this.iconElement.offsetHeight
517
+ };
518
+ this.lastContainerUpdateTime = now;
519
+ }
520
+ const containerRect = this.cachedContainerRect;
521
+ const iconWidth = this.cachedIconSize.width;
522
+ const iconHeight = this.cachedIconSize.height;
454
523
  // 计算新位置
455
524
  let newX = clientX - this.dragOffset.x - containerRect.left;
456
525
  let newY = clientY - this.dragOffset.y - containerRect.top;
457
526
  // 限制在容器内
458
- const iconWidth = this.iconElement.offsetWidth;
459
- const iconHeight = this.iconElement.offsetHeight;
460
527
  if (container === document.body) {
461
528
  // 限制在视口内
462
529
  newX = Math.max(0, Math.min(newX, window.innerWidth - iconWidth));
@@ -469,11 +536,22 @@ class IconManager {
469
536
  newX = Math.max(0, Math.min(newX, containerWidth - iconWidth));
470
537
  newY = Math.max(0, Math.min(newY, containerHeight - iconHeight));
471
538
  }
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';
539
+ // 性能优化:使用 requestAnimationFrame 节流位置更新
540
+ this.pendingPosition.x = newX;
541
+ this.pendingPosition.y = newY;
542
+ this.pendingPosition.needsUpdate = true;
543
+ if (this.rafId === null) {
544
+ this.rafId = requestAnimationFrame(() => {
545
+ this.rafId = null;
546
+ if (this.pendingPosition.needsUpdate && this.iconElement && this.isDragging) {
547
+ this.iconElement.style.left = `${this.pendingPosition.x}px`;
548
+ this.iconElement.style.top = `${this.pendingPosition.y}px`;
549
+ this.iconElement.style.right = 'auto';
550
+ this.iconElement.style.bottom = 'auto';
551
+ this.pendingPosition.needsUpdate = false;
552
+ }
553
+ });
554
+ }
477
555
  // 更新最后位置
478
556
  this.lastTouchPosition.x = clientX;
479
557
  this.lastTouchPosition.y = clientY;
@@ -573,6 +651,16 @@ class IconManager {
573
651
  window.clearTimeout(this.dragTimeoutId);
574
652
  this.dragTimeoutId = null;
575
653
  }
654
+ // 清理 requestAnimationFrame
655
+ if (this.rafId !== null) {
656
+ cancelAnimationFrame(this.rafId);
657
+ this.rafId = null;
658
+ }
659
+ // 清理缓存
660
+ this.cachedContainer = null;
661
+ this.cachedContainerRect = null;
662
+ this.cachedIframes = []; // 清理 iframe 缓存
663
+ this.pendingPosition.needsUpdate = false;
576
664
  }
577
665
  /**
578
666
  * 处理点击事件
@@ -18,6 +18,19 @@ class IconManager {
18
18
  this.lastTouchPosition = { x: 0, y: 0 }; // 最后触摸位置
19
19
  this.touchStartTime = 0; // 触摸开始时间
20
20
  this.clickThreshold = 15; // 点击阈值(像素)
21
+ // 性能优化:缓存容器信息,避免频繁查询 DOM
22
+ this.cachedContainer = null;
23
+ this.cachedContainerRect = null;
24
+ this.cachedIconSize = { width: 0, height: 0 };
25
+ this.lastContainerUpdateTime = 0;
26
+ this.containerUpdateInterval = 100; // 每 100ms 更新一次容器信息(避免频繁重排)
27
+ // 性能优化:缓存所有 iframe 的位置信息(用于检测鼠标是否在 iframe 上)
28
+ this.cachedIframes = [];
29
+ this.lastIframeUpdateTime = 0;
30
+ this.iframeUpdateInterval = 50; // 每 50ms 更新一次 iframe 位置信息(更频繁,因为 iframe 可能移动)
31
+ // 性能优化:使用 requestAnimationFrame 节流位置更新
32
+ this.rafId = null;
33
+ this.pendingPosition = { x: 0, y: 0, needsUpdate: false };
21
34
  // 事件处理器引用(用于清理)
22
35
  this.onDragHandler = null;
23
36
  this.stopDragHandler = null;
@@ -317,6 +330,14 @@ class IconManager {
317
330
  this.dragOffset.x = clientX - iconRect.left;
318
331
  this.dragOffset.y = clientY - iconRect.top;
319
332
  // 注意:不在这里转换位置,只在真正开始拖动时才转换(在 onDrag 中)
333
+ // 性能优化:在拖动开始时预加载所有 iframe 位置信息
334
+ // 这样可以避免在拖动过程中频繁查询 DOM
335
+ const allIframes = document.querySelectorAll('iframe');
336
+ this.cachedIframes = Array.from(allIframes).map(iframe => ({
337
+ element: iframe,
338
+ rect: iframe.getBoundingClientRect()
339
+ }));
340
+ this.lastIframeUpdateTime = Date.now();
320
341
  // 添加 document 事件监听器
321
342
  if (this.onDragHandler) {
322
343
  document.addEventListener('mousemove', this.onDragHandler);
@@ -359,7 +380,7 @@ class IconManager {
359
380
  };
360
381
  window.addEventListener('blur', this.blurHandler);
361
382
  // 3. 添加超时机制(如果一段时间没有收到 mousemove 事件,自动停止拖动)
362
- // 这可以处理鼠标移动到 iframe 上的情况
383
+ // 优化:缩短超时时间到 100ms,更快检测到事件丢失(特别是嵌套 iframe 场景)
363
384
  this.dragTimeoutId = window.setTimeout(() => {
364
385
  if (this.isDragging) {
365
386
  if (this.debug) {
@@ -367,7 +388,7 @@ class IconManager {
367
388
  }
368
389
  this.stopDrag();
369
390
  }
370
- }, 500); // 500ms 没有移动事件,自动停止拖动(处理鼠标移动到 iframe 上的情况)
391
+ }, 100); // 缩短到 100ms,更快检测到事件丢失(特别是嵌套 iframe 场景)
371
392
  if (this.debug) {
372
393
  console.log('Drag start');
373
394
  }
@@ -385,7 +406,41 @@ class IconManager {
385
406
  if (!this.iconElement)
386
407
  return;
387
408
  e.preventDefault();
409
+ const clientX = 'touches' in e ? e.touches[0].clientX : e.clientX;
410
+ const clientY = 'touches' in e ? e.touches[0].clientY : e.clientY;
411
+ // 检测鼠标是否在任何 iframe 上(通过坐标判断)
412
+ // 如果检测到鼠标在 iframe 区域,立即停止拖动
413
+ // 重要:需要检测所有 iframe(包括嵌套的),因为任何 iframe 都会导致事件丢失
414
+ if (this.isDragging) {
415
+ const now = Date.now();
416
+ // 性能优化:缓存 iframe 位置信息,避免频繁查询 DOM
417
+ if (this.cachedIframes.length === 0 ||
418
+ now - this.lastIframeUpdateTime > this.iframeUpdateInterval) {
419
+ // 更新 iframe 缓存
420
+ const allIframes = document.querySelectorAll('iframe');
421
+ this.cachedIframes = Array.from(allIframes).map(iframe => ({
422
+ element: iframe,
423
+ rect: iframe.getBoundingClientRect()
424
+ }));
425
+ this.lastIframeUpdateTime = now;
426
+ }
427
+ // 检查鼠标是否在任何 iframe 上
428
+ for (const { rect } of this.cachedIframes) {
429
+ if (clientX >= rect.left &&
430
+ clientX <= rect.right &&
431
+ clientY >= rect.top &&
432
+ clientY <= rect.bottom) {
433
+ // 鼠标在 iframe 上,立即停止拖动
434
+ if (this.debug) {
435
+ console.log('Mouse over iframe, stopping drag immediately');
436
+ }
437
+ this.stopDrag();
438
+ return;
439
+ }
440
+ }
441
+ }
388
442
  // 重置超时定时器(每次移动都重置,确保只有真正停止移动时才触发超时)
443
+ // 优化:缩短超时时间到 100ms,更快检测到事件丢失(特别是嵌套 iframe 场景)
389
444
  if (this.dragTimeoutId !== null) {
390
445
  window.clearTimeout(this.dragTimeoutId);
391
446
  this.dragTimeoutId = window.setTimeout(() => {
@@ -395,10 +450,8 @@ class IconManager {
395
450
  }
396
451
  this.stopDrag();
397
452
  }
398
- }, 500); // 500ms 没有移动事件,自动停止拖动
453
+ }, 100); // 缩短到 100ms,更快检测到事件丢失(特别是嵌套 iframe 场景)
399
454
  }
400
- const clientX = 'touches' in e ? e.touches[0].clientX : e.clientX;
401
- const clientY = 'touches' in e ? e.touches[0].clientY : e.clientY;
402
455
  // 检查是否有足够的移动距离
403
456
  const deltaX = Math.abs(clientX - this.lastTouchPosition.x);
404
457
  const deltaY = Math.abs(clientY - this.lastTouchPosition.y);
@@ -442,17 +495,31 @@ class IconManager {
442
495
  return;
443
496
  }
444
497
  try {
498
+ // 性能优化:缓存容器信息,避免频繁查询 DOM
499
+ const now = Date.now();
445
500
  const container = this.getTargetElement();
446
501
  if (!container) {
447
502
  return;
448
503
  }
449
- const containerRect = container.getBoundingClientRect();
504
+ // 只在必要时更新容器信息(避免频繁重排)
505
+ if (!this.cachedContainer ||
506
+ this.cachedContainer !== container ||
507
+ now - this.lastContainerUpdateTime > this.containerUpdateInterval) {
508
+ this.cachedContainer = container;
509
+ this.cachedContainerRect = container.getBoundingClientRect();
510
+ this.cachedIconSize = {
511
+ width: this.iconElement.offsetWidth,
512
+ height: this.iconElement.offsetHeight
513
+ };
514
+ this.lastContainerUpdateTime = now;
515
+ }
516
+ const containerRect = this.cachedContainerRect;
517
+ const iconWidth = this.cachedIconSize.width;
518
+ const iconHeight = this.cachedIconSize.height;
450
519
  // 计算新位置
451
520
  let newX = clientX - this.dragOffset.x - containerRect.left;
452
521
  let newY = clientY - this.dragOffset.y - containerRect.top;
453
522
  // 限制在容器内
454
- const iconWidth = this.iconElement.offsetWidth;
455
- const iconHeight = this.iconElement.offsetHeight;
456
523
  if (container === document.body) {
457
524
  // 限制在视口内
458
525
  newX = Math.max(0, Math.min(newX, window.innerWidth - iconWidth));
@@ -465,11 +532,22 @@ class IconManager {
465
532
  newX = Math.max(0, Math.min(newX, containerWidth - iconWidth));
466
533
  newY = Math.max(0, Math.min(newY, containerHeight - iconHeight));
467
534
  }
468
- // 更新位置
469
- this.iconElement.style.left = `${newX}px`;
470
- this.iconElement.style.top = `${newY}px`;
471
- this.iconElement.style.right = 'auto';
472
- this.iconElement.style.bottom = 'auto';
535
+ // 性能优化:使用 requestAnimationFrame 节流位置更新
536
+ this.pendingPosition.x = newX;
537
+ this.pendingPosition.y = newY;
538
+ this.pendingPosition.needsUpdate = true;
539
+ if (this.rafId === null) {
540
+ this.rafId = requestAnimationFrame(() => {
541
+ this.rafId = null;
542
+ if (this.pendingPosition.needsUpdate && this.iconElement && this.isDragging) {
543
+ this.iconElement.style.left = `${this.pendingPosition.x}px`;
544
+ this.iconElement.style.top = `${this.pendingPosition.y}px`;
545
+ this.iconElement.style.right = 'auto';
546
+ this.iconElement.style.bottom = 'auto';
547
+ this.pendingPosition.needsUpdate = false;
548
+ }
549
+ });
550
+ }
473
551
  // 更新最后位置
474
552
  this.lastTouchPosition.x = clientX;
475
553
  this.lastTouchPosition.y = clientY;
@@ -569,6 +647,16 @@ class IconManager {
569
647
  window.clearTimeout(this.dragTimeoutId);
570
648
  this.dragTimeoutId = null;
571
649
  }
650
+ // 清理 requestAnimationFrame
651
+ if (this.rafId !== null) {
652
+ cancelAnimationFrame(this.rafId);
653
+ this.rafId = null;
654
+ }
655
+ // 清理缓存
656
+ this.cachedContainer = null;
657
+ this.cachedContainerRect = null;
658
+ this.cachedIframes = []; // 清理 iframe 缓存
659
+ this.pendingPosition.needsUpdate = false;
572
660
  }
573
661
  /**
574
662
  * 处理点击事件