在前端开发中,页面中的数据元素横向排列是一种很常见的ui设计,比如首页面中的产品列表,tab标签页的标题等。
当列表元素数量过多的时候,横向排列不下,就会出现横向滚动条,或者让列表元素换行的情况。
今天我们介绍一种可以让列表元素左右拖拽的实现方案,以避免出现横向滚动条,和换行的情况。
最为演示示例,我们假定页面中有一个 div 作为数据显示的容器(container),container 内有一个子元素(div),子元素的宽度大于container,我们设置container 的css 样式:overflow: hidden; 如下图:
现在,我们在子元素中添加数据列表:
实现代码:
HTML
<div class="container">
<div class="data-list">
<div class="data-item">数据项 1</div>
<div class="data-item">数据项 2</div>
<div class="data-item">数据项 3</div>
<div class="data-item">数据项 4</div>
<div class="data-item">数据项 5</div>
<div class="data-item">数据项 6</div>
<div class="data-item">数据项 7</div>
<div class="data-item">数据项 8</div>
<div class="data-item">数据项 9</div>
<div class="data-item">数据项 10</div>
</div>
</div>
CSS
.container {
height: 100vh;
width: 600px;
background-color: #fff;
border: 1px solid #ddd;
overflow: hidden;
}
.data-list {
height: 100px;
width: 2090px;
}
.data-item {
float: left;
height: 100%;
width: 200px;
background-color: #eee;
text-align: center;
line-height: 100px;
user-select: none;
}
.data-item + .data-item {
margin-left: 10px;
}
接下来,我们给子元素(data-list)添加拖动事件。我们先前写过一篇关于拖动div的文章(JS 实现 div 自由拖拽),大家有兴趣的可以看一下。在那片文章中实现的是鼠标自由拖拽div,本章中只需要左右拖动。另外,为了多演示一种拖动方式,本章中我们使用 transform 方式来实现。如下 JS 代码:
var el = document.querySelector('.data-list');
el.addEventListener('mousedown', drag);
var offset=0;
function drag(e){
var startX = e.clientX;
var mousemove=(evt)=>{
let x=evt.clientX;
offset += x - startX;
setPosition(offset);
startX=x;
};
var mouseup=(evt)=>{
document.removeEventListener('mousemove', mousemove);
document.removeEventListener('mouseup', mouseup);
};
document.addEventListener('mousemove', mousemove);
document.addEventListener('mouseup', mouseup);
}
function setPosition(ofs){
el.style.transform='translateX('+ofs+'px)';
}
至此,子元素就实现了左右拖动:
不过还有个问题,就是在子元素可以被无限横向拖拽,甚至可以被拖到视窗范围之外。所以我们还需要在结束拖拽的时候,也就是 mouseup 事件中,检查一下子元素的位置,如果子元素的最左侧与容器的最左侧有间距,或者子元素的最右侧与容器的最右侧有间距,就重新调整子元素的偏移量。
为此,我们添加一个 checkPosition 的函数:
function checkPosition(){
if(offset>0){
this.setPosition(0);
return;
}
var maxOffset=el.offsetWidth - el.parentNode.offsetWidth;
if(Math.abs(offset)>maxOffset){
setPosition(-maxOffset);
}
}
同时,修改一下setPosition 函数,以更新偏移量:
function setPosition(ofs){
el.style.transform='translateX('+ofs+'px)';
offset=ofs;
}
然后在mouseUp事件中,调用 checkPosition:
var mouseup=(evt)=>{
checkPosition();
document.removeEventListener('mousemove', mousemove);
document.removeEventListener('mouseup', mouseup);
};
这样当子元素被拖到视窗之外时,会自动“复位”:
为了让子元素在“复位”的时候,更平滑一些,我们还可以再做一些改进,就是给子元素添加动画(transition)。但是我们不希望在拖动的过程中启用动画,而是在拖拽结束,让子元素恢复位置的时候再启用动画,所以我们在 mouseup 事件中添加 transition 样式,而在 mousedown 事件的时候,取消 transition 样式。如下:
…
var offset=0;
function drag(e){
el.style.transition='';
var startX = e.clientX;
…
function checkPosition(){
el.style.transition='transform 0.3s';
if(offset>0){
this.setPosition(0);
return;
…
}
…
完整代码如下:
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="utf-8">
<title>DIV 拖拽</title>
<style>
.container {
height: 100vh;
width: 600px;
background-color: #fff;
border: 1px solid #ddd;
overflow: hidden;
}
.data-list {
height: 100px;
width: 2090px;
}
.data-item {
float: left;
height: 100%;
width: 200px;
background-color: #eee;
text-align: center;
line-height: 100px;
user-select: none;
}
.data-item + .data-item {
margin-left: 10px;
}
</style>
</style>
</head>
<body>
<div class="container">
<div class="data-list">
<div class="data-item">数据项 1</div>
<div class="data-item">数据项 2</div>
<div class="data-item">数据项 3</div>
<div class="data-item">数据项 4</div>
<div class="data-item">数据项 5</div>
<div class="data-item">数据项 6</div>
<div class="data-item">数据项 7</div>
<div class="data-item">数据项 8</div>
<div class="data-item">数据项 9</div>
<div class="data-item">数据项 10</div>
</div>
</div>
<script>
var el = document.querySelector('.data-list');
el.addEventListener('mousedown', drag);
var offset=0;
function drag(e){
el.style.transition='';
var startX = e.clientX;
var mousemove=(evt)=>{
let x=evt.clientX;
offset += x - startX;
setPosition(offset);
startX=x;
};
var mouseup=(evt)=>{
checkPosition();
document.removeEventListener('mousemove', mousemove);
document.removeEventListener('mouseup', mouseup);
};
document.addEventListener('mousemove', mousemove);
document.addEventListener('mouseup', mouseup);
}
function setPosition(ofs){
el.style.transform='translateX('+ofs+'px)';
offset=ofs;
}
function checkPosition(){
el.style.transition='transform 0.3s';
if(offset>0){
this.setPosition(0);
return;
}
var maxOffset=el.offsetWidth - el.parentNode.offsetWidth;
if(Math.abs(offset)>maxOffset){
setPosition(-maxOffset);
}
}
</script>
</body>
</html>
该文章在 2025/7/1 21:09:46 编辑过