You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

JavaScript可排序列表跨列表拖拽异常及代码优化咨询

解决方案与代码优化

一、阻止跨列表拖拽的核心修改

问题根源是未校验拖拽源列表与目标列表是否一致,导致跨列表拖拽时错误触发了目标列表的交换逻辑。只需两步修改即可解决:

  1. 记录拖拽源列表类型
    新增变量存储拖拽元素所属的列表类型,而非仅记录索引:

    let dragSource; // 存储源列表类型('left'/'right')
    let dragStartIndex;
    
    function dragStart(e) {
        const parent = this.closest('[data-index]');
        dragStartIndex = +parent.dataset.index;
        // 通过dataset直接获取源列表标识
        dragSource = parent.dataset.left ? 'left' : 'right';
    }
    
  2. 校验源与目标列表匹配性
    在dragDrop中判断,仅当源列表和目标列表一致时才执行交换:

    function dragDrop(e) {
        const targetParent = this.closest('[data-index]');
        const targetList = targetParent.dataset.left ? 'left' : 'right';
    
        // 核心判断:跨列表拖拽直接终止逻辑
        if (dragSource !== targetList) return;
    
        const dragEndIndex = +targetParent.dataset.index;
        swapItems(dragStartIndex, dragEndIndex, targetList);
    }
    

二、代码优化建议

1. 封装重复的列表创建逻辑

左右列表的生成逻辑高度冗余,封装为通用函数减少重复代码:

function createListContainer(container, items, listArray, dataAttr) {
    [...items]
        .map(item => ({ value: item, sort: Math.random() }))
        .sort((a, b) => a.sort - b.sort)
        .forEach((item, index) => {
            const element = document.createElement('div');
            element.dataset.index = index;
            element.dataset[dataAttr] = true;

            element.innerHTML = `
                <div class="draggable ${dataAttr}-draggable" draggable="true" data-${dataAttr}="true">
                    <p class="item-name">${item.value}</p>
                </div>
            `;
            container.appendChild(element);
            listArray.push(element);
        });
}

// 调用时简化为:
function createList() {
    createListContainer(leftDiv, pairs.map(p => p.pol), leftList, 'left');
    createListContainer(rightDiv, pairs.map(p => p.eng), rightList, 'right');
    addEventListener();
}

2. 使用dataset替代getAttribute

dataset是更简洁的自定义属性操作方式,替代繁琐的getAttribute/setAttribute:

  • 替换element.setAttribute('data-left', true)为element.dataset.left = true
  • 替换e.target.getAttribute('data-left')为e.target.dataset.left

3. 简化事件监听逻辑

直接在创建元素时绑定事件,避免后续重复查询DOM:

// 在createListContainer函数中,创建元素后直接绑定事件
const draggable = element.querySelector('.draggable');
draggable.addEventListener('dragstart', dragStart);
element.addEventListener('dragenter', dragEnter);
element.addEventListener('dragleave', dragLeave);
element.addEventListener('dragover', dragOver);
element.addEventListener('drop', dragDrop);

// 可删除单独的addEventListener函数,减少DOM查询开销

4. 优化交换逻辑

利用数组解构交换元素,结合DOM重排简化代码:

function swapItems(fromIndex, toIndex, listType) {
    const list = listType === 'left' ? leftList : rightList;
    // 解构交换数组元素
    [list[fromIndex], list[toIndex]] = [list[toIndex], list[fromIndex]];
    
    // 重排DOM节点,自动更新顺序
    const container = listType === 'left' ? leftDiv : rightDiv;
    container.innerHTML = '';
    list.forEach(item => container.appendChild(item));
    
    // 更新每个元素的data-index
    list.forEach((item, index) => item.dataset.index = index);
}

三、完整优化后代码

HTML

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <link rel="stylesheet" href="style.css">
</head>
<body>
    <div class="grid">
        <div class="left-side"></div>
        <div class="right-side"></div>
    </div>

    <script src="main.js"></script>
</body>
</html>

CSS

*{
    box-sizing: border-box;
    margin: 0;
    padding: 0;
}

.grid {
    display: flex;
    margin: 50px auto;
    width: 500px;
    justify-content: center;
    gap: 30px;
}

div[data-index] {
    padding: 25px;
    border: solid 2px #333;
    margin-bottom: 10px;
}

.item-name {
    font-size: 25px;
    font-family: 'Lucida Sans', 'Lucida Sans Regular', 'Lucida Grande', 'Lucida Sans Unicode', Geneva, Verdana, sans-serif;
    text-transform: uppercase;
    padding: 10px 20px;
    border: 2px solid #ccc;
    border-radius: 16px;
    text-align: center;
    cursor: pointer;
}

.draggable:active {
    cursor: grabbing;
}

JavaScript

const leftDiv = document.querySelector('.left-side')
const rightDiv = document.querySelector('.right-side')

const pairs = [
    {pol: 'jeden', eng: 'one'},
    {pol: 'dwa', eng: 'two'},
    {pol: 'trzy', eng: 'three'},
    {pol: 'cztery', eng: 'four'},
    {pol: 'piec', eng: 'five'},
]

const leftList = [];
const rightList = [];
let dragSource;
let dragStartIndex;

createList();

function createListContainer(container, items, listArray, dataAttr) {
    [...items]
        .map(item => ({ value: item, sort: Math.random() }))
        .sort((a, b) => a.sort - b.sort)
        .forEach((item, index) => {
            const element = document.createElement('div');
            element.dataset.index = index;
            element.dataset[dataAttr] = true;

            element.innerHTML = `
                <div class="draggable ${dataAttr}-draggable" draggable="true" data-${dataAttr}="true">
                    <p class="item-name">${item.value}</p>
                </div>
            `;

            // 绑定拖拽事件
            const draggable = element.querySelector('.draggable');
            draggable.addEventListener('dragstart', dragStart);
            element.addEventListener('dragenter', dragEnter);
            element.addEventListener('dragleave', dragLeave);
            element.addEventListener('dragover', dragOver);
            element.addEventListener('drop', dragDrop);

            container.appendChild(element);
            listArray.push(element);
        });
}

function createList() {
    createListContainer(leftDiv, pairs.map(p => p.pol), leftList, 'left');
    createListContainer(rightDiv, pairs.map(p => p.eng), rightList, 'right');
}

function dragStart(e) {
    const parent = this.closest('[data-index]');
    dragStartIndex = +parent.dataset.index;
    dragSource = parent.dataset.left ? 'left' : 'right';
}

function dragEnter() {}
function dragLeave() {}

function dragOver(e) {
    e.preventDefault();
}

function dragDrop(e) {
    const targetParent = this.closest('[data-index]');
    const targetList = targetParent.dataset.left ? 'left' : 'right';

    if (dragSource !== targetList) return;

    const dragEndIndex = +targetParent.dataset.index;
    swapItems(dragStartIndex, dragEndIndex, targetList);
}

function swapItems(fromIndex, toIndex, listType) {
    const list = listType === 'left' ? leftList : rightList;
    [list[fromIndex], list[toIndex]] = [list[toIndex], list[fromIndex]];
    
    const container = listType === 'left' ? leftDiv : rightDiv;
    container.innerHTML = '';
    list.forEach(item => container.appendChild(item));
    
    list.forEach((item, index) => {
        item.dataset.index = index;
    });
}

内容的提问来源于stack exchange,提问作者lukas95

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.12 15:12:13