
本文详解如何在不依赖 jquery 的前提下,使用纯 javascript 同时实现 dom 元素的自由拖拽(draggable)与右下角缩放(resizable),重点解决“全区域拖拽手柄与 resize 句柄冲突”的核心问题。
本文详解如何在不依赖 jquery 的前提下,使用纯 javascript 同时实现 dom 元素的自由拖拽(draggable)与右下角缩放(resizable),重点解决“全区域拖拽手柄与 resize 句柄冲突”的核心问题。
在原生 JavaScript 中实现可拖拽(draggable)且可缩放(resizable)的
以下是一个生产就绪的轻量级类 Movable(已优化命名与逻辑,兼容现代浏览器):
class Movable {
constructor(element, options = {}) {
this.el = element;
this.handle = options.handle || element;
this.callback = options.callback || {};
this.isDragging = false;
// 绑定事件
this.handle.addEventListener('mousedown', this.onMouseDown.bind(this));
document.addEventListener('mouseup', this.onMouseUp.bind(this));
}
onMouseDown(e) {
// 阻止文本选中,提升拖拽体验
e.preventDefault();
// 判断是否点击在 resize 句柄区域(如右下角 20×20 像素)
const rect = this.el.getBoundingClientRect();
const xInEl = e.clientX - rect.left;
const yInEl = e.clientY - rect.top;
const handleSize = 20; // resize 句柄尺寸(可配置)
const isResizeCorner = (
xInEl >= rect.width - handleSize &&
yInEl >= rect.height - handleSize
);
if (isResizeCorner) return; // 不触发拖拽,留给 resize 逻辑处理
this.isDragging = true;
this.offsetX = e.clientX - rect.left;
this.offsetY = e.clientY - rect.top;
this.el.style.position = 'absolute';
this.el.style.zIndex = '1000';
if (this.callback.start) this.callback.start(e, this.el);
else this.el.classList.add('moving');
document.addEventListener('mousemove', this.onMouseMove.bind(this));
}
onMouseMove(e) {
if (!this.isDragging) return;
const viewWidth = window.innerWidth || document.documentElement.clientWidth;
const viewHeight = window.innerHeight || document.documentElement.clientHeight;
const elRect = this.el.getBoundingClientRect();
let left = e.clientX - this.offsetX;
let top = e.clientY - this.offsetY;
// 边界约束:禁止拖出视口
left = Math.max(0, Math.min(left, viewWidth - elRect.width));
top = Math.max(0, Math.min(top, viewHeight - elRect.height));
this.el.style.left = `${left}px`;
this.el.style.top = `${top}px`;
if (this.callback.move) this.callback.move(e, this.el);
}
onMouseUp() {
if (!this.isDragging) return;
this.isDragging = false;
document.removeEventListener('mousemove', this.onMouseMove.bind(this));
if (this.callback.end) this.callback.end(event, this.el);
else this.el.classList.remove('moving');
}
}
使用示例(HTML + CSS):
<style>
.box {
position: relative;
width: 200px;
height: 150px;
padding: 12px;
background: #374151;
color: white;
border: 2px solid #1f2937;
border-radius: 4px;
resize: both; /* 原生缩放支持(仅限 overflow:auto) */
overflow: auto;
user-select: none;
}
.box .header {
background: #dc2626;
padding: 6px 12px;
margin: -12px -12px 12px -12px;
cursor: move;
font-size: 0.9em;
font-weight: 600;
}
.moving { opacity: 0.85; }
</style><!-- 案例1:带 header 的可拖拽+缩放 --><div class="box">
<div class="header">拖我移动 →</div>
<p>内容区域可自由缩放(右下角拉伸)。</p><div class="aritcle_card flexRow artxards">
<div class="artcardd flexRow">
<a class="aritcle_card_img" rel="nofollow" href="/xiazai/skill8775" title="Java Maven Common"><img
src="https://img.php.cn/upload/skill/000/000/081/179222954019613.jpg" alt="Java Maven Common" onerror="this.onerror='';this.src='/static/lhimages/moren/morentu.png'" ></a>
<div class="aritcle_card_info flexColumn">
<a rel="nofollow" href="/xiazai/skill8775" title="Java Maven Common" class="overflowclass">Java Maven Common</a>
<p class="overflowclass">Java Maven 项目审查工作流的通用输入处理。当 Java Maven 项目以 ZIP压缩包或 GitLab 仓库 URL 形式提供且需要...时使用。</p>
</div>
<a rel="nofollow" href="/xiazai/skill8775" title="Java Maven Common" class="aritcle_card_btn flexRow flexcenter"><b></b><span>下载</span>
</a>
</div>
</div>
</div>
<!-- 案例2:无 header —— 推荐仍设语义化 handle -->
<div class="box" id="freeBox">
<p>全区域拖拽易与缩放冲突,不推荐。</p>
</div>
// 初始化:指定 header 为拖拽手柄
new Movable(document.querySelector('.box'), {
handle: document.querySelector('.header'),
callback: {
start: (e, el) => console.log('drag started'),
move: (e, el) => {},
end: (e, el) => el.classList.remove('moving')
}
});
// 若必须全区域拖拽,可在 onMouseDown 中增强判断(如排除 resize 角落)
new Movable(document.getElementById('freeBox'), {
handle: document.getElementById('freeBox')
});
✅ 关键要点总结:
- ✅ 永远优先使用语义化 handle(如 .header)而非整个元素作为拖拽目标,这是解决冲突最直观、最可靠的方式;
- ✅ resize: both 需配合 overflow: auto|hidden 才生效,且仅支持右下角缩放(浏览器原生行为);
- ✅ 拖拽过程中务必添加 user-select: none 和 z-index 提升,防止误选文本或被遮挡;
- ⚠️ 注意:getBoundingClientRect() 返回的是相对于视口的坐标,计算偏移时需严格对应 clientX/Y;
- ? 避免在 mousemove 中频繁读写 offsetLeft/Top(性能差),统一用 style.left/top 控制位置;
- ? 如需支持触摸设备,可扩展 touchstart/touchmove/touchend 事件,逻辑完全对称。
该方案零依赖、体积小、可扩展性强,已通过 Chrome/Firefox/Safari 测试,适用于构建仪表盘、弹窗管理器、画布组件等需要灵活布局控制的场景。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南










