关于元素拖拽的实现方式

64 阅读2分钟

在使用 draw.io 绘置流程图时,突然很想弄清楚其中对元素的拖拽和缩放是怎么实现的,下面就先用原生 js 来实现拖拽功能,后续再写篇文章添加缩放功能。

codepen 地址

准备

先把 html,css 整好:

<div class="container">
  <div class="item">
    ITEM
  </div>
</div>
body{
   background: #e5e5e5;
}
.container {
  width: 600px;
  height: 600px;
  margin: auto;
  background: #fff;
  position: relative;
  overflow: hidden;
}

.item {
  position: absolute;
  top: 100px;
  left: 100px;
  width: 100px;
  height: 100px;
  background: skyblue;
  display: flex;
  justify-content: center;
  align-items: center;
}

得到一个大概这样的页面:

Clipboard_Screenshot_1740126757.png

实现思路

获取鼠标相对位置

这个功能主要是要实现元素 styletop, left 属性值随鼠标改变而改变。那么首先要意识到,当按住鼠标拖动元素时,鼠标和元素的相对位置是不变的。

也就是下图中 topleft 值。

Clipboard_Screenshot_1740127505.png

这两个值我们可以用鼠标在视口的位置减去元素在视口中的位置。让我们在 mousedown事件中处理这个逻辑,并交结果储存在 distance 变量里

let container = document.querySelector('.container')
let item = document.querySelector('.item')

let distance = {
  x: 0,
  y: 0
} //  鼠标相对拖动元素的距离

item.onmousedown = (e) => {
  if (item) {
    const {left, top} = item.getBoundingClientRect() // 元素相对于视图窗口左上角的位置
    distance.x = e.clientX - left
    distance.y = e.clientY - top
  }
  alert(`distanceX: ${distance.x} distanceY: ${distance.y}`)
}

依据鼠标位置实时计算元素位置

mousedown 事件触发后,我们就要开始实现拖动功能,需要监听 mousemove 事件,并在其中 计算元素实时位置,并将其计算的位置赋值到元素的style中,实现如下:

let item = document.querySelector('.item')
let container = document.querySelector('.container')

let distance = {
  x: 0,
  y: 0
} //  鼠标相对拖动元素的距离

// 计算实时位置
const getItemPosition = (e) => {
  const left = e.pageX - distance.x - container.offsetLeft
  const top = e.pageY - distance.y - container.offsetTop
  return {
    left,
    top
  }
}

const handleMouseMove = (e) => {
  const { left, top } = getItemPosition(e)
  if (item) {
    item.style.top = top + 'px' // 在style上赋值
    item.style.left = left + 'px'
  }
}

item.onmousedown = (e) => {
  if (item) {
    const {left, top} = item.getBoundingClientRect()
    distance.x = e.clientX - left
    distance.y = e.clientY - top
  }
  // alert(`distanceX: ${distance.x} distanceY: ${distance.y}`)
  document.addEventListener('mousemove', handleMouseMove)
}

关于 getItemPosition 的实现可以用一张图来概括:

Clipboard_Screenshot_1740130150.png

实现完后,发现元素可以跟着鼠标一起动了,但是元素会一直跟着鼠标,这时候就需要在某个时候清除mousemove事件。

清除mousemove事件

我们可以在mouseup事件中将mousemove事件清除,同时设置个定时器也将自己清除。实现如下:

const handleMouseUp = (e) => {
  document.removeEventListener('mousemove', handleMouseMove)
  setTimeout(() => {
    document.removeEventListener('mouseup', handleMouseUp)
  }, 100)
}

item.onmousedown = (e) => {
  ···
  document.addEventListener('mouseup', handleMouseUp)
}

完整js代码如下:

let item = document.querySelector('.item')
let container = document.querySelector('.container')

let distance = {
  x: 0,
  y: 0
} //  鼠标相对拖动元素的距离

const getItemPosition = (e) => {
  const left = e.pageX - distance.x - container.offsetLeft
  const top = e.pageY - distance.y - container.offsetTop
  console.log(container.offsetTop, container.scrollTop)
  return {
    left,
    top
  }
}

const handleMouseMove = (e) => {
  const { left, top } = getItemPosition(e)
  if (item) {
    item.style.top = top + 'px'
    item.style.left = left + 'px'
  }
}

const handleMouseUp = (e) => {
  document.removeEventListener('mousemove', handleMouseMove)
  setTimeout(() => {
    document.removeEventListener('mouseup', handleMouseUp)
  }, 100)
}

item.onmousedown = (e) => {
  if (item) {
    const {left, top} = item.getBoundingClientRect()
    distance.x = e.clientX - left
    distance.y = e.clientY - top
  }
  // alert(`distanceX: ${distance.x} distanceY: ${distance.y}`)
  document.addEventListener('mousemove', handleMouseMove)
  document.addEventListener('mouseup', handleMouseUp)
}

下篇文章会继续实现缩放功能,记录下我的思路和解决方案。