Vue自定义指令实现内容区拖拽调整大小

 更新时间:2025年12月23日 09:33:10   作者:酸菜土狗  
日常开发中经常遇到需要手动调整内容区大小的场景,比如侧边栏、弹窗、报表面板等,下面我们就来看看Vue如何通过自定义指令实现内容区拖拽调整大小吧

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

先看效果

核心代码解析

指令文件 directives/resizable-full.js ,关键部分:

1. 指令钩子:初始化 + 更新 + 清理

Vue 指令的 3 个核心钩子,保证指令的生命周期完整:

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 件事:创建拖拽手柄、写拖拽逻辑。

创建拖拽手柄

我只保留了「右下角」的拖拽手柄(其他方向注释掉了,需要的话自己解开),样式可自定义:

// 定义手柄配置(只留了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); // 存起来方便后续清理
});

拖拽核心逻辑

分 3 步:按下鼠标(记录初始状态)→ 移动鼠标(计算新尺寸)→ 松开鼠标(触发回调 + 清理):

// 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 负责移除所有手柄元素和事件监听器,指令解绑时必执行:

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;
  }
}

如何使用

1.全局注册指令(main.js):

import resizableFull from './directives/resizable-full';
Vue.directive('resizable-full', resizableFull);

2.页面中使用

<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 提示(比如 “拖拽调整大小”);

支持拖拽时实时触发回调(不止结束时);

自定义手柄样式(比如改成虚线、加图标)。

总结

这个自定义指令核心是「创建拖拽手柄 + 监听鼠标事件 + 计算尺寸变化」,逻辑不复杂,可以根据自己的业务场景定制。亲测报表和弹窗都很适用~

以上就是Vue自定义指令实现内容区拖拽调整大小的详细内容,更多关于Vue拖拽调整内容区大小的资料请关注脚本之家其它相关文章!

相关文章

  • vue实现在线学生录入系统

    vue实现在线学生录入系统

    这篇文章主要为大家详细介绍了vue实现在线学生录入系统,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2020-05-05
  • Vue el-table实现右键菜单功能

    Vue el-table实现右键菜单功能

    这篇文章主要为大家详细介绍了Vue el-table实现右键菜单功能,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2022-04-04
  • vue项目通过a标签下载图片至zip包的示例代码

    vue项目通过a标签下载图片至zip包的示例代码

    在vue项目中,将图片下载可使用流的形式,下载成单个图片,或者将多个图片下载至zip包,本文就是介绍使用a标签下载图片的用法,文中有详细的代码示例供大家参考,具有一定的参考价值,需要的朋友可以参考下
    2023-10-10
  • vue修改this.$confirm的文字样式、自定义样式代码实例

    vue修改this.$confirm的文字样式、自定义样式代码实例

    this.$confirm是一个 Vue.js 中的弹窗组件,其样式可以通过 CSS 进行自定义,下面这篇文章主要给大家介绍了关于vue修改this.$confirm的文字样式、自定义样式的相关资料,需要的朋友可以参考下
    2024-02-02
  • vue-cli 3.x配置跨域代理的实现方法

    vue-cli 3.x配置跨域代理的实现方法

    这篇文章主要介绍了vue-cli 3.x配置跨域代理的实现方法,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2019-04-04
  • Vue实现星级评价效果实例详解

    Vue实现星级评价效果实例详解

    这篇文章主要介绍了Vue实现星级评价效果的实例代码,代码简单易懂,非常不错,具有一定的参考借鉴价值,需要的朋友可以参考下
    2019-12-12
  • 关于vue-admin-element中的动态加载路由

    关于vue-admin-element中的动态加载路由

    这篇文章主要介绍了关于vue-admin-element的动态加载路由,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2022-08-08
  • vue-cli使用stimulsoft.reports.js的详细教程

    vue-cli使用stimulsoft.reports.js的详细教程

    Stimulsoft Reports.JS是一个使用JavaScript和HTML5生成报表的平台。它拥有所有拥来设计,编辑和查看报表的必需组件。该报表工具根据开发人员数量授权而不是根据应用程序的用户数量。接下来通过本文给大家介绍vue-cli使用stimulsoft.reports.js的方法,一起看看吧
    2021-12-12
  • element-ui 中的table的列隐藏问题解决

    element-ui 中的table的列隐藏问题解决

    这篇文章主要介绍了element-ui 中的table的列隐藏问题解决,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2018-08-08
  • vue如何限制只能输入正负数及小数

    vue如何限制只能输入正负数及小数

    这篇文章主要介绍了vue如何限制只能输入正负数及小数,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2019-07-07

最新评论