位置: 文档库 > JavaScript > JS实现左边列表移到到右边列表功能

JS实现左边列表移到到右边列表功能

F4 上传于 2020-08-19 08:36

YPE html>

《JS实现左边列表移到到右边列表功能》

在Web开发中,列表项的动态移动是常见的交互需求,例如将左侧可选列表中的项目移动到右侧已选列表。这种功能常见于权限管理、商品选择、多选表单等场景。本文将详细介绍如何使用纯JavaScript实现这一功能,涵盖基础实现、优化交互、数据同步及兼容性处理,帮助开发者快速掌握核心技巧。

一、功能需求分析

典型的左右列表移动功能包含以下要素:

  • 左侧列表:显示可选项目(如未分配的用户)
  • 右侧列表:显示已选项目(如已分配的用户)
  • 控制按钮:包括“移到右侧”“移到左侧”“全部移动”等
  • 数据同步:实时更新列表数据,避免重复或遗漏

核心难点在于如何高效操作DOM元素,同时维护数据的一致性。接下来将从基础实现开始逐步优化。

二、基础HTML结构

首先构建静态HTML结构,包含两个列表容器和操作按钮:


可选列表

  • 项目1
  • 项目2
  • 项目3

已选列表

    关键点:

    • 使用data-id属性存储唯一标识,便于数据操作
    • 列表项使用
    • 标签,便于批量选择
    • 按钮分组布局,提升用户体验

    三、基础JavaScript实现

    1. 获取DOM元素

    
    const leftList = document.getElementById('leftList');
    const rightList = document.getElementById('rightList');
    const moveRightBtn = document.getElementById('moveRight');
    const moveAllRightBtn = document.getElementById('moveAllRight');
    const moveLeftBtn = document.getElementById('moveLeft');
    const moveAllLeftBtn = document.getElementById('moveAllLeft');
    

    2. 单个项目移动(右移)

    
    function moveSelectedItems(sourceList, targetList) {
      const selectedItems = sourceList.querySelectorAll('li.selected');
      if (selectedItems.length === 0) return;
    
      // 移动DOM元素
      selectedItems.forEach(item => {
        targetList.appendChild(item);
        item.classList.remove('selected');
      });
    
      // 更新数据(示例:假设有全局数据数组)
      updateDataArrays();
    }
    
    // 绑定右移按钮事件
    moveRightBtn.addEventListener('click', () => {
      moveSelectedItems(leftList, rightList);
    });
    

    3. 全部项目移动(右移)

    
    function moveAllItems(sourceList, targetList) {
      const items = sourceList.querySelectorAll('li');
      items.forEach(item => {
        targetList.appendChild(item);
      });
      updateDataArrays();
    }
    
    moveAllRightBtn.addEventListener('click', () => {
      moveAllItems(leftList, rightList);
    });
    

    4. 列表项选择功能

    
    // 为左侧列表添加点击选择效果
    leftList.addEventListener('click', (e) => {
      if (e.target.tagName === 'LI') {
        e.target.classList.toggle('selected');
      }
    });
    
    // 右侧列表同理(如需选择)
    rightList.addEventListener('click', (e) => {
      if (e.target.tagName === 'LI') {
        e.target.classList.toggle('selected');
      }
    });
    

    四、完整功能实现

    整合上述代码,并添加数据同步逻辑:

    
    // 模拟数据存储
    let leftData = [
      { id: 1, text: '项目1' },
      { id: 2, text: '项目2' },
      { id: 3, text: '项目3' }
    ];
    let rightData = [];
    
    // 初始化渲染
    function renderLists() {
      leftList.innerHTML = leftData.map(item => 
        `
  • ${item.text}
  • ` ).join(''); rightList.innerHTML = rightData.map(item => `
  • ${item.text}
  • ` ).join(''); } // 更新数据数组 function updateDataArrays() { // 获取左侧剩余项目 const leftItems = Array.from(leftList.querySelectorAll('li')).map(li => ({ id: parseInt(li.dataset.id), text: li.textContent })); // 获取右侧项目 const rightItems = Array.from(rightList.querySelectorAll('li')).map(li => ({ id: parseInt(li.dataset.id), text: li.textContent })); leftData = leftItems; rightData = rightItems; } // 移动选中项目(通用函数) function moveSelected(sourceList, targetList, isRightMove) { const list = isRightMove ? leftList : rightList; const target = isRightMove ? rightList : leftList; const selectedItems = list.querySelectorAll('li.selected'); if (selectedItems.length === 0) return; // 移动DOM selectedItems.forEach(item => { target.appendChild(item); item.classList.remove('selected'); }); updateDataArrays(); } // 事件绑定 document.addEventListener('DOMContentLoaded', () => { renderLists(); // 单个右移 moveRightBtn.addEventListener('click', () => { moveSelected(leftList, rightList, true); }); // 单个左移 moveLeftBtn.addEventListener('click', () => { moveSelected(rightList, leftList, false); }); // 全部右移 moveAllRightBtn.addEventListener('click', () => { moveAllItems(leftList, rightList); }); // 全部左移 moveAllLeftBtn.addEventListener('click', () => { moveAllItems(rightList, leftList); }); // 选择效果 [leftList, rightList].forEach(list => { list.addEventListener('click', (e) => { if (e.target.tagName === 'LI') { e.target.classList.toggle('selected'); } }); }); });

    五、功能优化

    1. 添加CSS样式提升交互体验

    
    .list-container {
      float: left;
      width: 45%;
      margin: 0 2%;
    }
    .list {
      min-height: 200px;
      border: 1px solid #ddd;
      padding: 10px;
    }
    .list li {
      padding: 8px;
      margin: 5px 0;
      background: #f5f5f5;
      cursor: pointer;
    }
    .list li.selected {
      background: #e0e0ff;
    }
    .button-group {
      float: left;
      width: 5%;
      text-align: center;
      padding: 80px 0;
    }
    .button-group button {
      display: block;
      margin: 10px auto;
      width: 60px;
    }
    

    2. 防止重复移动

    
    function moveSelectedSafe(sourceList, targetList, isRightMove) {
      const list = isRightMove ? leftList : rightList;
      const target = isRightMove ? rightList : leftList;
      const selectedItems = Array.from(list.querySelectorAll('li.selected'));
      
      // 检查目标列表是否已存在相同项目
      const targetIds = Array.from(target.querySelectorAll('li')).map(li => li.dataset.id);
      const movableItems = selectedItems.filter(item => !targetIds.includes(item.dataset.id));
      
      movableItems.forEach(item => {
        target.appendChild(item);
        item.classList.remove('selected');
      });
    
      updateDataArrays();
    }
    

    3. 添加动画效果

    
    // 使用CSS过渡效果
    .list li {
      transition: all 0.3s ease;
    }
    .list li.moving {
      transform: translateX(100px);
      opacity: 0.5;
    }
    
    // JavaScript添加动画类
    function moveWithAnimation(item, targetList) {
      item.classList.add('moving');
      setTimeout(() => {
        targetList.appendChild(item);
        item.classList.remove('moving');
      }, 300);
    }
    

    六、高级功能扩展

    1. 搜索过滤功能

    
    function addSearch() {
      const searchInput = document.createElement('input');
      searchInput.type = 'text';
      searchInput.placeholder = '搜索...';
      
      // 左侧搜索
      const leftSearch = searchInput.cloneNode();
      leftSearch.addEventListener('input', (e) => {
        const term = e.target.value.toLowerCase();
        Array.from(leftList.querySelectorAll('li')).forEach(li => {
          const text = li.textContent.toLowerCase();
          li.style.display = text.includes(term) ? '' : 'none';
        });
      });
      
      document.querySelector('.left').prepend(leftSearch);
      
      // 右侧同理
    }
    

    2. 拖拽排序功能

    
    function enableDragSort(list) {
      list.querySelectorAll('li').forEach(item => {
        item.draggable = true;
        
        item.addEventListener('dragstart', (e) => {
          e.dataTransfer.setData('text/plain', item.dataset.id);
          setTimeout(() => item.classList.add('dragging'), 0);
        });
        
        item.addEventListener('dragend', () => {
          item.classList.remove('dragging');
        });
      });
      
      list.addEventListener('dragover', (e) => {
        e.preventDefault();
        const draggingItem = document.querySelector('.dragging');
        const afterElement = getDragAfterElement(list, e.clientY);
        
        if (afterElement == null) {
          list.appendChild(draggingItem);
        } else {
          list.insertBefore(draggingItem, afterElement);
        }
      });
    }
    
    function getDragAfterElement(container, y) {
      const draggableElements = [...container.querySelectorAll('li:not(.dragging)')];
      return draggableElements.reduce((closest, child) => {
        const box = child.getBoundingClientRect();
        const offset = y - box.top - box.height / 2;
        if (offset  closest.offset) {
          return { offset: offset, element: child };
        } else {
          return closest;
        }
      }, { offset: Number.NEGATIVE_INFINITY }).element;
    }
    

    七、兼容性处理

    1. 旧版浏览器支持

    
    // 检查classList支持
    if (!('classList' in document.createElement('div'))) {
      // 使用className替代
      Element.prototype.classList = {
        _list: [],
        add(cls) {
          if (!this._list.includes(cls)) {
            this._list.push(cls);
            this.className = this._list.join(' ');
          }
        },
        remove(cls) {
          this._list = this._list.filter(c => c !== cls);
          this.className = this._list.join(' ');
        }
      };
    }
    
    // 检查dataset支持
    if (!('dataset' in document.createElement('div'))) {
      // 使用getAttribute/setAttribute替代
      Document.prototype.getDataId = function(el) {
        return el.getAttribute('data-id');
      };
    }
    

    2. 移动端触摸支持

    
    function addTouchSupport() {
      let touchStartX = 0;
      let touchStartY = 0;
      
      [leftList, rightList].forEach(list => {
        list.addEventListener('touchstart', (e) => {
          touchStartX = e.touches[0].clientX;
          touchStartY = e.touches[0].clientY;
          const target = e.target;
          if (target.tagName === 'LI') {
            target.classList.add('touch-selected');
          }
        }, { passive: true });
        
        list.addEventListener('touchend', (e) => {
          const target = e.target;
          if (target.tagName === 'LI') {
            target.classList.remove('touch-selected');
            const touchEndX = e.changedTouches[0].clientX;
            const touchEndY = e.changedTouches[0].clientY;
            
            // 左滑右滑判断
            const diffX = touchStartX - touchEndX;
            const diffY = touchStartY - touchEndY;
            
            if (Math.abs(diffX) > Math.abs(diffY) && Math.abs(diffX) > 50) {
              if (diffX > 0) {
                // 左滑 - 移到右侧
                if (list === leftList) {
                  moveSelectedSafe(leftList, rightList, true);
                }
              } else {
                // 右滑 - 移到左侧
                if (list === rightList) {
                  moveSelectedSafe(rightList, leftList, false);
                }
              }
            }
          }
        }, { passive: true });
      });
    }
    

    八、完整示例代码

    
      
      左右列表移动功能
      
    
    
      

    可选列表

      已选列表

        九、总结与最佳实践

        1. 数据与视图分离:始终维护独立的数据数组,避免直接操作DOM导致数据不同步

        2. 性能优化:对于大型列表,使用文档片段(DocumentFragment)批量更新DOM

        3. 可访问性:为列表项添加ARIA属性,支持键盘导航

        
        // 添加ARIA支持示例
        function addAriaSupport() {
          const lists = [leftList, rightList];
          lists.forEach(list => {
            list.setAttribute('role', 'listbox');
            Array.from(list.querySelectorAll('li')).forEach(li => {
              li.setAttribute('role', 'option');
              li.setAttribute('tabindex', '0');
              li.addEventListener('keydown', (e) => {
                if (e.key === 'Enter') {
                  li.classList.toggle('selected');
                }
              });
            });
          });
        }
        

        4. 模块化设计:将功能拆分为独立模块,便于复用和维护

        关键词:JavaScript列表移动、DOM操作、数据同步、拖拽排序、触摸交互、可访问性、模块化开发

        简介:本文详细介绍了如何使用纯JavaScript实现左右列表项移动功能,涵盖基础实现、数据同步、拖拽排序、触摸支持等核心技巧,提供了完整的代码示例和优化方案,适合Web开发者学习和应用。