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

拖拽元素事件监听(JS/React):类操作与监听移除问题

问题分析

你现有代码的核心问题导致类名不生效:

  1. event.target指向错误:拖拽时event.target可能是容器内的<h3>或<input>,而非目标容器,类名被错误添加到子元素上。
  2. 单元素选择限制:getElementById("draggable")只能选中第一个容器,无法适配可拖拽列表的多元素场景。
  3. 事件逻辑漏洞:dragover持续触发会重复添加类名,且未区分拖拽源与目标容器的样式逻辑。
正确实现方案

从你的代码看是React+styled-components环境,直接在组件内绑定事件更符合React开发模式,以下是完整实现:

1. React组件内事件绑定与类名操作

import { useRef, useState } from 'react';
import styled from 'styled-components';

const StyledDragableContainer = styled.div`
  padding: 1rem;
  margin: 0.5rem 0;
  border: 1px solid #eee;
  transition: all 0.2s;

  /* 拖拽中的源容器样式 */
  &.active {
    opacity: 0.5;
  }

  /* 拖拽进入的目标容器样式 */
  &.drag-over {
    background-color: #f0f8ff;
  }
`;

const DraggableList = () => {
  const dragItem = useRef(null);
  const dragOverItem = useRef(null);
  const [items, setItems] = useState([
    { id: 1, label: 'Item 1' },
    { id: 2, label: 'Item 2' },
    { id: 3, label: 'Item 3' }
  ]);

  // 拖拽开始:标记源容器,记录索引
  const handleDragStart = (e, index) => {
    dragItem.current = index;
    e.currentTarget.classList.add('active');
  };

  // 拖拽进入:标记目标容器,记录索引
  const handleDragEnter = (e, index) => {
    e.preventDefault();
    dragOverItem.current = index;
    e.currentTarget.classList.add('drag-over');
  };

  // 拖拽离开:移除目标容器标记
  const handleDragLeave = (e) => {
    e.currentTarget.classList.remove('drag-over');
  };

  // 拖拽悬停:必须阻止默认行为,否则无法触发后续事件
  const handleDragOver = (e) => {
    e.preventDefault();
  };

  // 拖拽结束:清理所有样式,执行排序
  const handleDragEnd = (e) => {
    e.currentTarget.classList.remove('active');
    // 强制清理所有残留的drag-over类
    document.querySelectorAll('.drag-over').forEach(el => el.classList.remove('drag-over'));
    
    // 执行排序逻辑
    if (dragItem.current !== null && dragOverItem.current !== null) {
      const newItems = [...items];
      const draggedItem = newItems.splice(dragItem.current, 1)[0];
      newItems.splice(dragOverItem.current, 0, draggedItem);
      setItems(newItems);
    }
    // 重置索引记录
    dragItem.current = null;
    dragOverItem.current = null;
  };

  return (
    <div>
      {items.map((item, index) => (
        <StyledDragableContainer
          key={item.id}
          draggable
          onDragStart={(e) => handleDragStart(e, index)}
          onDragEnter={(e) => handleDragEnter(e, index)}
          onDragLeave={handleDragLeave}
          onDragOver={handleDragOver}
          onDragEnd={handleDragEnd}
        >
          <h3>{item.label}</h3>
          <input type="checkbox" />
        </StyledDragableContainer>
      ))}
    </div>
  );
};

export default DraggableList;

2. 原生DOM事件监听写法(非React场景)

如果必须用原生DOM操作,需给每个可拖拽容器绑定事件,确保操作目标正确:

window.onload = function() {
  // 选中所有可拖拽容器(给容器加统一类名drag-container)
  const containers = document.querySelectorAll('.drag-container');

  // 单独定义事件函数,方便后续移除监听
  const handleDragStart = function() {
    this.classList.add('active');
  };

  const handleDragEnter = function(e) {
    e.preventDefault();
    this.classList.add('drag-over');
  };

  const handleDragLeave = function() {
    this.classList.remove('drag-over');
  };

  const handleDragOver = function(e) {
    e.preventDefault();
  };

  const handleDragEnd = function() {
    this.classList.remove('active');
    document.querySelectorAll('.drag-over').forEach(el => el.classList.remove('drag-over'));
  };

  // 给每个容器绑定事件
  containers.forEach(container => {
    container.addEventListener('dragstart', handleDragStart);
    container.addEventListener('dragenter', handleDragEnter);
    container.addEventListener('dragleave', handleDragLeave);
    container.addEventListener('dragover', handleDragOver);
    container.addEventListener('dragend', handleDragEnd);
  });

  // 移除事件监听的时机:页面卸载或容器销毁时
  window.addEventListener('beforeunload', () => {
    containers.forEach(container => {
      container.removeEventListener('dragstart', handleDragStart);
      container.removeEventListener('dragenter', handleDragEnter);
      container.removeEventListener('dragleave', handleDragLeave);
      container.removeEventListener('dragover', handleDragOver);
      container.removeEventListener('dragend', handleDragEnd);
    });
  });
};
关键注意事项
  • e.currentTarget优先使用:它永远指向绑定事件的容器元素,避免e.target指向子元素的问题。
  • dragover必须阻止默认行为:否则浏览器会禁止拖拽进入,dragenter和drop事件无法触发。
  • 事件监听移除规则:
    1. 必须使用同一个函数引用移除(匿名函数无法被移除,因为每次都是新引用)。
    2. 移除时机:React组件在useEffect清理函数中执行;原生DOM在页面卸载、容器销毁时执行,避免内存泄漏。
  • 样式强制清理:dragend时要清理所有drag-over类,避免拖拽到容器边界未触发dragleave导致样式残留。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.05 17:55:11