日常开发中经常遇到需要手动调整内容区大小的场景,比如侧边栏、弹窗、报表面板等。分享一个我写的「拖拽调整大小指令」,支持自定义最小尺寸、拖拽手柄样式,能监听尺寸变化
📌 先看效果

🛠 核心代码解析
指令文件 directives/resizable-full.js ,关键部分:
1. 指令钩子:初始化 + 更新 + 清理
Vue 指令的 3 个核心钩子,保证指令的生命周期完整:
js
scss
export default {
bind(el, binding) {
// 指令绑定时初始化拖拽功能
initResizable(el, binding);
},
update(el, binding) {
// 禁用状态变化时,重新初始化
if (binding.value?.disabled !== binding.oldValue?.disabled) {
cleanupResizable(el); // 先清理旧的
initResizable(el, binding); // 再初始化新的
}
},
unbind(el) {
// 指令解绑时,清理所有手柄和事件(避免内存泄漏)
cleanupResizable(el);
}
};
2. 初始化拖拽:创建手柄 + 核心逻辑
initResizable 是核心函数,主要做 2 件事:创建拖拽手柄、写拖拽逻辑。
(1)创建拖拽手柄
我只保留了「右下角」的拖拽手柄(其他方向注释掉了,需要的话自己解开),样式可自定义:
js
ini
// 定义手柄配置(只留了bottom-right)
const handles = [
{ dir: 'bottom-right', style: { bottom: 0, right: 0, cursor: 'nwse-resize' } }
];
// 循环创建手柄元素
handles.forEach(handleConf => {
const handle = document.createElement('div');
handle.className = `resizable-handle resizable-handle--${handleConf.dir}`;
handle.dataset.dir = handleConf.dir;
// 手柄样式:小方块、半透明、hover高亮
Object.assign(handle.style, {
position: 'absolute',
width: `${handleSize}px`,
height: `${handleSize}px`,
background: handleColor,
opacity: '0.6',
zIndex: 999,
transition: 'opacity 0.2s',
...handleConf.style
});
// hover时手柄高亮
handle.addEventListener('mouseenter', () => handle.style.opacity = '1');
handle.addEventListener('mouseleave', () => handle.style.opacity = '0.6');
el.appendChild(handle); // 把手柄加到目标元素上
el._resizableConfig.handles.push(handle); // 存起来方便后续清理
});
(2)拖拽核心逻辑
分 3 步:按下鼠标(记录初始状态)→ 移动鼠标(计算新尺寸)→ 松开鼠标(触发回调 + 清理):
js
ini
// 1. 按下鼠标:记录初始位置和尺寸
const mouseDownHandler = (e) => {
const handle = e.target.closest('.resizable-handle');
if (!handle) return;
e.stopPropagation();
e.preventDefault();
const dir = handle.dataset.dir;
const rect = el.getBoundingClientRect(); // 获取元素当前位置和尺寸
// 存初始状态:鼠标位置、元素尺寸/位置
startState = {
dir,
startX: e.clientX,
startY: e.clientY,
startWidth: rect.width,
startHeight: rect.height
};
// 绑定移动/松开事件(绑在document上,避免拖拽时鼠标移出元素失效)
document.addEventListener('mousemove', onMouseMove);
document.addEventListener('mouseup', onMouseUp);
};
// 2. 移动鼠标:计算新宽高并赋值
const onMouseMove = (e) => {
if (!startState) return;
const { dir, startX, startY, startWidth, startHeight } = startState;
let newWidth = startWidth;
let newHeight = startHeight;
// 只处理右下角拖拽:宽高都增加
if (dir === 'bottom-right') {
newWidth = startWidth + (e.clientX - startX);
newHeight = startHeight + (e.clientY - startY);
}
// 限制最小宽高(避免拖到太小)
newWidth = Math.max(minWidth, newWidth);
newHeight = Math.max(minHeight, newHeight);
// 给元素设置新尺寸
el.style.width = `${newWidth}px`;
el.style.height = `${newHeight}px`;
};
// 3. 松开鼠标:触发回调+清理事件
const onMouseUp = () => {
// 拖拽结束,触发自定义回调,返回最新尺寸
if (startState && el._resizableConfig.onResize) {
el._resizableConfig.onResize({
width: parseInt(el.style.width),
height: parseInt(el.style.height)
});
}
startState = null;
// 移除事件(避免重复绑定)
document.removeEventListener('mousemove', onMouseMove);
document.removeEventListener('mouseup', onMouseUp);
};
// 给元素绑定按下事件
el.addEventListener('mousedown', mouseDownHandler);
3. 清理函数:避免内存泄漏
cleanupResizable 负责移除所有手柄元素和事件监听器,指令解绑时必执行:
js
javascript
function cleanupResizable(el) {
if (el._resizableConfig) {
// 移除所有手柄
el._resizableConfig.handles.forEach(handle => {
if (handle.parentNode === el) el.removeChild(handle);
});
// 移除所有事件监听器
el.removeEventListener('mousedown', el._resizableConfig.mouseDownHandler);
document.removeEventListener('mousemove', el._resizableConfig.mouseMoveHandler);
document.removeEventListener('mouseup', el._resizableConfig.mouseUpHandler);
// 删除配置(释放内存)
delete el._resizableConfig;
}
}
🚀 如何使用?
- 全局注册指令(main.js):
js
javascript
import resizableFull from './directives/resizable-full';
Vue.directive('resizable-full', resizableFull);
- 页面中使用:
vue
xml
<template>
<!-- 给需要拖拽的元素加指令 -->
<div
v-resizable-full="{
minWidth: 300, // 最小宽度
minHeight: 200, // 最小高度
handleSize: 10, // 手柄大小
handleColor: '#409eff', // 手柄颜色
onResize: handleResize // 拖拽结束回调
}"
style="position: relative; width: 400px; height: 300px; border: 1px solid #eee;"
>
我是可拖拽调整大小的内容区~
</div>
</template>
<script>
export default {
methods: {
// 拖拽结束,拿到最新尺寸
handleResize({ width, height }) {
console.log('新尺寸:', width, height);
}
}
};
</script>
💡 关键注意点(避坑)
- 目标元素必须设
position: relative/absolute/fixed:因为手柄是绝对定位,依赖父元素的定位; - 事件绑在 document 上:拖拽时鼠标可能移出目标元素,绑在 document 上才不会断;
- 一定要清理事件 / 元素 :指令解绑时执行
cleanupResizable,避免内存泄漏; - 最小尺寸限制 :通过
minWidth/minHeight避免元素被拖到太小,影响体验。
🎨 扩展玩法
- 解开注释的其他 7 个方向手柄,实现全方向拖拽;
- 给手柄加 hover 提示(比如 "拖拽调整大小");
- 支持拖拽时实时触发回调(不止结束时);
- 自定义手柄样式(比如改成虚线、加图标)。
📝 总结
这个自定义指令核心是「创建拖拽手柄 + 监听鼠标事件 + 计算尺寸变化」,逻辑不复杂,可以根据自己的业务场景定制。亲测报表和弹窗都很适用~
如果觉得有用,可以点个赞收藏一下,下次需要直接翻出来用😜