美文网首页Web前端之路
列表拖拽转换的例子

列表拖拽转换的例子

作者: 我是皮蛋 | 来源:发表于2020-02-29 21:33 被阅读0次

列表拖拽转换的例子

今天看到有些表格中能够通过拖动来换行,手动排序的功能,想来自己实现一下,类似效果如下

image

主要解决问题的办法

  • drag元素

  • clientX和clientY

  • 动画元素animition

解决思路

  • 委托外部元素监听内容元素的(dragStart阶段,定位被拖动的元素)

  • 委托外部元素监听元素已经被拉到哪个元素的位置 (使用dragover确定,停留在哪个元素的位置上)

  • 为元素添加动画效果

  • 利用node.appendChild相同元素实现元素转换,参考不使用append的remove使节点快速转移的方法

从一个简单的例子开始

image
HTML

<!---html部分--->

<ul id="container">

    <li draggable="true">1</li>

    <li draggable="true">2</li>

    <li draggable="true">3</li>

    <li draggable="true">4</li>

    <li draggable="true">5</li>

    <li draggable="true">6</li>

    <li draggable="true">7</li>

    <li draggable="true">8</li>

    <li draggable="true">9</li>

    <li draggable="true">10</li>

</ul>

<button onclick="resetAll()">Reset</button>

  • 配置draggable=true,使元素变为滑动的
CSS

ul {

    list-style: none;

}

li {

    padding: 30px 15px;

    background-color: cornflowerblue;

    color: #fff;

    border: 1px solid #333;

    border-radius: 10px;

    width: 30px;

    text-align: center;

    margin: 10px 0;

    transition: transform 2s ease;

}

JS逻辑

const con = document.getElementById("container");

let list = [];

// 主要为了动画滑动的时候,用于控制滑动距离的

const init = () => {

    lists = Array.prototype.slice

        .call(document.querySelectorAll("li"))

        .map((element, index) => {

        element.dataset.index = index;

        return {

            index,

            offsetX: element.offsetLeft + con.offsetLeft,

            offsetY: element.offsetTop + con.offsetTop

        };

    });

};

init();

let startX = 0,

    startY = 0;

// 重置按钮的回调

let resetAll = () => {

    const list = Array.prototype.slice.call(

        document.querySelectorAll("li")

    );

    list.forEach(item => {

        item.style.transform = "translate(0, 0)";

    });

};

// 用于控制滑动到鼠标移动位置的函数

const changePosition = (node, relativeX = 0, relativeY = 0) =>

node &&

      (node.style.transform = `translate(${relativeX}px, ${relativeY}px)`);

// ondragstart中的event获取拖动元素的位置

con.ondragstart = function(event) {

    const target = event.target;

    startX = event.clientX;

    startY = event.clientY;

};

// ondragend 拖动元素目前到达的位置

// 通过event.clientX和event.clientY可以获取鼠标松掉的位置

con.ondragend = event => {

    const node = event.target;

    const init = lists.find(item => item.index === +node.dataset.index);

    changePosition(

        node,

        event.clientX - init.offsetX,

        event.clientY - init.offsetY

    );

};

关键点

  • 使用dragstart获取元素的初始移动的位置

  • 使用dragend获取元素最终拖动到鼠标落点的位置(用于后续动画移动)

  • 这里注意的是clientX和clientY是元素相对视口中的位置,所以需要在元素原有位置的基础上进行适当的长度补偿即可

回到之前的例子

HTML

<ul class="container">

    <li class="item" draggable="true">1</li>

    <li class="item" draggable="true">2</li>

    <li class="item" draggable="true">3</li>

    <li class="item" draggable="true">4</li>

    <li class="item" draggable="true">5</li>

    <li class="item" draggable="true">6</li>

    <li class="item" draggable="true">7</li>

    <li class="item" draggable="true">8</li>

    <li class="item" draggable="true">9</li>

    <li class="item" draggable="true">10</li>

</ul>

CSS

ul {

    list-style: none;

}

.item {

    padding: 20px;

    width: 400px;

    background-color: cyan;

    color: red;

    border: 2px solid #333;

    margin: 20px 0;

    transition: transform 1s ease;

    text-align: center;

}

JS逻辑
  • 在拖动时保存需要移动元素的对象,并且保存该元素的下一个元素(之后通过insertBefore来实现元素的交换)

  • 使用dropover来更新需要与之发生交换的元素,记录下最终的交换元素

  • 在dropend中对双方元素进行交换,先通过动画实现交换特效,加一个定时器,实现元素的交换(等到动画结束实现真正dom的交换)


const con = document.querySelector(".container");

const dragObj = { nextObj: null, target: null };

const exchangeObj = { nextObj: null, target: null };

let timer;

const changePosition = (node, relativeX = 0, relativeY = 0) =>

node &&

      (node.style.transform = `translate(${relativeX}px, ${relativeY}px)`);

function inserElem(exchange, target) {

    if (target.target === exchange.target) {

        return;

    } else {

        // nextObj没有说明与最后一个元素做交换

        if (exchange.nextObj === null) {

            con.appendChild(target.target);

        } else {

            // 将现在这个元素插入到需要交换的前一个元素之前

            // 这里对于同元素的insertBefore会直接执行在原来节点内元素的删除和新节点内元素的append不需要手动操作

            con.insertBefore(target.target, exchange.nextObj);

        }

    }

}

// 记录目前正在拖动的元素

con.ondragstart = event => {

    dragObj.target = event.target;

    dragObj.nextObj = event.target.nextElementSibling;

};

con.ondragend = event => {

    if (exchangeObj.target && dragObj.target) {

        // 先执行动画效果

        changePosition(

            dragObj.target,

            0,

            exchangeObj.target.offsetTop - dragObj.target.offsetTop

        );

        changePosition(

            exchangeObj.target,

            0,

            dragObj.target.offsetTop - exchangeObj.target.offsetTop

        );

        if (timer) {

            clearTimeout(timer);

        }

   // 动画结束后 定时器执行真正的dom交换

        timer = setTimeout(() => {

            inserElem(dragObj, exchangeObj);

            inserElem(exchangeObj, dragObj);

            changePosition(dragObj.target, 0, 0);

            changePosition(exchangeObj.target, 0, 0);

        }, 1000);

    }

};

// 记录下目前划过的需要交换的元素

con.ondragover = event => {

    exchangeObj.target = event.target;

    exchangeObj.nextObj = event.target.nextElementSibling;

};

最近,疫情没办法回学校,做了一个博客自己玩玩,作为一个React菜鸡练手项目,有兴趣的可以看看my-koa-react-blog

相关文章

网友评论

    本文标题:列表拖拽转换的例子

    本文链接:https://www.haomeiwen.com/subject/rswbhhtx.html