本文介绍了如何实现元素的拖动功能,包括简单元素的拖动、列表项的拖动以及表格列和行的拖动。通过处理鼠标的mousedown、mousemove和mouseup事件,结合动态DOM操作,读者可以掌握拖动效果的实现技巧,提升用户体验。文中提供了详细的HTML和JavaScript代码示例,帮助读者理解拖动功能的核心逻辑和实现方法。
元素拖动
刚开始,咱们循序渐进,先来实现一个最简单的功能:让一个元素变成可拖动元素。
布局与样式
<!DOCTYPE html>
<html>
<head>
<title>元素拖动</title>
<style>
#drag {
width: 100px;
height: 100px;
border: 1px solid #cbd5e0;
display: flex;
justify-content: center;
align-items: center;
cursor: move;
user-select: none;
position: absolute;
}
</style>
</head>
<body>
<div id="drag">橙某人</div>
</body>
</html>
事件处理
要让一个元素可拖动,我们需要处理三个事件:
- mousedown:按下。
- mousemove:移动。
- mouseup:释放。
具体逻辑如下:
<script>
document.addEventListener('DOMContentLoaded', () => {
const drag = document.getElementById('drag');
// 添加鼠标按下事件
drag.addEventListener('mousedown', mouseDownHandler);
// 记录鼠标坐标信息
let x = 0;
let y = 0;
// 鼠标按下事件
function mouseDownHandler(e) {
// 记录鼠标初始位置
x = e.clientX;
y = e.clientY;
// 添加鼠标移动与释放事件
document.addEventListener('mousemove', mouseMoveHandler);
document.addEventListener('mouseup', mouseUpHandler);
}
// 鼠标移动事件
function mouseMoveHandler(e) {
// 计算鼠标拖动的距离
const dx = e.clientX - x;
const dy = e.clientY - y;
// 将拖动距离赋值给目标元素
drag.style.top = `${drag.offsetTop + dy}px`;
drag.style.left = `${drag.offsetLeft + dx}px`;
// 不断记录鼠标上一个位置
x = e.clientX;
y = e.clientY;
}
// 鼠标释放事件
function mouseUpHandler() {
// 重置相关变量
x = 0;
y = 0;
// 移除事件
document.removeEventListener('mousemove', mouseMoveHandler);
document.removeEventListener('mouseup', mouseUpHandler);
}
});
</script>
列表拖动
简单的整完,咱们开始上点强度?,这次要做的功能如下:
布局与样式
<!DOCTYPE html>
<html>
<head>
<title>列表拖动</title>
<style>
.item {
width: 200px;
height: 60px;
border: 1px solid #cbd5e0;
display: flex;
justify-content: center;
align-items: center;
cursor: move;
user-select: none;
margin: 10px 0;
box-sizing: border-box;
}
</style>
</head>
<body>
<div id="list">
<div class="item">第一个元素</div>
<div class="item">第二个元素</div>
<div class="item">第三个元素</div>
<div class="item">第四个元素</div>
<div class="item">第五个元素</div>
</div>
</body>
</html>
事件处理
接下来,让每个元素变成可拖动元素:
<script>
document.addEventListener('DOMContentLoaded', () => {
const list = document.getElementById('list');
list.querySelectorAll('.item').forEach(item => {
item.addEventListener('mousedown', mouseDownHandler);
});
let x = 0;
let y = 0;
let draggingElement;
function mouseDownHandler(e) {
draggingElement = e.target;
const rect = draggingElement.getBoundingClientRect();
x = e.clientX - rect.left;
y = e.clientY - rect.top;
document.addEventListener('mousemove', mouseMoveHandler);
document.addEventListener('mouseup', mouseUpHandler);
}
function mouseMoveHandler(e) {
const left = e.clientX - x;
const top = e.clientY - y;
draggingElement.style.position = 'absolute';
draggingElement.style.top = `${top}px`;
draggingElement.style.left = `${left}px`;
}
function mouseUpHandler() {
draggingElement.style.removeProperty('top');
draggingElement.style.removeProperty('left');
draggingElement.style.removeProperty('position');
x = 0;
y = 0;
draggingElement = null;
document.removeEventListener('mousemove', mouseMoveHandler);
document.removeEventListener('mouseup', mouseUpHandler);
}
});
</script>
效果如下:
表格拖动 - 列
接下来要做的是表格上的拖动功能,先看效果图:
动态创建列表
在表格中实现拖动,我们首先要将表格转换为一个可拖动的列表。
事件处理
具体实现如下:
<script>
document.addEventListener('DOMContentLoaded', () => {
const table = document.getElementById('table');
table.querySelectorAll('th').forEach(headerCell => {
headerCell.classList.add('draggable');
headerCell.addEventListener('mousedown', mouseDownHandler);
});
let draggingElement;
let draggingColumnIndex;
let x = 0;
let y = 0;
let isDraggingStarted = false;
function mouseDownHandler(e) {
draggingColumnIndex = [].slice.call(table.querySelectorAll('th')).indexOf(e.target);
x = e.clientX - e.target.offsetLeft;
y = e.clientY - e.target.offsetTop;
document.addEventListener('mousemove', mouseMoveHandler);
document.addEventListener('mouseup', mouseUpHandler);
}
function mouseMoveHandler(e) {
if (!isDraggingStarted) {
isDraggingStarted = true;
createList();
}
}
function createList() {
const rect = table.getBoundingClientRect();
list = document.createElement('div');
list.classList.add('list');
list.style.position = 'absolute';
list.style.left = rect.left + 'px';
list.style.top = rect.top + 'px';
table.parentNode.insertBefore(list, table);
table.style.visibility = 'hidden';
const originalCells = [].slice.call(table.querySelectorAll('tbody td'));
const originalHeaderCells = [].slice.call(table.querySelectorAll('th'));
const numColumns = originalHeaderCells.length;
originalHeaderCells.forEach((headerCell, headerIndex) => {
const { width } = window.getComputedStyle(headerCell);
const item = document.createElement('div');
item.classList.add('draggable');
const newTable = document.createElement('table');
newTable.setAttribute('class', 'list__table');
newTable.style.width = width;
const th = headerCell.cloneNode(true);
let newRow = document.createElement('tr');
newRow.appendChild(th);
newTable.appendChild(newRow);
const cells = originalCells.filter((c, idx) => {
return (idx - headerIndex) % numColumns === 0;
});
cells.forEach(cell => {
const newCell = cell.cloneNode(true);
newCell.style.width = width + 'px';
newRow = document.createElement('tr');
newRow.appendChild(newCell);
newTable.appendChild(newRow);
});
item.appendChild(newTable);
list.appendChild(item);
});
}
function mouseUpHandler() {
document.removeEventListener('mousemove', mouseMove
Handler);
document.removeEventListener('mouseup', mouseUpHandler);
}
});
</script>
表格拖动 - 行
既然讲了表格的列拖动,那么行的拖动肯定也不能落下啦。?
CSS 样式
.list {
border-top: 1px solid #ccc;
}
.list__table th,
.list__table td {
border: 1px solid #ccc;
border-top: none;
box-sizing: border-box;
padding: 10px;
text-align: center;
color: red;
}
事件处理
具体实现如下:
<script>
document.addEventListener('DOMContentLoaded', () => {
const table = document.getElementById('table');
table.querySelectorAll('tr').forEach((row, index) => {
if (index === 0) return;
const firstCell = row.firstElementChild;
firstCell.classList.add('draggable');
firstCell.addEventListener('mousedown', mouseDownHandler);
});
let draggingColumnIndex;
let x = 0;
let y = 0;
let isDraggingStarted = false;
let list;
let draggingElement;
let placeholder;
function mouseDownHandler(e) {
const originalRow = e.target.parentNode;
draggingRowIndex = [].slice.call(table.querySelectorAll('tr')).indexOf(originalRow);
x = e.clientX;
y = e.clientY;
document.addEventListener('mousemove', mouseMoveHandler);
document.addEventListener('mouseup', mouseUpHandler);
}
function mouseMoveHandler(e) {
if (!isDraggingStarted) {
isDraggingStarted = true;
createList();
draggingElement = [].slice.call(list.children)[draggingRowIndex];
draggingElement.classList.add('dragging');
placeholder = document.createElement('div');
placeholder.classList.add('placeholder');
draggingElement.parentNode.insertBefore(placeholder, draggingElement.nextSibling);
placeholder.style.height = draggingElement.offsetHeight + 'px';
}
draggingElement.style.position = 'absolute';
draggingElement.style.top = (draggingElement.offsetTop + e.clientY - y) + 'px';
draggingElement.style.left = (draggingElement.offsetLeft + e.clientX - x) + 'px';
x = e.clientX;
y = e.clientY;
const prevEle = draggingElement.previousElementSibling;
const nextEle = placeholder.nextElementSibling;
if (prevEle && prevEle.previousElementSibling && isAbove(draggingElement, prevEle)) {
swap(placeholder, draggingElement);
swap(placeholder, prevEle);
return;
}
if (nextEle && isAbove(nextEle, draggingElement)) {
swap(nextEle, placeholder);
swap(nextEle, draggingElement);
}
}
function createList() {
const rect = table.getBoundingClientRect();
const width = window.getComputedStyle(table).width;
list = document.createElement('div');
list.classList.add('list');
list.style.position = 'absolute';
list.style.left = rect.left + 'px';
list.style.top = rect.top + 'px';
table.parentNode.insertBefore(list, table);
table.style.visibility = 'hidden';
table.querySelectorAll('tr').forEach(row => {
const item = document.createElement('div');
item.classList.add('draggable');
const newTable = document.createElement('table');
newTable.setAttribute('class', 'list__table');
newTable.style.width = width;
const newRow = document.createElement('tr');
const cells = [].slice.call(row.children);
cells.forEach(cell => {
const newCell = cell.cloneNode(true);
newCell.style.width = window.getComputedStyle(cell).width;
newRow.appendChild(newCell);
});
newTable.appendChild(newRow);
item.appendChild(newTable);
list.appendChild(item);
});
}
function swap(nodeA, nodeB) {
// ... 类似前面的代码逻辑
}
function isAbove(nodeA, nodeB) {
const rectA = nodeA.getBoundingClientRect();
const rectB = nodeB.getBoundingClientRect();
const centerPointA = rectA.top + rectA.height / 2;
const centerPointB = rectB.top + rectB.height / 2;
return centerPointA < centerPointB;
}
function mouseUpHandler() {
placeholder && placeholder.parentNode.removeChild(placeholder);
draggingElement.classList.remove('dragging');
draggingElement.style.removeProperty('top');
draggingElement.style.removeProperty('left');
draggingElement.style.removeProperty('position');
const endRowIndex = [].slice.call(list.children).indexOf(draggingElement);
isDraggingStarted = false;
list.parentNode.removeChild(list);
let rows = [].slice.call(table.querySelectorAll('tr'));
if (draggingRowIndex > endRowIndex) {
rows[endRowIndex].parentNode.insertBefore(
rows[draggingRowIndex],
rows[endRowIndex]
);
}else{
rows[endRowIndex].parentNode.insertBefore(
rows[draggingRowIndex],
rows[endRowIndex].nextSibling
);
}
table.style.removeProperty('visibility');
document.removeEventListener('mousemove', mouseMoveHandler);
document.removeEventListener('mouseup', mouseUpHandler);
}
});
</script>
结语
通过以上步骤,我们实现了表格的列拖动和行拖动的效果。掌握这些技巧,可以让你```markdown
在需要拖动表格内容时更灵活地进行操作,提升用户体验。
小结
实现拖动功能的核心在于事件处理和动态DOM操作。通过对元素的mousedown
、mousemove
、mouseup
事件的监听,我们可以精确地控制元素的拖动行为。结合getBoundingClientRect
方法获取元素的位置和尺寸信息,以及insertBefore
等DOM操作方法,可以灵活地实现元素的拖动和交换。
拖动实现的几点总结:
事件处理:
mousedown
用于捕获鼠标按下事件,mousemove
用于检测鼠标移动的距离,并更新元素的位置,mouseup
用于检测鼠标释放并清理事件监听。坐标计算:通过计算鼠标在拖动元素上的相对位置,以及鼠标的移动距离,更新元素的
top
和left
属性,实现元素的拖动效果。占位元素:在拖动列表和表格行列时,使用占位元素保持页面布局的稳定,避免元素拖动时导致其他元素位置变化。
动态创建列表:在表格列拖动的实现中,通过动态创建与表格列对应的列表项,实现灵活的列拖动效果。这样避免了直接操作复杂的表格DOM结构,而是通过操作列表来简化问题。
拖动方向判断:通过比较元素中心点的位置,判断拖动的方向,以便在拖动过程中正确地交换元素位置。
交换元素:无论是列表项还是表格列或行,交换元素位置的核心在于正确使用
insertBefore
方法,确保交换后的DOM结构保持一致性。
最终效果展示:
通过这些技术,我们可以实现更复杂的UI交互效果,如列表项拖动、表格列拖动、表格行拖动等。最终的实现效果不仅功能完善,还具有良好的用户体验。
完整源码
你可以通过Gitee仓库获取本文中所有示例的完整源码。
学习和练习
拖动效果的实现是前端开发中的一个经典案例,也是很多高级UI交互中的核心部分。通过对这类功能的实现和理解,能够帮助你更好地掌握DOM操作、事件处理等前端核心技术。