1
0

useChatDrag.ts 2.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384
  1. import { nextTick, onBeforeUnmount, watch } from 'vue';
  2. import { applyWrapStyles, findModalWrap, queryModalWrap } from '../lib/modalWrap';
  3. /**
  4. * 弹框拖拽与居中:拖标题栏移动窗口,缩放环境下把鼠标视口坐标换算回布局坐标。
  5. *
  6. * 拖拽状态刻意用普通变量(非响应式),避免 mousemove 触发渲染;
  7. * modal-wrap 的定位样式统一走 lib/modalWrap,与面板侧的全屏逻辑共用同一套选择器。
  8. */
  9. export interface ChatDragDeps {
  10. getIsFullscreen: () => boolean;
  11. getVisible: () => boolean | undefined;
  12. }
  13. export const useChatDrag = (deps: ChatDragDeps) => {
  14. // 拖拽相关(使用普通变量避免 mousemove 触发 Vue 响应式导致卡顿)
  15. let isDragging = false;
  16. let dragStartX = 0;
  17. let dragStartY = 0;
  18. let modalStartLeft = 0;
  19. let modalStartTop = 0;
  20. // 容器 scale 缩放比(视觉尺寸/布局尺寸),用于把鼠标视口坐标换算回布局坐标
  21. let dragScaleX = 1;
  22. let dragScaleY = 1;
  23. let draggedEl: HTMLElement | null = null;
  24. const centerModal = () => {
  25. nextTick(() => {
  26. applyWrapStyles(deps.getIsFullscreen());
  27. draggedEl = null;
  28. });
  29. };
  30. const onDragStart = (e: MouseEvent) => {
  31. if (deps.getIsFullscreen()) return;
  32. draggedEl = findModalWrap(e.target as HTMLElement) || queryModalWrap();
  33. if (!draggedEl) return;
  34. // 记录容器缩放比,并把鼠标/弹框的视口坐标换算回布局坐标,避免不同分辨率下拖拽位移失真
  35. const rect = draggedEl.getBoundingClientRect();
  36. dragScaleX = draggedEl.offsetWidth ? rect.width / draggedEl.offsetWidth : 1;
  37. dragScaleY = draggedEl.offsetHeight ? rect.height / draggedEl.offsetHeight : 1;
  38. isDragging = true;
  39. dragStartX = e.clientX;
  40. dragStartY = e.clientY;
  41. modalStartLeft = rect.left / dragScaleX;
  42. modalStartTop = rect.top / dragScaleY;
  43. draggedEl.style.position = 'fixed';
  44. draggedEl.style.left = `${modalStartLeft}px`;
  45. draggedEl.style.top = `${modalStartTop}px`;
  46. draggedEl.style.margin = '0';
  47. draggedEl.style.transform = 'none';
  48. document.addEventListener('mousemove', onDragMove);
  49. document.addEventListener('mouseup', onDragEnd);
  50. };
  51. const onDragMove = (e: MouseEvent) => {
  52. if (!isDragging || !draggedEl) return;
  53. draggedEl.style.left = `${modalStartLeft + (e.clientX - dragStartX) / dragScaleX}px`;
  54. draggedEl.style.top = `${modalStartTop + (e.clientY - dragStartY) / dragScaleY}px`;
  55. };
  56. const onDragEnd = () => {
  57. isDragging = false;
  58. document.removeEventListener('mousemove', onDragMove);
  59. document.removeEventListener('mouseup', onDragEnd);
  60. };
  61. watch(deps.getVisible, (newVisible) => {
  62. if (newVisible) {
  63. centerModal();
  64. }
  65. });
  66. onBeforeUnmount(() => {
  67. document.removeEventListener('mousemove', onDragMove);
  68. document.removeEventListener('mouseup', onDragEnd);
  69. });
  70. return { onDragStart, centerModal };
  71. };