# 拖拽列表顺序

利用拖拽API实现列表顺序的改变。如下图

主要思路,一个值是鼠标的Event.clientY属性,还有就是利用getBoundingClientRect 获取每个子元素的topheight值。

剩下的就是比较当前拖拽的元素应该在第一位,中间位,还是最后一位。同时添加部分引导动画即可。

# PC端使用drag实现

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>通过拖拽实现列表顺序变化</title>
    <style>
      * {
        padding: 0;
        margin: 0;
        box-sizing: border-box;
      }
      .container {
        width: 500px;
        border: 1px solid #ccc;
        padding: 8px;
        margin: 100px 0 0 50px;
        list-style: none;
      }
      .container li {
        height: 41px;
        border: 1px solid #ccc;
        font-size: 14px;
        line-height: 40px;
        margin-bottom: 4px;
        transition: all 0.1s linear;
      }
    </style>
  </head>
  <body>
    <ul class="container">
      <!-- <li></li> -->
    </ul>
    <button id="add">append</button>
    <h4>list 结果</h4>
    <p id="list"></p>
    <script>
      let list = [{ item: 1 }, { item: 2 }, { item: 3 }, { item: 4 }, { item: 5 }, { item: 6 }];
      let liList = [];
      let appendToFirst = false;
      let appendToLast = false;
      let insertBeforeReferenceNode = null;

      const container = document.querySelector('.container');
      const listResultEl = document.querySelector('#list');
      initList(list);

      let dragTarget = null;
      container.addEventListener('dragstart', e => {
        const target = findDraggableParent(e.target);
        if (target) {
          dragTarget = target;
        }
      });
      container.addEventListener('dragend', e => {
        dragEndHandler(e);
      });
      container.addEventListener('dragover', e => {
        e.preventDefault();
        overHandler(e);
      });
      container.addEventListener('dragenter', e => {
        e.preventDefault();
        e.stopPropagation();
      });
      container.addEventListener('dragleave', e => {
        e.preventDefault();
        e.stopPropagation();
      });

      container.addEventListener('drop', e => {});

      let timer = null;
      function overHandler(e) {
        // 判断应该放入那里
        // 判断鼠标最终的落点, 是在各个子元素的哪个位置
        /*
            主要是是三个位置
            1、顶部
            2、中间
            3、底部
            */
        for (let i = 0, len = liList.length; i < len; i++) {
          const liEl = liList[i];
          const { top, height } = liEl.getBoundingClientRect();
          const clientY = e.clientY;
          if (i === 0) {
            // console.log(clientY, top + height / 2);
            if (clientY <= top + height / 2) {
              // 说明需要插入到最上面
              console.log('top');
              appendToFirst = true;
              appendToLast = false;
              setActiveElStyle(liEl);
              insertBeforeReferenceNode = liEl;
              break;
            }
          }
          if (i === len - 1) {
            if (clientY > top + height / 2) {
              // 说明需要插入到底部
              // dragTarget.remove();
              // container.appendChild(dragTarget);
              appendToFirst = false;
              appendToLast = true;
              setActiveElStyle(liEl);
              insertBeforeReferenceNode = null;
              break;
            }
          }

          const nextLiEl = liList[i + 1];
          if (liEl && nextLiEl) {
            const { top: nextTop, height: nextHeight } = nextLiEl.getBoundingClientRect();
            const middleLine = top + height / 2;
            const nextMiddleLine = nextTop + nextHeight / 2;
            if (middleLine < clientY && clientY <= nextMiddleLine) {
              // 说明在这两个元素中间
              // dragTarget.remove();
              // container.insertBefore(dragTarget, nextLiEl);
              appendToFirst = false;
              appendToLast = false;
              insertBeforeReferenceNode = nextLiEl;
              setActiveElStyle(liEl);
            }
          }
        }
      }

      function dragEndHandler(e) {
        // 阻止一些默认事件, 比如图片预览,打开链接等功能
        e.preventDefault();

        setTimeout(() => {
          /* dragTarget 表示当前拖动的是哪个元素 */
          const target = e.target; // 表示当前放置的元素
          if (!target) return;
          /* if (target === dragTarget) {
          return;
        } */

          console.log('dragTarget', dragTarget);
          console.log('insertBeforeReferenceNode', insertBeforeReferenceNode);
          if (dragTarget === insertBeforeReferenceNode) {
            resetActiveElStyle();
            return;
          }
          if (appendToLast) {
            dragTarget.remove();
            container.appendChild(dragTarget);
          } else {
            dragTarget.remove();
            container.insertBefore(dragTarget, insertBeforeReferenceNode);
          }

          liList = container.querySelectorAll('li.drag-item');

          getNewList();
          resetActiveElStyle();
        });
      }

      function initList(list) {
        clearChild(container);
        const fragment = document.createDocumentFragment();
        list.forEach((item, index) => {
          const li = document.createElement('li');
          li.textContent = JSON.stringify(item);
          li.setAttribute('draggable', 'true');
          li.setAttribute('data-index', index);
          li.classList.add('drag-item');
          fragment.appendChild(li);
        });
        container.appendChild(fragment);
        liList = container.querySelectorAll('li.drag-item');
      }
      function clearChild(el) {
        while (el.firstChild) {
          el.firstChild.remove();
        }
      }

      function findDraggableParent(
        el,
        predicate = el => {
          try {
            return el.getAttribute('draggable') === 'true';
          } catch (error) {
            return false;
          }
        }
      ) {
        if (!el) return null;
        let target = el;
        while (target) {
          if (predicate(target)) {
            break;
          } else {
            target = target.parentNode;
          }
        }
        return target;
      }

      function getNewList() {
        const result = [];
        for (let i = 0, len = liList.length; i < len; i++) {
          const index = liList[i].dataset.index - 0;
          result.push(list[index]);
        }
        listResultEl.textContent = result.map(_ => _.item).join(',');
        return result;
      }

      function setActiveElStyle(el) {
        resetActiveElStyle();
        if (appendToFirst) {
          el.style.marginTop = '40px';
        } else {
          el.style.marginBottom = '40px';
        }
      }
      function resetActiveElStyle() {
        for (let i = 0, len = liList.length; i < len; i++) {
          const el = liList[i];
          el.style.borderColor = '#ccc';
          el.style.borderTopColor = '#ccc';
          el.style.borderBottomColor = '#ccc';
          el.style.marginBottom = '4px';
          el.style.marginTop = '0';
        }
      }

      const add = document.querySelector('#add');
      add.addEventListener('click', () => {
        list.push({ item: list.length + 1 });
        initList(list);

        list = getNewList();
      });
    </script>
  </body>
</html>

# 移动端使用touch实现

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>drag3通过拖拽实现列表顺序变化</title>
    <style>
      * {
        padding: 0;
        margin: 0;
        box-sizing: border-box;
      }
      .container {
        width: 100%;
        max-width: 600px;
        /* padding: 100px 0; */
        /* border: 1px solid #ccc; */
        list-style: none;
        position: relative;
        margin-top: 100px;
      }
      .child-item {
        height: 41px;
        border: 1px solid #ccc;
        font-size: 14px;
        line-height: 40px;
        margin-bottom: 4px;
        transition: margin 0.1s linear;
        width: 100%;
      }
    </style>
  </head>
  <body>
    <ul class="container">
      <!-- <li></li> -->
    </ul>
    <!-- <button id="add">append</button>
    <h4>list 结果</h4>
    <p id="list"></p> -->
    <script>
      let list = [{ item: 1 }, { item: 2 }, { item: 3 }, { item: 4 }, { item: 5 }, { item: 6 }];
      let liList = [];
      let appendToFirst = false;
      let appendToLast = false;
      let insertBeforeReferenceNode = null;
      let zIndex = 10;
      let disX = 0,
        disY = 0;

      const body = document.body;
      const container = document.querySelector('.container');
      const listResultEl = document.querySelector('#list');
      initList(list);

      const { left: containerLeft, top: containerTop } = container.getBoundingClientRect();

      let dragTarget = null;
      let dragTargetCloned = null;
      container.addEventListener('touchstart', e => {
        e.preventDefault();

        const target = findDraggableParent(e.target);
        if (target) {
          dragTarget = target;
          dragTargetCloned = dragTarget.cloneNode(true);
          const touch = e.touches[0];
          const { clientX, clientY } = touch;
          const { top, left } = dragTarget.getBoundingClientRect();
          disX = left - clientX;
          disY = top - clientY;
          container.appendChild(dragTargetCloned);
          setPositionAbsolute(dragTargetCloned);
          //   moveHandler(e);
        }
      });
      container.addEventListener('touchmove', moveHandler);

      container.addEventListener('touchend', touchEndHandler);

      function setPositionAbsolute(el) {
        const { top, left } = el.getBoundingClientRect();
        el.style.position = 'absolute';
        el.style.left = left - containerLeft + 'px';
        el.style.top = top - containerTop + 'px';
        el.style.zIndex = zIndex++;
      }
      let removeFlag = true;
      function moveHandler(event) {
        event.preventDefault();
        const e = event.touches[0];

        const { clientX, clientY } = e;

        if (dragTargetCloned) {
          dragTargetCloned.style.left = clientX + disX - containerLeft + 'px';
          dragTargetCloned.style.top = clientY + +disY - containerTop + 'px';
        }

        // return;
        // 判断应该放入那里
        // 判断鼠标最终的落点, 是在各个子元素的哪个位置
        /*
              主要是是三个位置
              1、顶部
              2、中间
              3、底部
              */
        // const liList2 = [...liList].filter(el => el !== dragTarget);

        for (let i = 0, len = liList.length; i < len; i++) {
          const liEl = liList[i];
          const { top, height } = liEl.getBoundingClientRect();
          const clientY = e.clientY;
          if (i === 0) {
            if (clientY <= top + height / 2) {
              // 说明需要插入到最上面
              appendToFirst = true;
              appendToLast = false;
              setActiveElStyle(liEl);
              insertBeforeReferenceNode = liEl;
              break;
            }
          }
          if (i === len - 1) {
            if (clientY > top + height / 2) {
              // 说明需要插入到底部
              // container.appendChild(dragTarget);
              appendToFirst = false;
              appendToLast = true;
              setActiveElStyle(liEl);
              insertBeforeReferenceNode = null;
              break;
            }
          }

          const nextLiEl = liList[i + 1];
          if (liEl && nextLiEl) {
            const { top: nextTop, height: nextHeight } = nextLiEl.getBoundingClientRect();
            const middleLine = top + height / 2;
            const nextMiddleLine = nextTop + nextHeight / 2;
            if (middleLine < clientY && clientY <= nextMiddleLine) {
              // 说明在这两个元素中间
              // container.insertBefore(dragTarget, nextLiEl);
              appendToFirst = false;
              appendToLast = false;
              insertBeforeReferenceNode = nextLiEl;
              setActiveElStyle(liEl);
            }
          }
        }
      }

      function touchEndHandler(e) {
        // 阻止一些默认事件, 比如图片预览,打开链接等功能
        e.preventDefault();
        removeFlag = true;

        setTimeout(() => {
          /* dragTarget 表示当前拖动的是哪个元素 */
          //   const target = e.target; // 表示当前放置的元素
          //   if (!target) return;
          /* if (target === dragTarget) {
            return;
          } */

          console.log('dragTarget', dragTarget);
          console.log('insertBeforeReferenceNode', insertBeforeReferenceNode);
          if (dragTarget === insertBeforeReferenceNode) {
            dragTarget.style.position = 'relative';
            dragTarget.style.left = '0';
            dragTarget.style.top = '0';
            dragTargetCloned.remove();
            resetActiveElStyle();
            return;
          }
          if (appendToLast) {
            dragTarget.remove();
            container.appendChild(dragTarget);
          } else {
            dragTarget.remove();
            container.insertBefore(dragTarget, insertBeforeReferenceNode);
          }
          dragTarget.style.position = 'relative';
          dragTarget.style.left = '0';
          dragTarget.style.top = '0';
          dragTargetCloned.remove();

          liList = container.querySelectorAll('li.child-item');

          getNewList();
          resetActiveElStyle();
        });
      }

      function initList(list) {
        clearChild(container);
        const fragment = document.createDocumentFragment();
        list.forEach((item, index) => {
          const li = document.createElement('li');
          li.textContent = JSON.stringify(item);
          //   li.setAttribute('draggable', 'true');
          li.setAttribute('data-item', '1');
          li.setAttribute('data-index', index);
          li.classList.add('child-item');
          li.style.position = 'relative';
          li.style.zIndex = zIndex++;
          fragment.appendChild(li);
        });
        container.appendChild(fragment);
        liList = container.querySelectorAll('li.child-item');
      }
      function clearChild(el) {
        while (el.firstChild) {
          el.firstChild.remove();
        }
      }

      function findDraggableParent(
        el,
        predicate = el => {
          try {
            return el.dataset.item === '1';
          } catch (error) {
            return false;
          }
        }
      ) {
        if (!el) return null;
        let target = el;
        while (target) {
          if (predicate(target)) {
            break;
          } else {
            target = target.parentNode;
          }
        }
        return target;
      }

      function getNewList() {
        const result = [];
        for (let i = 0, len = liList.length; i < len; i++) {
          const index = liList[i].dataset.index - 0;
          result.push(list[index]);
        }
        // listResultEl.textContent = result.map(_ => _.item).join(',');
        return result;
      }

      function setActiveElStyle(el) {
        resetActiveElStyle();
        if (appendToFirst) {
          el.style.marginTop = '40px';
        } else {
          el.style.marginBottom = '40px';
        }
      }
      function resetActiveElStyle() {
        for (let i = 0, len = liList.length; i < len; i++) {
          const el = liList[i];
          el.style.borderColor = '#ccc';
          el.style.borderTopColor = '#ccc';
          el.style.borderBottomColor = '#ccc';
          el.style.marginBottom = '4px';
          el.style.marginTop = '0';
        }
      }

      /* const add = document.querySelector('#add');
      add.addEventListener('click', () => {
        list.push({ item: list.length + 1 });
        initList(list);

        list = getNewList();
      }); */
    </script>
  </body>
</html>

上次更新: 1/22/2025, 9:39:13 AM