• 详细讲解原生js拖拽


    场景描述

    今天遇见一个问题,那就是产品希望在弹出来的窗口。
    可以移动这个弹窗的位置
    增加用户体验,我们直接使用的element-ui中的 Dialog 对话框
    我们现在需要拖拽标题,移动元素位置
    

    元素拖拽的思路

    要让元素按下移动,我们需要实现以下几个步骤:
    1.鼠标按下元素跟随光标移动
    2.鼠标抬起元素停止移动
    3.移动的区域进行限制(只能在屏幕可视区域内移动-不能产生滚动条)
    4.鼠标抬起之后,移除移动和抬起事件
    5.处理抬起事件偶尔不会被触发呢?
    

    拖拽的核心示意图以及用到的方法

    offsetX:设置或获取鼠标指针位置相对于触发事件对象的x坐标。
    offsetY:设置或获取鼠标指针位置相对于触发事件对象的y坐标。
    
    clientX 获取鼠标相对于浏览器左上角x轴的坐标;
    clientY 获取鼠标相对于浏览器左上角y轴的坐标;
    
    window.innerWidth:表示窗口视图区的大小(即视口(viewport)大小而非浏览器窗口大小)
    window.innerHeight
    window.innerHeight和innerWidth的大小会根据具体显示器和具体浏览器放大缩小所变化。
    它的值仅仅只是当前浏览器窗口大小所对应的宽高,单位是px(像素)。
    
    offsetWidth:返回盒子模型的宽度(包括width+左右padding+左右border)
    offsetHeight:
    

    鼠标按下元素跟随光标移动

    html>
    <html lang="en">
    <head>
      <meta charset="UTF-8">
      <meta name="viewport" content="width=device-width, initial-scale=1.0">
      <title>Documenttitle>
      <style>
        *{
          padding: 0px;
          margin: 0px;
        }
        .box{
          width: 300px;
          height: 30px;
          background: pink;
          position: absolute;
          left: 0;
          top: 0;
        }
      style>
    head>
    <body>
      <div class="box" id="moveElement">我可以移动div>
      <script>
        window.onload = function(){
          // 获取元素节点
          let moveElement = document.getElementById('moveElement');
          // 给元素注册鼠标按下事件
          moveElement.onmousedown = function(e){
            //兼容  e || window.event  现在都可以
            let event = e || window.event;  
            // 获取鼠标按下去的那一个点距离边框顶部和左侧的距离
            let point_x=event.offsetX;
            let point_y=event.offsetY;
            //  鼠标移动(小方块在文档上移动,给文档注册一个是移动事件)
            document.onmousemove = function(ent){
              let evt = ent || window.event;
              // 获取鼠标移动的坐标位置
              let ele_left= evt.clientX - point_x;
              let ele_top= evt.clientY - point_y;
              // 将移动的新的坐标位置进行赋值
              moveElement.style.left = ele_left + 'px';
              moveElement.style.top = ele_top + 'px'
            }
          }
        }
      script>
    body>
    html>
    

    鼠标抬起元素停止移动

    鼠标抬起停止移动的思路是:
    我们可以在全局设置一个开关,let flag = false;
    当鼠标按下的时候设置为  true
    moveElement.onmousedown = function(e){
      flag = true
    }
    
    当鼠标抬起的时候设置 flase
    document.onmouseup = function(event){
      flag= false
    }
    
    在对移动元素赋值的时候,需要进行判断;
    只有鼠标按下的状态才可以移动
    if(flag){
      // 将移动的新的坐标位置进行赋值
      moveElement.style.left = ele_left + 'px';
      moveElement.style.top = ele_top + 'px'
    }
    

    为什么我们要对元素移动的区域进行限制了

    为什么我们需要对元素的移动区域进行限制?
    如果不进行限制。
    元素拖拽的时候会产生滚条。用户体验不好。
    

    移动的区域进行限制(不能产生滚动条)

    我们来分析一下:
    元素在水平反向的取值:最小值0(元素在最左侧);
    最大值元素(屏幕窗体innerWidth - 元素宽度offsetWidth);
    此时元素紧靠最右侧。这样元素就不会产生滚条。
    
    在垂直方向上的取值:最小值是0(元素在最顶部);
    最大值元素(屏幕窗体innerHeight - 元素宽度offsetHeight);
    此时元素紧靠最底侧。这样元素就不会产生滚条。
    
     // 给元素注册鼠标按下事件
    moveElement.onmousedown = function(e){
      flag = true
      //兼容  e || window.event  现在都可以
      let event = e || window.event;  
      // 获取鼠标按下去的那一个点距离边框顶部和左侧的距离
      let point_x=event.offsetX;
      let point_y=event.offsetY;
      //  鼠标移动(小方块在文档上移动,给文档注册一个是移动事件)
      document.onmousemove = function(ent){
        let evt = ent || window.event;
        // 获取鼠标移动的坐标位置
        let ele_left= evt.clientX - point_x;
        let ele_top= evt.clientY - point_y;
    
        if(ele_left<=0){
          // 设置水平方向的最小值
          ele_left = 0
        }else if(ele_left >= window.innerWidth - moveElement.offsetWidth){
          // 设置水平方向的最大值
          ele_left = window.innerWidth - moveElement.offsetWidth
        }
    
        if(ele_top<=0){
          // 设置垂直方向的最小值
          ele_top = 0
        }else if(ele_top >= window.innerHeight - moveElement.offsetHeight){
          // 设置垂直方向的最大值
          ele_top = window.innerHeight - moveElement.offsetHeight
        }
    
        // 只有鼠标按下的状态才可以移动
        if(flag){
            // 将移动的新的坐标位置进行赋值
          moveElement.style.left = ele_left + 'px';
          moveElement.style.top = ele_top + 'px'
        }
      }
    }
    

    优化 移动的区域进行限制的代码

    我们发现
    if(ele_left<=0){
      ele_left = 0
    }else if(ele_left >= window.innerWidth - moveElement.offsetWidth){
      ele_left = window.innerWidth - moveElement.offsetWidth
    }
    if(ele_top<=0){
      ele_top = 0
    }else if(ele_top >= window.innerHeight - moveElement.offsetHeight){
      ele_top = window.innerHeight - moveElement.offsetHeight
    }
    这一部分的代码太冗余了,我们可以进行优化一下:
    我们发现:
    在最最左侧(产生滚东条)的时候元素的坐标位置可能是负数,我们要取最大值。
    在最最右侧的时候元素的坐标可能会大于
    window.innerWidth - moveElement.offsetWidth
    因此我哦们取最小值
    垂直方向上同理:我们可以优化为如下
    ele_left = Math.min(Math.max(0,ele_left), window.innerWidth - moveElement.offsetWidth)
    ele_top=  Math.min(Math.max(0,ele_top),  window.innerHeight - moveElement.offsetHeight)
    

    有bug:鼠标抬起之后,会触发 document.onmousemove事件

    我们发现一个问题,在鼠标抬起之后
    document.onmousemove = function(ent){}
    之中的代码仍然在执行。
    为什么鼠标抬起之后仍然会触发 document.onmousemove 之中的代码呢?
    因为:在一开始,按下抬起移动事件结束注册后(鼠标抬起,移动事件就应该销毁)
    但是我们并没有做销毁处理。
    

    鼠标抬起之后,移除移动事件与抬起事件

    // 抬起停止移动
    document.onmouseup = function(event){
      // 移除移动和抬起事件
      this.onmousemove = null; 
      this.onmouseup = null
    }
    
    这样一来我们就不需要flag这个开关了。
    就可以删除开关部分的代码了
    

    又又出现bug:有些时候抬起事件没有被触发

    有些时候抬起事件不会被触发:
    鼠标按下左键移动,与此同时按下右键移动元素后。
    然后左右键松开,这个时候会出现菜单点击空白处。
    在这个过程中你会出现(鼠标选中了其他的元素,或者元素中的文字)
    这个时候元素就100%不会触发mouseup事件。
    

    为什么选择文字抬起事件就不会被触发呢?

    其实抬起事件 mouseup 并没有失效,
    而是我们拖动时,鼠标选中了其他的元素或者选中了选中元素中的文字。
    这个时候鼠标即使松开。
    浏览器内部还是认为用户在复制文字,鼠标还是按下的状态,所以不会触发mouseup事件。
    我们知道了为什么不会触发 mouseup 解决办法就非常简单了。
    第一种方式:使用css 禁用选中文字
    第二种方式:ondragstart 和 ondragend 来处理
    

    第1种方式:css 禁用选中处理抬起事件偶尔不会触发

    .box{
      width: 300px;
      height: 30px;
      background: pink;
      position: absolute;
      left: 0;
      top: 0;
    
      -webkit-user-select: none;
      -moz-user-select: none;
      -o-user-select: none;
      user-select: none;
    }
    

    第2种方式: ondragstart 和 ondragend 来处理

    // 解决有些时候,在鼠标松开的时候,元素仍然可以拖动;
    document.ondragstart = function(ev) {
      ev.preventDefault();
    }
    document.ondragend = function(ev) {
      ev.preventDefault();
    }
    

    全部代码

    html>
    <html lang="en">
    <head>
      <meta charset="UTF-8">
      <meta name="viewport" content="width=device-width, initial-scale=1.0">
      <title>Documenttitle>
      <style>
        *{
          padding: 0px;
          margin: 0px;
        }
        .box{
          width: 300px;
          height: 30px;
          background: pink;
          position: absolute;
          left: 0;
          top: 0;
        }
        .box:hover{
          cursor: move;
        }
      style>
    head>
    <body>
      <div class="box" id="moveElement">我可以移动div>
      <script>
        window.onload = function(){
          // 获取元素节点
          let moveElement = document.getElementById('moveElement');
          // 给元素注册鼠标按下事件
          moveElement.onmousedown = function(e){
            //兼容  e || window.event  现在都可以
            let event = e || window.event;  
            // 获取鼠标按下去的那一个点距离边框顶部和左侧的距离
            let point_x=event.offsetX;
            let point_y=event.offsetY;
            //  鼠标移动(小方块在文档上移动,给文档注册一个是移动事件)
            document.onmousemove = function(ent){
              let evt = ent || window.event;
              // 获取鼠标移动的坐标位置
              let ele_left= evt.clientX - point_x;
              let ele_top= evt.clientY - point_y;
    
              // ----冗余代码---
              // if(ele_left<=0){
              //   // 设置水平方向的最小值
              //   ele_left = 0
              // }else if(ele_left >= window.innerWidth - moveElement.offsetWidth){
              //   // 设置水平方向的最大值
              //   ele_left = window.innerWidth - moveElement.offsetWidth
              // }
              // if(ele_top<=0){
              //   // 设置垂直方向的最小值
              //   ele_top = 0
              // }else if(ele_top >= window.innerHeight - moveElement.offsetHeight){
              //   // 设置垂直方向的最大值
              //   ele_top = window.innerHeight - moveElement.offsetHeight
              // }
              // 优化为下面的
              ele_left = Math.min(Math.max(0,ele_left), window.innerWidth - moveElement.offsetWidth)
              ele_top=  Math.min(Math.max(0,ele_top),  window.innerHeight - moveElement.offsetHeight)
              
              moveElement.style.left = ele_left + 'px';
              moveElement.style.top = ele_top + 'px'
            }
    
            // 抬起停止移动
            document.onmouseup = function(event){
              console.log("抬起停止移动" )
              // 移除移动和抬起事件
              this.onmouseup = null;
              this.onmousemove = null;
              //修复低版本的ie可能出现的bug
              if(typeof moveElement.releaseCapture!='undefined'){  
                moveElement.releaseCapture();  
              }  
            }
            // 解决有些时候,在鼠标松开的时候,元素仍然可以拖动-使用的是第二种方式
            document.ondragstart = function(ev) {
              ev.preventDefault();
            }
            document.ondragend = function(ev) {
              ev.preventDefault();
            }
          }
        }
      script>
    body>
    html>
    

    需要注意的是:

    我们的onmousemove 移动事件, onmouseup抬起事件
    都必须要放置在 moveElement.onmousedown 的里面,
    否者会出现 鼠标抬起后,元素仍在在移动
    
  • 相关阅读:
    LeetCode 面试题 08.06. 汉诺塔问题
    一探究竟:为什么需要 JVM?它处在什么位置?
    “新KG”视点 | 知识图谱与大语言模型协同模式探究
    BGP(Border Gateway Protocol)
    遗传算法GA求解非连续函数问题
    【逆向】(c++)分析pe结构,拉伸pe结构,缩小pe结构
    halcon程序如何导出C#文件
    ROS知识点——生成点云,发布、订阅ROS点云话题
    python3:split()分割字符串为字符/字符串列表 2023-11-20
    C++异常处理和断言
  • 原文地址:https://www.cnblogs.com/IwishIcould/p/17626217.html