# 实现拖拽控制页面宽度

// 拖拽实现
const isDragging = ref(false)
const QA_WIDTH = 816 // 问答区域最小宽度
let startX = 0 // 按下时的起始坐标
let startWidth = 0
let maxWidth = 0
let minWidth = defaultHistoryRecordsWidth

const startDrag = (e: MouseEvent) => {
  e.preventDefault()
  e.stopPropagation()

  const container = document.querySelector('.contain') as HTMLElement
  const containerWidth = container ? container.clientWidth : window.innerWidth
  maxWidth = containerWidth - QA_WIDTH
  // 如果已经在拖拽中,忽略新的拖拽事件
  if (isDragging.value) return

  isDragging.value = true

  startX = e.clientX
  startWidth = historyRecordsWidth.value

  document.addEventListener('mousemove', onDrag)
  document.addEventListener('mouseup', stopDrag)
  document.addEventListener('mouseleave', stopDrag)
}

function onDrag(e: MouseEvent) {
  const delta = startX - e.clientX
  let newWidth = startWidth + delta
  if (newWidth < minWidth) newWidth = minWidth
  if (newWidth > maxWidth) newWidth = maxWidth
  historyRecordsWidth.value = newWidth
}

function stopDrag() {
  isDragging.value = false
  document.removeEventListener('mousemove', onDrag)
  document.removeEventListener('mouseup', stopDrag)
  document.removeEventListener('mouseleave', stopDrag)
  localStorage.setItem('historyRecordsWidth', historyRecordsWidth.value.toString())
}

const cleanupDrag = () => {
  startX = 0
  startWidth = 0
  isDragging.value = false
  document.removeEventListener('mousemove', onDrag)
  document.removeEventListener('mouseup', stopDrag)
  document.removeEventListener('mouseleave', stopDrag)
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52

# 需要注意的坑

浏览器事件模型的常见坑: 当鼠标拖拽经过 <iframe> 边界时,mousemove / mouseup 事件会“丢失”,因为鼠标进入了 另一个文档的上下文,原页面不再接收事件,所以拖拽逻辑就“卡住”了。

解决方案:

# 方案 1:在 iframe 内部也监听(同域才可行)

如果 iframe 和父页面是同域,可以:

const iframe = document.querySelector('iframe') as HTMLIFrameElement
iframe.onload = () => {
  const iframeDoc = iframe.contentDocument!
  iframeDoc.addEventListener('mousemove', onDrag)
  iframeDoc.addEventListener('mouseup', stopDrag)
}
1
2
3
4
5
6

这样鼠标进入 iframe 时,依然能触发内部的事件。

⚠️ 限制:必须同源(协议、域名、端口都相同)。

# 方案 2:拖拽时加一层「透明遮罩」(推荐 ✅)

核心思路:开始拖拽时,在页面最上层放一个 div 覆盖整个窗口, 即使鼠标移动到 iframe 区域,实际上仍然停留在这个透明层之上, 因此所有 mousemove / mouseup 事件都仍由父页面捕获。

<template>
<div
     v-if="previewDocShow"
     :class="`history-records  ${isfullScreen && 'history-records-full'} ${
             isDragging && 'dragging'
             }`"
     :style="`width: ${historyRecordsWidth}px`"
     >
  <!-- 拖拽遮罩 -->
  <div v-if="isDragging" class="drag-overlay"></div>
  <!-- 拖拽调整宽度控制 -->
  <div
       id="drag-resize"
       class="drag-resize"
       :title="`拖拽调整宽度 (当前: ${historyRecordsWidth}px)`"
       @mousedown="startDrag"
       >
    ⋮
  </div>
</div>
</template>
<style scoped>
  .drag-overlay {
    position: absolute;
    inset: 0;
    z-index: 98; // 低于拖拽手柄(99),高于内部 iframe
    background: transparent;
    cursor: ew-resize;
  }
</style>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30

优点:

  • 不需要访问 iframe。
  • 适用跨域 iframe。
  • 简单可靠。

drag-overlay的样式作用如下:

.drag-overlay {
  position: fixed;      /* 位置固定在视口,不随滚动条变化 */
  inset: 0;             /* 等价于 top:0; right:0; bottom:0; left:0; 让元素充满整个窗口 */
  z-index: 9999;        /* 放到页面最上层,保证盖住所有内容和 iframe */
  cursor: col-resize;   /* 鼠标样式显示为左右拖拽(左右两向箭头) */
  background: transparent; /* 完全透明,看不到但仍然接收鼠标事件 */
}
1
2
3
4
5
6
7