场景描述

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

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

<!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>

鼠标抬起元素停止移动

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

全部代码

<!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 移动事件, onmouseup抬起事件
都必须要放置在 moveElement.onmousedown 的里面,
否者会出现 鼠标抬起后,元素仍在在移动

详细讲解原生js拖拽的更多相关文章

  1. 再谈React.js实现原生js拖拽效果

    前几天写的那个拖拽,自己留下的疑问...这次在热心博友的提示下又修正了一些小小的bug,也加了拖拽的边缘检测部分...就再聊聊拖拽吧 一.不要直接操作dom元素 react中使用了虚拟dom的概念,目 ...

  2. React.js实现原生js拖拽效果及思考

    一.起因&思路 不知不觉,已经好几天没写博客了...近来除了研究React,还做了公司官网... 一直想写一个原生js拖拽效果,又加上近来学react学得比较嗨.所以就用react来实现这个拖 ...

  3. 原生js拖拽、jQuery拖拽、vue自定义指令拖拽

    原生js拖拽: <!DOCTYPE html> <html lang="en"> <head> <meta charset="U ...

  4. 原生js拖拽功能制作滑动条实例教程

    拖拽属于前端常见的功能,很多效果都会用到js的拖拽功能.滑动条的核心功能也就是使用js拖拽滑块来修改位置.一个完整的滑动条包括 滑动条.滑动痕迹.滑块.文本 等元素,先把html代码写出来,如下所示: ...

  5. html5原生js拖拽上传(golang版)

    一次只能传一个文件,需在main.go同级目录中建一个upload文件夹 main.go package main import ( "fmt" "io" &q ...

  6. 原生js拖拽

    <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/ ...

  7. 关于 JS 拖拽功能的冲突问题及解决方法

    前言 我在之前写过关于 JS 拖拽的文章,实现方式和网上能搜到的方法大致相同,别无二致,但是在一次偶然的测试中发现,这种绑定事件的方式可能会和其它的拖拽事件产生冲突,由此产生了对于事件绑定的思考.本文 ...

  8. js拖拽效果

    <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8&quo ...

  9. js拖拽分析

    js拖拽分析 思路 1.三个鼠标事件,mousedown,mousemove,mouseup 2.可移动性absolute 3.边界限制 得到鼠标点击处和div边界的距离,然后得出top 和 left ...

  10. JS拖拽div(移动)

    <!doctype html><html><head> <meta charset="utf-8"> <title>JS ...

随机推荐

  1. PlayWright(一)

    1.如何安装? 安装playwright只需要一条命令,就是pip安装命令,命令如下: pip install playwright 注:playwright需要Python3.7或更新的版本 2.然 ...

  2. Github疯传!200本计算机经典书籍!

    好书在精不在多,每一本经典书籍都值得反复翻阅,温故而知新! 下面分享几本计算机经典书籍,都是我自己看过的. 重构 改善既有代码的设计 就像豆瓣评论所说的,看后有种醍醐灌顶.欲罢不能的感觉.无论你是初学 ...

  3. Java(循环语句,数组)

    Java循环 1.while while( 表达式 ) { //循环内容 } 2.do while do { //循环内容 }while(表达式); 3.for for(初始化; 表达式; 更新) { ...

  4. bulkWrite探秘

    MongoDB有很多有趣的内置方法,其中为了批量处理一些写入操作,并且可以按照一定顺序执行,自从3.2版本之后提供了该批量方法:bulkWrite. 它的语法很简单: db.collection.bu ...

  5. 在Istio中,到底怎么获取 Envoy 访问日志?

    Envoy 访问日志记录了通过 Envoy 进行请求 / 响应交互的相关记录,可以方便地了解具体通信过程和调试定位问题. 环境准备 部署 httpbin 服务: kubectl apply -f sa ...

  6. Solon 成为信通院可信开源社区、可信开源项目

    自2021年9月17日成立以来,可信开源社区共同体共有五批新成员加入.在4月21日"OSCAR开源生态建设论坛"上,可信开源社区共同体又迎来2位正式成员和6位预备成员,Solon ...

  7. 1. Mybatis 简介

    1. Mybatis历史 MyBatis最初是Apache的一个开源项目iBatis, 2010年6月这个项目由Apache Software Foundation迁移到了Google Code.随着 ...

  8. ASIC加速技术在ASIC加速性能优化中的新应用与挑战

    目录 1. 引言 2. 技术原理及概念 3. 实现步骤与流程 4. 应用示例与代码实现讲解 5. 优化与改进 1. 引言 随着计算机技术的发展,芯片的性能和面积都得到了极大的提升.为了进一步提高芯片的 ...

  9. 使用部分写时复制提升Lakehouse的 ACID Upserts性能

    使用部分写时复制提升Lakehouse的 ACID Upserts性能 译自:Fast Copy-On-Write within Apache Parquet for Data Lakehouse A ...

  10. n皇后问题的分析和实现

    N皇后问题的分析和实现 1.实现要求 2.代码实现 1.实现要求 在n*n的方格棋中,放置n个皇后,要求每个皇后不同行,不同列,不同对角线 以行为依据,遍历行,判断行对应的列是否符合要求 判定要求: ...