首页 > 其他分享 >详细讲解原生js拖拽

详细讲解原生js拖拽

时间:2023-11-01 11:35:54浏览次数:29  
标签:原生 移动 鼠标 top ele window js 拖拽 moveElement

场景描述
今天遇见一个问题,那就是产品希望在弹出来的窗口。
可以移动这个弹窗的位置
增加用户体验,我们直接使用的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:

详细讲解原生js拖拽_鼠标移动

鼠标按下元素跟随光标移动
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
  <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>

详细讲解原生js拖拽_鼠标移动_02

鼠标抬起元素停止移动
鼠标抬起停止移动的思路是:
我们可以在全局设置一个开关,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'
}

详细讲解原生js拖拽_鼠标移动_03

为什么我们要对元素移动的区域进行限制了
为什么我们需要对元素的移动区域进行限制?
如果不进行限制。
元素拖拽的时候会产生滚条。用户体验不好。
移动的区域进行限制(不能产生滚动条)
我们来分析一下:
元素在水平反向的取值:最小值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'
    }
  }
}

详细讲解原生js拖拽_Math_04

优化 移动的区域进行限制的代码
我们发现
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)

详细讲解原生js拖拽_鼠标移动_05

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

详细讲解原生js拖拽_Math_06

鼠标抬起之后,移除移动事件与抬起事件
// 抬起停止移动
document.onmouseup = function(event){
  // 移除移动和抬起事件
  this.onmousemove = null; 
  this.onmouseup = null
}

这样一来我们就不需要flag这个开关了。
就可以删除开关部分的代码了

详细讲解原生js拖拽_Math_07

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

详细讲解原生js拖拽_Math_08

为什么选择文字抬起事件就不会被触发呢?
其实抬起事件 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();
}
全部代码
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
  <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 移动事件, onm ouseup抬起事件
都必须要放置在 moveElement.onmousedown 的里面,
否者会出现 鼠标抬起后,元素仍在在移动

遇见问题,这是你成长的机会,如果你能够解决,这就是收获。


作者:晚来南风晚相识


标签:原生,移动,鼠标,top,ele,window,js,拖拽,moveElement
From: https://blog.51cto.com/u_12553473/8120202

相关文章

  • 【2023-11-01】一款基于 pdf.js 的 PDF 批注注释插件库(纯JS、高亮、画笔、多边形、历
    基于纯JavaScript和PDF.js做的一款PDF批注拓展插件-PDFMaster,一款仍能兼容支持IE11的PDF批注插件,界面美观功能强大,有无开发经验都可以快速简单快速使用。Demo及源码Demo和源码地址:https://demos.libertynlp.com功能演示视频:https://www.bilibili.com/video/BV12C4y1n7TL......
  • vuejs3.0 从入门到精通——初始化项目——路由
    路由 VueRouter是Vue.js官方的路由管理器,它和Vue.js深度集成,用于构建单页面应用。Vue.js单页面应用是基于路由和组件映射的,路由用于配置访问路径,将组件(components)映射到路由路径(routes)。一、路由模式 VueRouter常用的两种模式是hash模式和HTML5模式,对应的创建......
  • umijs4框架别名配置错误问题及解决方法
    umijs3中别名配置都是在config下文件下的config.ts中配置,或者在.umirc.ts使用如下方式来配置://引入import{resolve}from'path';  resolve:{  alias:{   '@/src':resolve(__dirname,'../src'),   '@/static':resolve(__dirname,&#......
  • 使用原生js 写的picker 效果
    classPicker{ DEFAULT_DURATION=200; MIN_DISTANCE=10; DEMO_DATA=[]; //demo数据 //惯性滑动思路: //在手指离开屏幕时,如果和上一次move时的间隔小于`MOMENTUM_LIMIT_TIME`且move //距离大于`MOMENTUM_LIMIT_DISTANCE`时,执行惯性滑动......
  • 【闭包应用】JS:防抖、节流
    1、防抖:当进行连续操作时,只执行最后一次的操作。//防抖的概念是当进行连续操作时,只执行最后一次的操作。functiondebounce(fn,delayTime){lettimeout=null;returnfunction(){if(timeout){clearTimeout(timeout);......
  • 软件测试|Python对JSON的解析和创建详解
    简介JSON(JavaScriptObjectNotation)是一种轻量级的数据交换格式,已经成为当今互联网应用中广泛使用的数据格式之一。Python提供了内置的模块来解析和创建JSON数据,使得在Python中处理JSON变得非常简单。本文将详细介绍Python对JSON的解析和创建过程,并提供示例代码来帮助大家更好......
  • js中字符串使用单引号还是双引号
    ES6如下描述:字符串静态字符串一律使用单引号或反引号,不使用双引号。动态字符串使用反引号。//badconsta="foobar";constb='foo'+a+'bar';//acceptableconstc=`foobar`;//goodconsta='foobar';constb=`foo${a}bar`;......
  • Node.js子进程:你想要知道的一切
    如何使用spawn(),exec(),execFile()和fork()对于单进程而言,Node.js的单线程和非阻塞特性表现地非常好。然而,对于处理功能越来越复杂的应用程序而言,一个单进程的CPU是远远无法满足需要的。无论你的服务器有多强大,单线程都是远远不够用的。事实上,Node.js的单线程特性......
  • SIP UserAgent (B2BUA client)——pjsip
    1.sipstackspjsip/bell-sip/sofia-sip/libeXosip/librehttps://github.com/staskobzar/sip_stacks_examples 2.sipuseragentandservernetworkarchitecture3. InstallingpjsiponUbuntuhttps://www.pjsip.orgsudoapt-getinstalllibasound2-devLinuxsys......
  • js实现web端批量下载
    使用场景:比如一个展现pdf文档列表的网页内需要允许客户自己勾选(可多选)需要打包下载的内容,最终以zip文件的形式在浏览器下载此功能也可在服务端实现,但是在web端实现可以降低服务器负载,提升客户体验。下面是代码<!DOCTYPEhtml><html><headlang="en"><metacharset="UTF-8">......