本文介绍如何使用 JavaScript 实现可拖动的模态框,包含简单版与带样式的复杂版两种方案。核心思路是通过监听鼠标按下、移动和弹起事件,动态计算并更新模态框的偏移量。文中详细解析了 offsetWidth 只读特性、style 定位必须加单位以及事件对象参数传递等关键注意事项,帮助开发者掌握基础拖拽逻辑。
本文分享了利用 JavaScript 实现简易可拖动模态框的具体代码,供读者参考学习。
文章提供了两个版本的实现:一个是基础简易版,另一个是带有样式且逻辑稍复杂的版本。
简易版本实现
运行效果如下图所示:

核心实现逻辑如下:
首先,为模态框的可拖动区域绑定点击事件。当鼠标按下时,计算鼠标相对于该区域的坐标(通过 e.pageX 减去 box.offsetLeft),从而获取初始的 x 和 y 值。
其次,为 document 对象绑定鼠标移动事件。由于拖拽操作是在整个窗口范围内进行的,需保持鼠标与模态框的相对位置不变。因此,在移动过程中,通过当前鼠标的 e.pageX 减去初始计算的 x 值,来动态计算模态框的新位置并更新其样式。注意,此时的 e.pageX 已随鼠标移动发生变化,与按下时的值不同。
最后,当鼠标弹起时,移除鼠标移动事件监听器,结束拖拽操作。
代码编写时的关键注意点:
1. offsetWidth 等 offset 系列属性为只读属性,无法直接通过赋值修改元素尺寸。
2. 若需通过 style.top 或 style.left 控制元素位置,该元素必须已设置定位(如 absolute 或 relative),且赋值时必须包含单位 'px'。
3. mousemove 事件回调函数的参数 e 不可省略。若省略,函数将捕获外部作用域中的 e(即 mousedown 事件中的事件对象),导致获取的鼠标坐标错误。
具体代码如下:
<!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修改样式 -->
<style>
.box {
position: absolute;
height: 200px;
width: 200px;
background-color: plum;
}
.title {
width: 100%;
height: 50px;
background-color: rosybrown;
cursor: move;
/* 让文字不可选中 */
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
}
</style>
</head>
<body>
<div class="box">
<div class="title">head部分</div>
</div>
<script>
var box = document.querySelector('.box');
var title = document.querySelector('.title');
title.addEventListener('mousedown', function (e) {
//获取鼠标在box内的坐标值
var x = e.pageX - box.offsetLeft;
var y = e.pageY - box.offsetTop;
document.addEventListener('mousemove', move);
function move(e) {
box.style.left = e.pageX - x + 'px';
box.style.top = e.pageY - y + 'px';
}
document.addEventListener('mouseup', function () {
document.removeEventListener('mousemove', move);
})
})
</script>
</body>
</html>复杂版本实现
复杂版本的运行效果如下:

其核心实现思路与简易版本基本一致。
主要区别在于增加了视觉样式和更完善的交互细节。
完整实现代码如下:
<!DOCTYPE html>
<html>
<head lang="en">
<meta charset="UTF-8">
<title>拖动模态框</title>
<style>
.login-header {
width: 100%;
text-align: center;
height: 30px;
font-size: 24px;
line-height: 30px;
}
ul,
li,
ol,
dl,
dt,
dd,
div,
p,
span,
h1,
h2,
h3,
h4,
h5,
h6,
a {
padding: 0px;
margin: 0px;
}
.login {
display: none;
width: 512px;
height: 280px;
position: fixed;
border: #ebebeb solid 1px;
left: 50%;
top: 50%;
background: #ffffff;
box-shadow: 0px 0px 20px #ddd;
z-index: 9999;
transform: translate(-50%, -50%);
}
.login-title {
width: 100%;
margin: 10px 0px 0px 0px;
text-align: center;
line-height: 40px;
height: 40px;
font-size: 18px;
position: relative;
cursor: move;
}
.login-input-content {
margin-top: 20px;
}
.login-button {
width: 50%;
margin: 30px auto 0px auto;
line-height: 40px;
font-size: 14px;
border: #ebebeb 1px solid;
text-align: center;
}
.login-bg {
display: none;
width: 100%;
height: 100%;
position: fixed;
top: 0px;
left: 0px;
background: rgba(0, 0, 0, .3);
}
a {
text-decoration: none;
color: #000000;
}
.login-button a {
display: block;
}
.login-input input.list-input {
float: left;
line-height: 35px;
height: 35px;
width: 350px;
border: #ebebeb 1px solid;
text-indent: 5px;
}
.login-input {
overflow: hidden;
margin: 0px 0px 20px 0px;
}
.login-input label {
float: left;
width: 90px;
padding-right: 10px;
text-align: right;
line-height: 35px;
height: 35px;
font-size: 14px;
}
.login-title span {
position: absolute;
font-size: 12px;
right: -20px;
top: -30px;
background: #ffffff;
border: #ebebeb solid 1px;
width: 40px;
height: 40px;
border-radius: 20px;
}
</style>
</head>
<body>
<div class="login-header"><a id="link" href="javascript:;">点击,弹出登录框</a></div>
<div id="login" class="login">
<div id="title" class="login-title">登录会员
<span><a id="closeBtn" href="javascript:void(0);" class="close-login">关闭</a></span>
</div>
<div class="login-input-content">
<div class="login-input">
<label>用户名:</label>
<input type="text" placeholder="请输入用户名" name="info[username]" id="username" class="list-input">
</div>
<div class="login-input">
<label>登录密码:</label>
<input type="password" placeholder="请输入登录密码" name="info[password]" id="password" class="list-input">
</div>
</div>
<div id="loginBtn" class="login-button"><a href="javascript:void(0);" id="login-button-submit">登录会员</a></div>
</div>
<!-- 遮盖层 -->
<div id="bg" class="login-bg"></div>
<script>
// 1. 获取元素
var login = document.querySelector('.login');
var mask = document.querySelector('.login-bg');
var link = document.querySelector('#link');
var closeBtn = document.querySelector('#closeBtn');
var title = document.querySelector('#title');
// 2. 点击弹出层这个链接 link 让mask 和login 显示出来
link.addEventListener('click', function () {
mask.style.display = 'block';
login.style.display = 'block';
})
// 3. 点击 closeBtn 就隐藏 mask 和 login
closeBtn.addEventListener('click', function () {
mask.style.display = 'none';
login.style.display = 'none';
})
// 4. 开始拖拽
// (1) 当我们鼠标按下, 就获得鼠标在盒子内的坐标
title.addEventListener('mousedown', function (e) {
var x = e.pageX - login.offsetLeft;
var y = e.pageY - login.offsetTop;
// (2) 鼠标移动的时候,把鼠标在页面中的坐标,减去 鼠标在盒子内的坐标就是模态框的left和top值
document.addEventListener('mousemove', move)
function move(e) {
login.style.left = e.pageX - x + 'px';
login.style.top = e.pageY - y + 'px';
}
// (3) 鼠标弹起,就让鼠标移动事件移除
document.addEventListener('mouseup', function () {
document.removeEventListener('mousemove', move);
})
})
</script>
</body>
</html>希望以上内容能对您的学习有所帮助,欢迎继续支持本站获取更多技术分享。