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

拖拽复位后Div意外跳至鼠标位置的问题排查与修复咨询

拖拽复位后Div意外跳至鼠标位置的问题排查与修复咨询

我想在HTML中模拟手机端拖拽删除/复位的交互效果:用户拖拽蓝色的time_slot Div,若拖拽距离未达屏幕宽度的75%,Div会通过动画复位到初始位置;若超过则删除Div。目前大部分逻辑正常,但Div完成复位动画后,会突然跳转到当前鼠标位置,即使isDown已经在mouseup事件中设为false。

问题复现代码

HTML

<body style="margin: 0;">
    <div id="BUS_auto_refresh_setting_area" class="BUS_auto_refresh_setting_area_style">
        <div id="BUS_auto_refresh_setting_sub_area"  class="BUS_auto_refresh_setting_sub_area_style">
            <div style="width: 100%; height: 100%; border: 2px solid red;">
                <div id="BUS_auto_refresh_setting_header_region" >
                    <div style="font-size: 18px; text-align: center;" class="prevent-select"> This is a test.</div>
                </div>
                
                <div id="time_slot_region" class="time_slot_style">

                   
                </div>
            </div>
        </div>
    </div>
  </body>

CSS

*
{
    box-sizing: border-box;
    font-family: Calibri;
}

#BUS_auto_refresh_setting_sub_area::-webkit-scrollbar 
{
    display: none;
}

#BUS_auto_refresh_setting_sub_area
{
    -ms-overflow-style: none;  /* IE and Edge */
    scrollbar-width: none;  /* Firefox */
}

.time_slot
{
    margin-left: 0px;
    width: 80%;
    height: 50px;
    background-color:   #0077ff;
    border: 2px solid grey;
    border-radius: 15px;
}

.prevent-select 
{
    -webkit-user-select: none;
    -ms-user-select: none; 
    user-select: none; 
}

.BUS_auto_refresh_setting_area_style
{
    display: flex; 
    flex-direction: row; 
    justify-content: center; 
    width: 100%; 
    height: 100%; top:0px; margin-right:auto; margin-left:auto; overflow: auto; background-color: rgb(0,0,0);background-color: rgba(0,0,0,0.6); z-index: 6; overflow-x: hidden;
}
.BUS_auto_refresh_setting_sub_area_style
{
    display: flex; flex-direction: row; width: 80%; height: 100%; position: relative; background-color: #fefefe;
}

.time_slot_style
{
    display: flex; flex-direction: column; justify-content: flex-start; align-items: center;
}

JavaScript

var w = window.innerWidth;
var h = window.innerHeight;


document.getElementById("BUS_auto_refresh_setting_header_region").style.height = h * 0.1 + "px";
document.getElementById("BUS_auto_refresh_setting_header_region").style.top = 0 + "px";
document.getElementById("BUS_auto_refresh_setting_area").style.height = h + "px";

function create_a_new_time_slot_div()
{
    var mousePosition;
    var offset = [0,0];
    var div;
    var isDown = false;
    var is_mousse_click_before = false;
    var x_displacement;

    div = document.createElement("div");
    div.style.position = "absolute";
    //div.style.left = "0px";
    
    var base_height = document.getElementById("BUS_auto_refresh_setting_header_region").style.height.substring(0, document.getElementById("BUS_auto_refresh_setting_header_region").style.height.length - 2);

    div.style.top = (document.getElementById("time_slot_region").childNodes.length) * base_height + "px";
    //console.log(base_height);

    var id_name = "time_slot_" + (document.getElementById("time_slot_region").childNodes.length);
    div.id = id_name;
    div.style.left = "0px";

    div.setAttribute("class", "time_slot");

    document.getElementById("time_slot_region").appendChild(div);


    div.addEventListener('mousedown', function(e) 
    {
        isDown = true;
        offset = 
        [
            div.offsetLeft - e.clientX,
            div.offsetTop - e.clientY
        ];
        
        document.addEventListener('mousemove', function(event) 
        {
            console.log(isDown);
            
            //event.preventDefault();
            if (isDown) 
            {
                console.log("Moving");
                mousePosition = 
                {
                    x : event.clientX,
                    y : event.clientY
                };
                x_displacement = (mousePosition.x + offset[0])
                console.log("x_displacement is " + x_displacement);
                div.style.left = x_displacement + 'px';
            }
        }, true);
        
    }, true);

    document.addEventListener('mouseup', function(event) 
    {
        isDown = false;
        mouseFinalPosition = 
        {
            x : event.clientX,
            y : event.clientY
        };
        
        
        if (w * -0.75 < x_displacement && x_displacement <  w * 0.75)
        {
            console.log("Return");
            var loading = document.getElementById(id_name);
            var keyFrames = document.createElement("style");

            
            keyFrames.innerHTML = `@keyframes another_loading 
            {
                from 
                {
                    left: ` + x_displacement + `px;
                }
                to 
                {
                    left: 0px;
                }
            }

            .time_slot 
            {
                animation: another_loading 0.5s 1 linear forwards;
            }`;
            
            loading.appendChild(keyFrames);
        }
        else
        {
            div.remove();
        }
    }, true);


    document.getElementById(id_name).addEventListener("animationend", function()
    {
        
        document.getElementById(id_name).removeChild(document.getElementById(id_name).lastElementChild);
        console.log("Now cancelled");
    });
}

create_a_new_time_slot_div();

问题根源分析

  1. 事件绑定与移除不匹配:你在mousedown中给document添加了匿名的mousemove事件监听器,但后续尝试移除时,又创建了一个新的匿名函数——匿名函数无法被正确移除(内存中是两个不同的函数引用),导致mousemove事件一直存在。当动画结束后,若鼠标仍在移动,可能会触发事件并覆盖动画设置的样式。

  2. 动画与内联样式的冲突:动画通过forwards关键字会把left:0px应用为内联样式,但如果残留的mousemove事件触发,会直接覆盖这个内联样式,导致Div跳走。

  3. 事件队列延迟触发:浏览器的事件队列可能存在延迟,mouseup设置isDown=false后,仍有未处理的mousemove事件在队列中,动画结束后触发就会导致异常。

修复方案

1. 命名mousemove事件处理函数,确保能正确移除

把mousemove的处理函数抽成命名函数,这样在mouseup时可以准确移除:

function create_a_new_time_slot_div()
{
    // ... 其他变量定义 ...
    var moveHandler; // 声明命名的移动处理函数

    div.addEventListener('mousedown', function(e) 
    {
        e.preventDefault(); // 阻止默认行为,避免文本选择干扰
        isDown = true;
        offset = 
        [
            div.offsetLeft - e.clientX,
            div.offsetTop - e.clientY
        ];
        
        // 命名函数,方便后续移除
        moveHandler = function(event) 
        {
            if (isDown) 
            {
                mousePosition = 
                {
                    x : event.clientX,
                    y : event.clientY
                };
                x_displacement = (mousePosition.x + offset[0]);
                div.style.left = x_displacement + 'px';
            }
        };
        document.addEventListener('mousemove', moveHandler, true);
        
    }, true);

    document.addEventListener('mouseup', function(event) 
    {
        isDown = false;
        // 立即移除mousemove事件,避免后续触发
        document.removeEventListener('mousemove', moveHandler, true);
        
        mouseFinalPosition = 
        {
            x : event.clientX,
            y : event.clientY
        };
        
        if (w * -0.75 < x_displacement && x_displacement <  w * 0.75)
        {
            // ... 原有动画代码不变 ...
        }
        else
        {
            div.remove();
        }
    }, true);

    // ... 其他代码 ...
}

2. 动画结束后强制同步内联样式

在animationend事件中手动设置left:0px,确保样式稳定:

document.getElementById(id_name).addEventListener("animationend", function()
{
    document.getElementById(id_name).removeChild(document.getElementById(id_name).lastElementChild);
    // 强制设置left为0,避免样式冲突
    this.style.left = "0px";
    console.log("Now cancelled");
});

3. 优化动画选择器,避免全局影响

原有动画用了.time_slot类选择器,会影响所有同类型Div,建议改为针对当前Div的ID选择器:

keyFrames.innerHTML = `@keyframes another_loading_${id_name} 
{
    from 
    {
        left: ` + x_displacement + `px;
    }
    to 
    {
        left: 0px;
    }
}

#${id_name}
{
    animation: another_loading_${id_name} 0.5s 1 linear forwards;
}`;

备注:内容来源于stack exchange,提问作者BikeProgram

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.04.13 20:00:31