在前端开发中,页面中的数据元素横向排列是一种很常见的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 编辑过