LOGO OA教程 ERP教程 模切知识交流 PMS教程 CRM教程 开发文档 其他文档  
 
网站管理员

【WEB开发】轻松搞定自定义对话框窗口拖拽缩放、移动

admin
2025年3月31日 18:17 本文热度 112

经常会碰到需要拖拽缩放的情况,只要有思路,实现起来会非常顺畅。
功能的核心是鼠标放在四个边和角上,拖拽把容器放大或缩小

功能演示

缩放:

移动:

演示网址:宝藏导航


缩放设计思路

  1. 使用css绘制四条边和四个角
  2. 通过css定位,控制四根线和四个角在对应的位置
  3. 监听鼠标点击和移动事件
  4. 在移动的过程中,改变容器的大小

核心设计

基础html结构

<template>
  <!-- 使用 v-if 判断是否插入到 body 中 -->
  <!-- 创建一个容器,支持拖拽,使用 ref 引用该容器 -->
  <div
    ref="draggableContainer"
    class="draggable-container"
    @mousedown="startDrag"
    :style="containerStyle"
  >
    <!-- 插槽,用户可以将其他内容插入到这个容器中 -->
    <slot></slot>

    <!-- 创建缩放控制点,每个控制点代表一个边角,使用 v-for 循环渲染 -->
    <span
      v-for="type in resizeTypes"
      :key="type"
      :class="`${type}-resize`"
      @mousedown="startResize($event, type)"
    ></span>
  </div>
</template>

基础data数据:

data: {
      // 定义可缩放的边和角的类型
      resizeTypes: ["lt", "t", "rt", "r", "rb", "b", "lb", "l"],
      // 定义容器位置和大小的响应式数据
      position: { x: this.left, y: this.top }, // 容器的位置
      size: { width: this.width, height: this.height }, // 容器的尺寸
  }

核心代码和思路

容器新宽度 = 容器初始宽度 + 鼠标移动距离

通过上面公式,我们需要记录

  1. 容器的初始宽度
  2. 鼠标移动距离 = 鼠标新位置 - 鼠标初始距离

1. 记录容器和鼠标初始状态

// 鼠标按下,开始拖拽
startResize(event) {
    // 记录鼠标初始位置
    this.originMouseX = event.clientX;
    this.originMouseY = event.clientY;

    // 记录容器初始宽高
    this.originWidth = this.size.width;
    this.originHeight = this.size.height;
},

2. 计算拖拽后新宽度

根据:容器新宽度 = 容器初始宽度 + 鼠标移动距离

拖拽容器右边:

// 计算鼠标的移动距离 deltaX
const deltaX = event.clientX - this.originMouseX; 
// 容器新宽度 = 初始宽度 + 鼠标移动距离
newWidth = this.originWidth + deltaX; 

拖拽容器右下角:
当我们拖拽容器右下角,容器的宽和高都会改变。我们需要把这个拆分成两个步骤来解决。

// 获取新宽度
const deltaX = event.clientX - this.originMouseX; 
newWidth = this.originWidth + deltaX;

// 获取新高度
const deltaY = event.clientY - this.originMouseY; // 计算鼠标的纵向位移
newHeight = this.originHeight + deltaY;

拖拽左边和上边:
拖拽左边的时候,左边的定位不能始终都是在原来的位置。
假设:
    我们的初始位置是 left: 200px。左边向左拖拽50px后,需要变为left: 150px 首先我们需要在开始的时候记录容器的初始位置

// 鼠标按下,开始拖拽
startResize(event) {
    // 记录鼠标初始位置
    this.originMouseX = event.clientX;
    this.originMouseY = event.clientY;

    // 记录容器初始宽高
    this.originWidth = this.size.width;
    this.originHeight = this.size.height;
    
    // 记录容器初始位置
    this.originContainX = this.position.x;
    this.originContainY = this.position.y;
}

改变宽高的同时,改变容器左上角的位置

// 改变高度
const deltaX = event.clientX - this.originMouseX; 
newWidth = this.originWidth - deltaX;

// 改变左边的位置
this.position.x = this.originContainX + deltaX;

3. 确定拖拽的是哪条边

我们在点击的时候会传递type,使用变量把type记录下。

<span
  v-for="type in resizeTypes"
  :key="type"
  :class="`${type}-resize`"
  @mousedown="startResize($event, type)"
></span>
// 鼠标按下,开始拖拽
startResize(event, type) {
  this.resizeType = type; // 记录拖拽的边角的类型
  ......
}

// 开始拖拽的过程中,改变容器状态
handleResize() {
const deltaX = event.clientX - this.originMouseX; // 计算鼠标的横向位移
const deltaY = event.clientY - this.originMouseY; // 计算鼠标的纵向位移

let newWidth = this.originWidth;
let newHeight = this.originHeight;

// 根据缩放类型计算新的容器尺寸
switch (this.resizeType) {
    case "lt": // 左上角
      newWidth = this.originWidth - deltaX;
      this.size.width = newWidth;
      this.position.x = this.originContainX + deltaX;
      
      newHeight = this.originHeight - deltaY;
      this.size.height = newHeight;
      this.position.y = this.originContainY + deltaY;
      break;
      
    case "t": // 上边
      newHeight = this.originHeight - deltaY;
      this.size.height = newHeight;
      this.position.y = this.originContainY + deltaY;
      break;

      右边,右下角同理......
}

4.设置最小的拖拽宽和高

如果新拖拽的宽度,已经小于最小宽度。拖拽时不进行任何改动。

switch (this.resizeType) {
    case "lt": // 左上角
      newWidth = this.originWidth - deltaX;
      newHeight = this.originHeight - deltaY;
      if (newWidth >= this.minWidth) {
        this.position.x = this.originContainX + deltaX;
        this.size.width = newWidth;
      }
      if (newHeight >= this.minHeight) {
        this.position.y = this.originContainY + deltaY;
        this.size.height = newHeight;
      }
      break;
    case "t": // 上边
      newHeight = this.originHeight - deltaY;
      if (newHeight >= this.minHeight) {
        this.position.y = this.originContainY + deltaY;
        this.size.height = newHeight;
      }
      break;
      
      右边边,右下角同理......
}

5.添加拖拽移动

拖拽移动的详细内容,笔者写的另一篇文章:拖拽移动详细思路
下面的完整代码是结合了拖拽移动和缩放整合在一起,一个较为完整的拖拽组件


完整代码

<template>
  <!-- 使用 v-if 判断是否插入到 body 中 -->
  <!-- 创建一个容器,支持拖拽,使用 ref 引用该容器 -->
  <div
    ref="draggableContainer"
    class="draggable-container"
    @mousedown="startDrag"
    :style="containerStyle"
  >
    <!-- 插槽,用户可以将其他内容插入到这个容器中 -->
    <slot></slot>

    <!-- 创建缩放控制点,每个控制点代表一个边角,使用 v-for 循环渲染 -->
    <span
      v-for="type in resizeTypes"
      :key="type"
      :class="`${type}-resize`"
      @mousedown="startResize($event, type)"
    ></span>
  </div>
</template>

<script>
export default {
  props: {
    zIndex: { type: Number, default: 1 }, // 层级,控制显示顺序
    left: { type: Number, default: 0 }, // 容器的初始 X 位置
    top: { type: Number, default: 0 }, // 容器的初始 Y 位置
    width: { type: Number, default: 300 }, // 容器的初始宽度
    height: { type: Number, default: 300 }, // 容器的初始高度
    minWidth: { type: Number, default: 100 }, // 容器的最小宽度
    minHeight: { type: Number, default: 100 }, // 容器的最小高度
  },
  data() {
    return {
      // 定义可缩放的边和角的类型
      resizeTypes: ["lt", "t", "rt", "r", "rb", "b", "lb", "l"],
      // 定义容器位置和大小的响应式数据
      position: { x: this.left, y: this.top }, // 容器的位置
      size: { width: this.width, height: this.height }, // 容器的尺寸
      originMouseX: 0, // 鼠标初始 X 坐标
      originMouseY: 0, // 鼠标初始 Y 坐标
      originContainX: 0, // 容器初始 X 坐标
      originContainY: 0, // 容器初始 Y 坐标
      originWidth: 0, // 容器初始宽度
      originHeight: 0, // 容器初始高度
      resizeType: "", // 当前缩放类型
    };
  },
  computed: {
    // 计算容器的样式
    containerStyle() {
      return {
        top: `${this.position.y}px`, // 设置容器的 top 样式
        left: `${this.position.x}px`, // 设置容器的 left 样式
        width: `${this.size.width}px`, // 设置容器的宽度
        height: `${this.size.height}px`, // 设置容器的高度
        zIndex: this.zIndex, // 设置容器的层级
      };
    },
  },
  methods: {
    /**
     * 拖拽逻辑
     */
    startDrag(event) {
      // 记录鼠标初始位置
      this.originMouseX = event.clientX;
      this.originMouseY = event.clientY;

      // 记录容器初始位置
      this.originContainX = this.position.x;
      this.originContainY = this.position.y;

      // 添加鼠标移动和鼠标松开事件监听
      document.addEventListener("mousemove", this.handleDrag);
      document.addEventListener("mouseup", this.stopDrag);
    },

    handleDrag(event) {
      this.position.x = this.originContainX + event.clientX - this.originMouseX;
      this.position.y = this.originContainY + event.clientY - this.originMouseY;
    },

    /**
     * 缩放逻辑
     */
    startResize(event, type) {
      this.resizeType = type; // 记录拖拽的边角的类型

      // 记录鼠标初始位置
      this.originMouseX = event.clientX;
      this.originMouseY = event.clientY;

      // 记录容器初始宽高
      this.originWidth = this.size.width;
      this.originHeight = this.size.height;

      // 记录容器初始位置
      this.originContainX = this.position.x;
      this.originContainY = this.position.y;
      

      event.stopPropagation(); // 阻止事件传播,防止触发拖拽

      // 添加鼠标移动和鼠标松开事件监听
      document.addEventListener("mousemove", this.handleResize);
      document.addEventListener("mouseup", this.stopDrag);
    },

    handleResize(event) {
      const deltaX = event.clientX - this.originMouseX; // 计算鼠标的横向位移
      const deltaY = event.clientY - this.originMouseY; // 计算鼠标的纵向位移

      let newWidth = this.originWidth;
      let newHeight = this.originHeight;

      // 根据缩放类型计算新的容器尺寸
      switch (this.resizeType) {
        case "lt": // 左上角
          newWidth = this.originWidth - deltaX;
          newHeight = this.originHeight - deltaY;
          if (newWidth >= this.minWidth) {
            this.position.x = this.originContainX + deltaX;
            this.size.width = newWidth;
          }
          if (newHeight >= this.minHeight) {
            this.position.y = this.originContainY + deltaY;
            this.size.height = newHeight;
          }
          break;
        case "t": // 上边
          newHeight = this.originHeight - deltaY;
          if (newHeight >= this.minHeight) {
            this.position.y = this.originContainY + deltaY;
            this.size.height = newHeight;
          }
          break;
        case "rt": // 右上角
          newWidth = this.originWidth + deltaX;
          newHeight = this.originHeight - deltaY;
          if (newWidth >= this.minWidth) {
            this.size.width = newWidth;
          }
          if (newHeight >= this.minHeight) {
            this.position.y = this.originContainY + deltaY;
            this.size.height = newHeight;
          }
          break;
        case "r": // 右边
          newWidth = this.originWidth + deltaX;
          if (newWidth >= this.minWidth) {
            this.size.width = newWidth;
          }
          break;
        case "rb": // 右下角
          newWidth = this.originWidth + deltaX;
          newHeight = this.originHeight + deltaY;
          if (newWidth >= this.minWidth) {
            this.size.width = newWidth;
          }
          if (newHeight >= this.minHeight) {
            this.size.height = newHeight;
          }
          break;
        case "b": // 下边
          newHeight = this.originHeight + deltaY;
          if (newHeight >= this.minHeight) {
            this.size.height = newHeight;
          }
          break;
        case "lb": // 左下角
          newWidth = this.originWidth - deltaX;
          newHeight = this.originHeight + deltaY;
          if (newWidth >= this.minWidth) {
            this.position.x = this.originContainX + deltaX;
            this.size.width = newWidth;
          }
          if (newHeight >= this.minHeight) {
            this.size.height = newHeight;
          }
          break;
        case "l": // 左边
          newWidth = this.originWidth - deltaX;
          if (newWidth >= this.minWidth) {
            this.position.x = this.originContainX + deltaX;
            this.size.width = newWidth;
          }
          break;
      }
    },

    /**
     * 停止拖拽或缩放
     * 清除事件监听器
     */
    stopDrag() {
      document.removeEventListener("mousemove", this.handleDrag);
      document.removeEventListener("mousemove", this.handleResize);
      document.removeEventListener("mouseup", this.stopDrag);
    },
  },

  // 组件销毁时移除事件监听
  beforeDestroy() {
    this.stopDrag();
  },
};
</script>
<style lang="scss" scoped>
$lineOffset: -6px;
$cornerOffset: -8px;
/* 拖拽容器的样式 */
.draggable-container {
  position: fixed; /* 绝对定位 */
  cursor: move; /* 鼠标移动时显示抓手指针 */
  user-select: none; /* 禁止选中文本 */
  background-color: #ccc;
  span {
    position: absolute;
    display: block;
  }
  /* 左边和右边 */
  .l-resize,
  .r-resize {
    width: 8px;
    height: 100%;
    top: 0;
    cursor: w-resize;
  }
  .l-resize {
    left: $lineOffset;
  }
  .r-resize {
    right: $lineOffset;
  }

  /* 上边和下边 */
  .t-resize,
  .b-resize {
    width: 100%;
    height: 8px;
    left: 0;
    cursor: s-resize;
  }
  .t-resize {
    top: $lineOffset;
  }
  .b-resize {
    bottom: $lineOffset;
  }
  /* 四个角 */
  .lt-resize,
  .rt-resize,
  .rb-resize,
  .lb-resize {
    width: 15px;
    height: 15px;
    z-index: 10;
  }
  .lt-resize,
  .lb-resize {
    left: $cornerOffset;
  }
  .lt-resize,
  .rt-resize {
    top: $cornerOffset;
  }
  .rt-resize,
  .rb-resize {
    right: $cornerOffset;
  }
  .rb-resize,
  .lb-resize {
    bottom: $cornerOffset;
  }

  .lt-resize,
  .rb-resize {
    cursor: se-resize;
  }
  .rt-resize,
  .lb-resize {
    cursor: sw-resize;
  }
}
</style>

组件引用

<DraggableContainer
  :width="400"
  :height="400"
  :min-height="300"
  :min-width="300"
>
  <div>能拖动我了</div>
</DraggableContainer>

总结

部分代码设计参考了著名第三方库vxe-modal的设计思路:vxe-modal

本文实现了拖拽移动和缩放的功能,同学们也可以根据需要往上面添加自己的改动。希望对您有所帮助!


作者:前端金熊
链接:https://juejin.cn/post/7451927213435093033
来源:稀土掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

该文章在 2025/4/1 13:03:47 编辑过
关键字查询
相关文章
正在查询...
点晴ERP是一款针对中小制造业的专业生产管理软件系统,系统成熟度和易用性得到了国内大量中小企业的青睐。
点晴PMS码头管理系统主要针对港口码头集装箱与散货日常运作、调度、堆场、车队、财务费用、相关报表等业务管理,结合码头的业务特点,围绕调度、堆场作业而开发的。集技术的先进性、管理的有效性于一体,是物流码头及其他港口类企业的高效ERP管理信息系统。
点晴WMS仓储管理系统提供了货物产品管理,销售管理,采购管理,仓储管理,仓库管理,保质期管理,货位管理,库位管理,生产管理,WMS管理系统,标签打印,条形码,二维码管理,批号管理软件。
点晴免费OA是一款软件和通用服务都免费,不限功能、不限时间、不限用户的免费OA协同办公管理系统。
Copyright 2010-2025 ClickSun All Rights Reserved