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

无法移除JavaScript元素事件监听器问题求助

问题原因分析

你遇到的核心问题是:draggableProgressBar是一个高阶函数,每次调用draggableProgressBar(el)都会返回一个全新的函数实例。这就导致添加监听时用的是一个函数,移除时调用同样方法又生成了另一个完全不同的函数引用——removeEventListener只能移除与添加时完全相同的函数引用,所以自然失效。

解决方案:存储函数引用

要解决这个问题,关键是把每个元素对应的mousemove处理函数保存下来,确保移除时用的是同一个引用。下面是两种可行的实现方式:

方式一:利用元素自定义属性存储

修改代码如下:

const videoTimeline = document.querySelectorAll(".video-timeline");

// 调整变量名避免参数混淆:el是timeline元素,e是mousemove事件对象
const draggableProgressBar = el => function(e) {
    const container = el.closest(".container");
    const video = container.querySelector("video");
    const timelineWidth = el.clientWidth;
    
    container.querySelector(".progress-bar").style.width = `${e.offsetX}px`;
    video.currentTime = (e.offsetX / timelineWidth) * video.duration;
    container.querySelector(".current-time").innerText = formatTime(video.currentTime);
};

videoTimeline.forEach(el => {
    el.addEventListener("mousedown", () => {
        // 生成处理函数并存储到元素的自定义属性中
        const handler = draggableProgressBar(el);
        el.dataset.dragHandler = handler;
        el.addEventListener("mousemove", handler);
    });
});

document.addEventListener("mouseup", () => {
    videoTimeline.forEach(el => {
        // 取出存储的函数引用
        const handler = el.dataset.dragHandler;
        if (handler) {
            el.removeEventListener("mousemove", handler);
            // 移除后清空存储,避免内存泄漏
            delete el.dataset.dragHandler;
        }
    });
});

方式二:用Map存储元素与函数的对应关系

如果不想修改元素属性,可以用Map来管理引用:

const videoTimeline = document.querySelectorAll(".video-timeline");
const handlerMap = new Map(); // 存储元素对应的处理函数

const draggableProgressBar = el => function(e) {
    const container = el.closest(".container");
    const video = container.querySelector("video");
    const timelineWidth = el.clientWidth;
    
    container.querySelector(".progress-bar").style.width = `${e.offsetX}px`;
    video.currentTime = (e.offsetX / timelineWidth) * video.duration;
    container.querySelector(".current-time").innerText = formatTime(video.currentTime);
};

videoTimeline.forEach(el => {
    el.addEventListener("mousedown", () => {
        const handler = draggableProgressBar(el);
        handlerMap.set(el, handler);
        el.addEventListener("mousemove", handler);
    });
});

document.addEventListener("mouseup", () => {
    videoTimeline.forEach(el => {
        const handler = handlerMap.get(el);
        if (handler) {
            el.removeEventListener("mousemove", handler);
            handlerMap.delete(el);
        }
    });
});
额外优化建议
  • 多次调用querySelector会重复DOM查询,建议提前获取容器内的元素(比如在mousedown时一次性获取),提升性能。
  • 变量名尽量清晰,原代码中draggableProgressBar的参数e其实是DOM元素,容易和事件对象混淆,上面的代码已经调整为el,避免歧义。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.16 23:13:27