JS怎么实现悬浮窗拖拽 4行代码让元素支持鼠标自由拖拽

JS怎么实现悬浮窗拖拽 4行代码让元素支持鼠标自由拖拽
最新回答
辣条的发明妨碍我当淑女

2022-08-05 05:56:59

实现悬浮窗拖拽的4行核心代码(基础版)

element.addEventListener('mousedown', (e) => { const { offsetX, offsetY } = e; const onMouseMove = (e) => { element.style.transform = `translate(${e.clientX - offsetX}px, ${e.clientY - offsetY}px)`; }; document.addEventListener('mousemove', onMouseMove); document.addEventListener('mouseup', () => document.removeEventListener('mousemove', onMouseMove), { once: true });});完整实现方案(含优化与扩展功能)1. 基础拖拽实现
  • 核心逻辑:通过监听mousedown、mousemove、mouseup事件,计算鼠标偏移量并更新元素位置。
  • 代码说明

    mousedown时记录初始偏移量(offsetX/Y)。

    mousemove时通过transform: translate()更新元素位置,避免直接操作left/top导致的重绘。

    mouseup时移除mousemove监听,防止内存泄漏。

2. 性能优化
  • 硬件加速:使用transform替代left/top,触发GPU加速。
  • 节流处理:限制mousemove触发频率(如16ms一次,约60FPS)。
function throttle(func, delay) { let lastCall = 0; return (...args) => { const now = new Date().getTime(); if (now - lastCall >= delay) { func(...args); lastCall = now; } };}document.addEventListener('mousemove', throttle((e) => { if (!dragging) return; element.style.transform = `translate(${e.clientX - offsetX}px, ${e.clientY - offsetY}px)`;}, 16));3. 边界限制
  • 逻辑:计算屏幕与元素的宽高,限制translate值不超出边界。
document.addEventListener('mousemove', (e) => { if (!dragging) return; const { innerWidth: screenW, innerHeight: screenH } = window; const { offsetWidth: elemW, offsetHeight: elemH } = element; let x = e.clientX - offsetX, y = e.clientY - offsetY; x = Math.max(0, Math.min(x, screenW - elemW)); y = Math.max(0, Math.min(y, screenH - elemH)); element.style.transform = `translate(${x}px, ${y}px)`;});4. 事件冲突处理
  • 阻止冒泡:在悬浮窗的mousedown中调用e.stopPropagation()。
  • 禁用内部元素事件:拖拽时临时设置pointer-events: none。
element.addEventListener('mousedown', (e) => { e.stopPropagation(); element.querySelectorAll('*').forEach(el => el.style.pointerEvents = 'none'); // ...其余拖拽逻辑});document.addEventListener('mouseup', () => { element.querySelectorAll('*').forEach(el => el.style.pointerEvents = 'auto');});5. 边缘吸附效果
  • 逻辑:在mouseup时计算元素到四边的距离,移动到最近边缘。
document.addEventListener('mouseup', () => { const { left, top, width, height } = element.getBoundingClientRect(); const screenW = window.innerWidth, screenH = window.innerHeight; const distances = { left, top, right: screenW - left - width, bottom: screenH - top - height }; const closestEdge = Object.entries(distances).reduce((a, b) => a[1] < b[1] ? a : b)[0]; const target = { left: closestEdge === 'right' ? screenW - width : 0, top: closestEdge === 'bottom' ? screenH - height : 0 }; if (closestEdge === 'left' || closestEdge === 'right') target.top = top; if (closestEdge === 'top' || closestEdge === 'bottom') target.left = left; element.style.transition = 'transform 0.3s ease'; element.style.transform = `translate(${target.left}px, ${target.top}px)`;});最终优化版代码let dragging = false;let offsetX, offsetY;const element = document.getElementById('draggable'); // 替换为实际元素// 基础拖拽 + 性能优化 + 边界限制element.addEventListener('mousedown', (e) => { dragging = true; offsetX = e.offsetX; offsetY = e.offsetY; e.stopPropagation(); element.querySelectorAll('*').forEach(el => el.style.pointerEvents = 'none');});const throttleMove = throttle((e) => { if (!dragging) return; const { innerWidth: screenW, innerHeight: screenH } = window; const { offsetWidth: elemW, offsetHeight: elemH } = element; let x = e.clientX - offsetX, y = e.clientY - offsetY; x = Math.max(0, Math.min(x, screenW - elemW)); y = Math.max(0, Math.min(y, screenH - elemH)); element.style.transform = `translate(${x}px, ${y}px)`;}, 16);document.addEventListener('mousemove', throttleMove);document.addEventListener('mouseup', () => { dragging = false; element.querySelectorAll('*').forEach(el => el.style.pointerEvents = 'auto'); // 边缘吸附 const { left, top, width, height } = element.getBoundingClientRect(); const screenW = window.innerWidth, screenH = window.innerHeight; const distances = { left, top, right: screenW - left - width, bottom: screenH - top - height }; const closestEdge = Object.entries(distances).reduce((a, b) => a[1] < b[1] ? a : b)[0]; const target = { left: closestEdge === 'right' ? screenW - width : 0, top: closestEdge === 'bottom' ? screenH - height : 0 }; if (closestEdge === 'left' || closestEdge === 'right') target.top = top; if (closestEdge === 'top' || closestEdge === 'bottom') target.left = left; element.style.transition = 'transform 0.3s ease'; element.style.transform = `translate(${target.left}px, ${target.top}px)`; setTimeout(() => element.style.transition = 'none', 300);});function throttle(func, delay) { let lastCall = 0; return (...args) => { const now = new Date().getTime(); if (now - lastCall >= delay) { func(...args); lastCall = now; } };}关键点总结
  • 核心机制:通过鼠标事件计算偏移量,动态更新元素位置。
  • 性能优化:transform + 节流减少重绘,提升流畅度。
  • 边界处理:实时计算屏幕与元素尺寸,限制拖拽范围。
  • 交互完善:阻止事件冒泡、禁用内部元素事件,避免冲突。
  • 体验增强:拖拽结束后自动吸附边缘,增加transition平滑动画。