拖拽功能在今天的前端开发中变得非常普遍,不论是原生JavaScript还是流行的前端框架如Vue和React,都提供了丰富的拖拽实现方式。本文将带深入探讨拖拽技术的实现原理和不同框架下的实际应用,帮助大家全面掌握拖拽功能的实现方法。
原生
需求1:弹出拖拽框案例
- 点击拖拽按钮,显示拖拽框(阻止事件冒泡)
- 点击关闭按钮,隐藏拖拽框
- 点击页面的其他部分,隐藏拖拽框
- 点击拖拽框(阻止事件冒泡)
- 点击关闭,隐藏拖拽框
- 用户点击esc键之后,关闭拖拽框
需求2:拖拽
-
拖拽头部注册鼠标按下事件
-
document
注册鼠标移动事件(注意这里是document
)-
我们知道鼠标距离屏幕左侧和顶部的距离, 拖拽框跟着鼠标移动,需要用鼠标距离屏幕 - 鼠标距离拖拽框的距离,所以当鼠标按下去的时候,先要计算出鼠标距离拖拽框的距离x,y
-
当鼠标拖动的时候,鼠标距离屏幕的距离 - x =
drag
需要移动的距离 -
控制屏幕弹框在可视区域内
左边:最小0,右边:最大为屏幕宽度 - 盒子宽度 - 关闭弹框一半宽度
上边:最小关闭弹框一半宽度,下边:屏幕高度 - 盒子宽度
-
document
注册鼠标抬起事件,并且移除document
的鼠标移动事件
直接上代码吧
html
<!DOCTYPE html>
<html>
<head lang="en">
<meta charset="UTF-8" />
<title></title>
<style>
* {
padding: 0px;
margin: 0px;
}
.drag {
width: 512px;
border: #ebebeb solid 1px;
position: fixed;
left: 0;
top: 0;
box-shadow: 0px 0px 20px #ddd;
z-index: 2;
background-color: white;
display: none;
}
.drag-title {
height: 52px;
line-height: 52px;
text-align: center;
font-size: 20px;
border-bottom: #ebebeb solid 1px;
cursor: move;
/* 禁止用户选择文字 */
user-select: none;
}
.drag-content {
padding: 12px;
text-align: center;
}
.drag-content p {
height: 36px;
line-height: 36px;
}
.drag-close {
width: 36px;
height: 36px;
line-height: 36px;
text-align: center;
border-radius: 50%;
border: 1px solid #ebebeb;
position: absolute;
right: -18px;
top: -18px;
background-color: white;
font-size: 12px;
cursor: pointer;
}
.popup-draggable {
height: 52px;
line-height: 52px;
text-align: center;
}
a {
text-decoration: none;
color: #000000;
}
</style>
</head>
<body>
<div class="popup-draggable">
<a id="link" href="javascript:void(0);">点击,弹出拖拽框</a>
</div>
<div class="drag">
<div class="drag-title">
请拖拽我
</div>
<div class="drag-content">
<p>需求:弹出拖拽框案例</p>
<p>1、点击拖拽按钮,显示拖拽框(阻止事件冒泡)</p>
<p>2、点击关闭按钮,隐藏拖拽框</p>
<p>3、点击页面的其他部分,隐藏拖拽框</p>
<p>4、点击拖拽框(阻止事件冒泡)</p>
<p>5、点击关闭,隐藏拖拽框</p>
<p>6、用户点击esc键之后,关闭拖拽框</p>
</div>
<div class="drag-close">
关闭
</div>
</div>
<script>
/*
需求1:弹出拖拽框案例
1、点击拖拽按钮,显示拖拽框(阻止事件冒泡)
2、点击关闭按钮,隐藏拖拽框
3、点击页面的其他部分,隐藏拖拽框
4、点击拖拽框(阻止事件冒泡)
5、点击关闭,隐藏拖拽框
6、用户点击esc键之后,关闭拖拽框
*/
const link = document.querySelector('#link')
const drag = document.querySelector('.drag')
const close = document.querySelector('.drag-close')
const dragTitle = document.querySelector('.drag-title')
link.addEventListener('click', function (e) {
e.stopPropagation()
drag.style.display = 'block'
drag.style.top = (window.innerHeight - drag.offsetHeight) / 2 + 'px'
drag.style.left = (window.innerWidth - drag.offsetWidth) / 2 + 'px'
})
document.addEventListener('click', function (e) {
drag.style.display = 'none'
})
drag.addEventListener('click', function (e) {
e.stopPropagation()
})
close.addEventListener('click', function (e) {
drag.style.display = 'none'
})
document.addEventListener('keyup', function (e) {
if (e.keyCode !== 27) return
drag.style.display = 'none'
})
/*
需求2:拖拽
1、拖拽头部注册鼠标按下事件
2、document注册鼠标移动事件(注意这里是document)
*/
let x = 0
let y = 0
dragTitle.addEventListener('mousedown', dragFn)
function dragFn(e) {
console.log('按下鼠标', e.pageX, e.pageY)
// 2.1 我们知道鼠标距离屏幕左侧和顶部的距离,
// 拖拽框跟着鼠标移动,需要用鼠标距离屏幕 - 鼠标距离拖拽框的距离
// 所以当鼠标按下去的时候,先要计算出鼠标距离拖拽框的距离
x = e.pageX - drag.offsetLeft
y = e.pageY - drag.offsetTop
// 2.2 给document注册移动事件
document.addEventListener('mousemove', moveFn)
}
function moveFn(e) {
console.log('拖动鼠标')
// 2.3 鼠标距离屏幕的距离 - x = drag需要移动的距离
let moveX = e.pageX - x
let moveY = e.pageY - y
// 2.4 控制屏幕弹框在可视区域内
// 左边:最小0,右边:最大为屏幕宽度 - 盒子宽度 - 关闭弹框一半宽度
// 上边:最小关闭弹框一半宽度,下边:屏幕高度 - 盒子宽度
const maxWidth = window.innerWidth - drag.offsetWidth - 18
moveX = Math.min(moveX, maxWidth)
moveX = Math.max(0, moveX)
const maxHeight = window.innerHeight - drag.offsetHeight
moveY = Math.min(moveY, maxHeight)
moveY = Math.max(18, moveY)
drag.style.left = moveX + 'px'
drag.style.top = moveY + 'px'
}
// 特别注意:这里是给document添加抬起事件
document.addEventListener('mouseup', function (e) {
console.log('鼠标弹起了')
// 特别注意:移除的是document的mousemove事件
document.removeEventListener('mousemove', moveFn)
})
</script>
</body>
</html>
vue hooks
新建hooks
文件
src
文件下新建hooks
文件夹,新建文件useDraggable.ts
typescript
const useDraggable = (x = 0, y = 0) => {
const position = reactive({x, y})
const box = ref<HTMLDivElement | null>(null)
const handleMouseDown = (e: MouseEvent) => {
const startX = e.clientX - position.x
const startY = e.clientY - position.y
const handleMouseMove = (e: MouseEvent) => {
let distanceX = e.clientX - startX
let distanceY = e.clientY - startY
const maxX = document.body.clientWidth - box.value!.offsetWidth
const maxY = document.body.clientHeight - box.value!.offsetHeight
distanceX = Math.min(maxX, distanceX)
distanceX = Math.max(0, distanceX)
distanceY = Math.min(maxY, distanceY)
distanceY = Math.max(0, distanceY)
position.x = distanceX
position.y = distanceY
}
const handleMouseUp = () => {
document.removeEventListener('mousemove', handleMouseMove)
document.removeEventListener('mouseup', handleMouseUp)
}
document.addEventListener('mousemove', handleMouseMove)
document.addEventListener('mouseup', handleMouseUp)
}
return {
onMouseDown: handleMouseDown,
box,
position
}
}
export default useDraggable
使用方式
vue
<template>
<div class="drag" ref="box" :style="dragPositionStyle" @mousedown="onMouseDown">
</div>
</template>
<script lang="ts" setup>
import useDraggable from '@/hooks/useDraggable'
const { position, onMouseDown, box} = useDraggable()
const dragPositionStyle = computed(() => ({left: `${position.x}px`, top: `${position.y}px`}))
</script>
react hooks
新建hooks
文件
src
文件下新建hooks
文件夹,新建文件useDraggable.ts
ini
import { useRef, useState } from 'react';
const useDraggable = (x = 0, y = 0) => {
const [position, setPosition] = useState({ x, y });
const box = useRef(null);
const handleMouseDown = (e) => {
const startX = e.clientX - position.x;
const startY = e.clientY - position.y;
const handleMouseMove = (e) => {
let distanceX = e.clientX - startX;
let distanceY = e.clientY - startY;
let maxX = document.body.clientWidth - box.current.offsetWidth;
let maxY = document.body.clientHeight - box.current.offsetHeight;
distanceX = Math.min(maxX, distanceX);
distanceX = Math.max(0, distanceX);
distanceY = Math.min(maxY, distanceY);
distanceY = Math.max(0, distanceY);
setPosition({
x: distanceX,
y: distanceY,
});
};
const handleMouseUp = () => {
document.removeEventListener('mousemove', handleMouseMove);
document.removeEventListener('mouseup', handleMouseUp);
};
document.addEventListener('mousemove', handleMouseMove);
document.addEventListener('mouseup', handleMouseUp);
};
return {
style: {
position: 'fixed',
left: position.x + 'px',
top: position.y + 'px',
cursor: 'move',
},
onMouseDown: handleMouseDown,
box
};
};
export default useDraggable;
使用方式
javascript
import useDraggable from '@/hooks/useDraggable';
const { style, onMouseDown, box } = useDraggable(window.innerWidth - 160, window.innerHeight / 2 - 100);
<div style={style} onMouseDown={onMouseDown} ref={box}></div>
vue
和react
的hooks
略有不同,大家可以在此基础扩展功能和细节!