详细讲解原生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:

鼠标按下元素跟随光标移动
<!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拖拽的更多相关文章
- 再谈React.js实现原生js拖拽效果
前几天写的那个拖拽,自己留下的疑问...这次在热心博友的提示下又修正了一些小小的bug,也加了拖拽的边缘检测部分...就再聊聊拖拽吧 一.不要直接操作dom元素 react中使用了虚拟dom的概念,目 ...
- React.js实现原生js拖拽效果及思考
一.起因&思路 不知不觉,已经好几天没写博客了...近来除了研究React,还做了公司官网... 一直想写一个原生js拖拽效果,又加上近来学react学得比较嗨.所以就用react来实现这个拖 ...
- 原生js拖拽、jQuery拖拽、vue自定义指令拖拽
原生js拖拽: <!DOCTYPE html> <html lang="en"> <head> <meta charset="U ...
- 原生js拖拽功能制作滑动条实例教程
拖拽属于前端常见的功能,很多效果都会用到js的拖拽功能.滑动条的核心功能也就是使用js拖拽滑块来修改位置.一个完整的滑动条包括 滑动条.滑动痕迹.滑块.文本 等元素,先把html代码写出来,如下所示: ...
- html5原生js拖拽上传(golang版)
一次只能传一个文件,需在main.go同级目录中建一个upload文件夹 main.go package main import ( "fmt" "io" &q ...
- 原生js拖拽
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/ ...
- 关于 JS 拖拽功能的冲突问题及解决方法
前言 我在之前写过关于 JS 拖拽的文章,实现方式和网上能搜到的方法大致相同,别无二致,但是在一次偶然的测试中发现,这种绑定事件的方式可能会和其它的拖拽事件产生冲突,由此产生了对于事件绑定的思考.本文 ...
- js拖拽效果
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8&quo ...
- js拖拽分析
js拖拽分析 思路 1.三个鼠标事件,mousedown,mousemove,mouseup 2.可移动性absolute 3.边界限制 得到鼠标点击处和div边界的距离,然后得出top 和 left ...
- JS拖拽div(移动)
<!doctype html><html><head> <meta charset="utf-8"> <title>JS ...
随机推荐
- 2022-04-13:给你一个下标从 0 开始包含 n 个正整数的数组 arr ,和一个正整数 k 。 如果对于每个满足 k <= i <= n-1 的下标 i ,都有 arr[i-k] <= arr
2022-04-13:给你一个下标从 0 开始包含 n 个正整数的数组 arr ,和一个正整数 k . 如果对于每个满足 k <= i <= n-1 的下标 i ,都有 arr[i-k] ...
- 2022-03-26:给定一个无向图, 从任何一个点x出发,比如有一条路径: x -> a -> b -> c -> y, 这条路径上有5个点并且5个点都不一样的话,我们说(x,a,b,c,y)是一条
2022-03-26:给定一个无向图, 从任何一个点x出发,比如有一条路径: x -> a -> b -> c -> y, 这条路径上有5个点并且5个点都不一样的话,我们说(x ...
- SqliLabs 第六关 报错注入!!!
点开网址,首先看到一个页面,首先尝试闭合字符 id=1 报错 id=1' 报错 id=1"成功 然后开始爆字段?id=1" order by 3 --+ 发现有三个字段然后查询显示 ...
- WPF入门教程系列二十四——DataGrid使用示例(2)
WPF入门教程系列目录 WPF入门教程系列二--Application介绍 WPF入门教程系列三--Application介绍(续) WPF入门教程系列四--Dispatcher介绍 WPF入门教程系 ...
- Linux运维5月2号
了解安装VMware虚拟机 镜像文件 以及镜像文件安装过程中的设置 vmware安装步骤 ...
- 腾讯云 cloudbase 云开发使用笔记
产品概述 云开发(Tencent CloudBase,TCB)是腾讯云提供的云原生一体化开发环境和工具平台,为开发者提供高可用.自动弹性扩缩的后端云服务,包含计算.存储.托管等 serverless ...
- Hybrid 实验
实验拓扑 实验需求 按图示给各 PC 配置 IP 地址 PC1属于 VLAN 10 : PC2 与 PC5 属于 VLAN 20:PC4属于 VLAN 30 实现全网互通 实验步骤 1.配置链路聚合 ...
- 不成生DOM的非主流Blazor UI开源啦!
作者之前介绍了开发中的PixUI,为了适配Web应用采用了将C#通过Roslyn语法语义分析后转换为Javascript的方案,但是这样带来的问题是工程量较大,在短时间内无法达到生产级质量.因此在 ...
- 前端学习 C 语言 —— GDB调试器
GDB调试器 我们在讲指针时用 GDB 调试段错误. 本篇将详细介绍 gdb 的最常用命令.日志记录.检测点,最后介绍如何用 gdb 调试进程以及用gdb 调试一个开源项目的调试版本 -- glmar ...
- 多线程知识:三个线程如何交替打印ABC循环100次
本文博主给大家讲解一道网上非常经典的多线程面试题目.关于三个线程如何交替打印ABC循环100次的问题. 下文实现代码都基于Java代码在单个JVM内实现. 问题描述 给定三个线程,分别命名为A.B.C ...