在使用 draw.io 绘置流程图时,突然很想弄清楚其中对元素的拖拽和缩放是怎么实现的,下面就先用原生 js 来实现拖拽功能,后续再写篇文章添加缩放功能。
准备
先把 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;
}
得到一个大概这样的页面:
实现思路
获取鼠标相对位置
这个功能主要是要实现元素 style
的 top
, left
属性值随鼠标改变而改变。那么首先要意识到,当按住鼠标拖动元素时,鼠标和元素的相对位置是不变的。
也就是下图中 top
、left
值。
这两个值我们可以用鼠标在视口的位置减去元素在视口中的位置。让我们在 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
的实现可以用一张图来概括:
实现完后,发现元素可以跟着鼠标一起动了,但是元素会一直跟着鼠标,这时候就需要在某个时候清除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)
}
下篇文章会继续实现缩放功能,记录下我的思路和解决方案。