Vue实现可拖拽边界布局

10,976次阅读
没有评论

共计 6259 个字符,预计需要花费 16 分钟才能阅读完成。

Vue 实现可拖拽边界布局

在前端开发中,有时需要实现一种可拖拽边界的布局,通过拖动分隔线来调整不同区域大小。例如,下图是一个典型的可拖拽边界布局,它由左右两个区域组成,左边是一个树形菜单,右边是一个上下分割的内容区域。用户可以通过拖动水平和垂直的分隔线来改变左右区域和上下区域的宽度和高度。
Vue 实现可拖拽边界布局

本文用 Vue 来实现这种可拖拽边界布局,只需要用到 Vue 的基本特性,如数据绑定、事件处理、样式绑定等(额外的 el-tree 基于 elementui 可不加)。主要涉及到以下几个方面:

  • 布局结构:使用 flex 布局来实现容器和子元素的分配,使用 style 绑定来动态调整区域的大小,使用 cursor 属性来改变鼠标的形状。
  • 数据定义:使用 data 选项来定义不同区域的宽度和高度,以及是否正在拖动分隔线,以及拖动开始时的鼠标位置和区域大小。
  • 事件处理:使用 methods 选项来定义开始拖动、拖动中和结束拖动的函数,使用 draggingH 和 draggingV 来判断拖动的方向,使用 startX 和 startY 来记录拖动的起点,使用 delta 来计算拖动的距离,使用 leftWidth、rightWidth、topHeight 和 bottomHeight 来更新区域的大小。
  • 事件绑定:使用 v -on 指令来绑定分隔线的 mousedown 事件,表示用户开始拖动分隔线,给 document 绑定 mousemove 事件,表示用户正在拖动分隔线,给 document 绑定 mouseup 事件,表示用户结束拖动分隔线。

布局结构

首先定义布局的结构,这里使用 flex 布局来实现。布局由一个容器 div 和四个子 div 组成,分别是左边区域、右边区域、水平分隔线和垂直分隔线。容器 div 的 display 属性设置为 flex,表示它是一个弹性盒子,它的子元素可以按照一定的比例分配空间。左边区域和右边区域的 flex-direction 属性设置为 column,表示它们是一个垂直方向的弹性盒子,它们的子元素可以按照一定的比例分配高度。右边区域又由上下两个子 div 组成,分别是上面区域和下面区域。水平分隔线和垂直分隔线的宽度和高度分别设置为 10px,表示它们是分隔线的宽度。水平分隔线的 cursor 属性设置为 col-resize,表示当鼠标移动到分隔线上时,鼠标的形状会变成一个水平方向的双箭头,表示可以拖动分隔线。垂直分隔线的 cursor 属性设置为 row-resize,表示当鼠标移动到分隔线上时,鼠标的形状会变成一个垂直方向的双箭头,表示可以拖动分隔线。我们还可以给分隔线添加一些样式,如背景色、边框等,以增加视觉效果。以下是布局结构的代码:

template>
  div id="app">
    div class="container">
      div class="left" :style="{width: leftWidth +'px'}">

        el-tree class="tree" :data="treeData" :props="defaultProps" node-key="id">
        el-tree>
      div>
      div class="divider-h" @mousedown="startDragH">
        span>||span>
      div>
      div class="right" :style="{width: rightWidth +'px'}">
        div class="top" :style="{height: topHeight +'px'}">
          p> 这是右边上面的区域 p>
        div>
        div class="divider-v" @mousedown="startDragV">
          
        div>
        div class="bottom" :style="{height: bottomHeight +'px'}">
          p> 这是右边下面的区域 p>
        div>
      div>
    div>
  div>
template>

数据定义

接下来定义一些数据,用来表示不同区域的宽度和高度,以及是否正在拖动分隔线,以及拖动开始时的鼠标位置和区域大小。我们可以在 Vue 实例的 data 选项中定义这些数据,如下所示:

export default {
  name: "App",
  data() {
    return {
      containerWidth: 800, 
      containerHeight: 600, 
      leftWidth: 400, 
      rightWidth: 400, 
      topHeight: 300, 
      bottomHeight: 300, 
      draggingH: false, 
      draggingV: false, 
      startX: 0, 
      startY: 0, 
      startLeftWidth: 0, 
      startRightWidth: 0,
      startTopHeight: 0, 
      startBottomHeight: 0,
    };
  },
};

事件处理

然后需要定义一些事件处理函数,用来实现拖动分隔线的逻辑。监听分隔线的 mousedown 事件,表示用户开始拖动分隔线,以及 document 的 mousemove 事件,表示用户正在拖动分隔线,以及 document 的 mouseup 事件,表示用户结束拖动分隔线。我们可以在 Vue 实例的 methods 选项中定义这些事件处理函数,如下所示:

methods: {
    
    startDragH(e) {
      this.draggingH = true;
      this.startX = e.clientX;
      this.startLeftWidth = this.leftWidth;
      this.startRightWidth = this.rightWidth;
    },
    
    startDragV(e) {
      this.draggingV = true;
      this.startY = e.clientY;
      this.startTopHeight = this.topHeight;
      this.startBottomHeight = this.bottomHeight;
    },
    
    onDrag(e) {
      if (this.draggingH) {
        let delta = e.clientX - this.startX;
        
        this.leftWidth = this.startLeftWidth + delta;
        this.rightWidth = this.startRightWidth - delta;
      }
      if (this.draggingV) {
        let delta = e.clientY - this.startY;
        
        this.topHeight = this.startTopHeight + delta;
        this.bottomHeight = this.startBottomHeight - delta;
      }
    },
    
    endDrag() {
      this.draggingH = false;
      this.draggingV = false;
    },
  },

在开始水平拖动和开始垂直拖动的函数中,设置 draggingH 和 draggingV 为 true,表示正在拖动分隔线,同时记录下鼠标的位置和区域的大小,作为拖动的起点。在拖动中的函数中,我们需要根据鼠标的位置和拖动的起点计算出拖动的距离,然后根据拖动的距离更新左右区域和上下区域的宽度和高度。在结束拖动的函数中,我们需要设置 draggingH 和 draggingV 为 false,表示停止拖动分隔线。

事件绑定

最后给水平分隔线和垂直分隔线绑定 mousedown 事件,表示用户开始拖动分隔线,给 document 绑定 mousemove 事件

  mounted() {
    
    document.addEventListener("mousemove", this.onDrag);
    document.addEventListener("mouseup", this.endDrag);
  },
  beforeDestroy() {
    
    document.removeEventListener("mousemove", this.onDrag);
    document.removeEventListener("mouseup", this.endDrag);
  },
};

样式定义

最后,我们需要给布局的元素添加一些样式,以增加辨识度。我们可以在 Vue 实例的 style 选项中定义这些样式

完整代码

以下是完整的代码,你可以复制到编辑器中运行

template>
  div id="app">
    div class="container">
      div class="left" :style="{width: leftWidth +'px'}">

        el-tree class="tree" :data="treeData" :props="defaultProps" node-key="id">
        el-tree>
      div>
      div class="divider-h" @mousedown="startDragH">
        span>||span>
      div>
      div class="right" :style="{width: rightWidth +'px'}">
        div class="top" :style="{height: topHeight +'px'}">
          p> 这是右边上面的区域 p>
        div>
        div class="divider-v" @mousedown="startDragV">
          
        div>
        div class="bottom" :style="{height: bottomHeight +'px'}">
          p> 这是右边下面的区域 p>
        div>
      div>
    div>
  div>
template>

script>
export default {
  name: "App",
  data() {
    return {
      containerWidth: 800, 
      containerHeight: 600, 
      leftWidth: 400, 
      rightWidth: 400, 
      topHeight: 300, 
      bottomHeight: 300, 
      draggingH: false, 
      draggingV: false, 
      startX: 0, 
      startY: 0, 
      startLeftWidth: 0, 
      startRightWidth: 0,
      startTopHeight: 0, 
      startBottomHeight: 0,
      treeData: [
        {
          id: 1,
          label: "一级 1",
          children: [
            {
              id: 4,
              label: "二级 1-1",
              children: [
                {
                  id: 9,
                  label: "三级 1-1-1",
                },
                {
                  id: 10,
                  label: "三级 1-1-2",
                },
              ],
            },
          ],
        },
        {
          id: 2,
          label: "一级 2",
          children: [
            {
              id: 5,
              label: "二级 2-1",
            },
            {
              id: 6,
              label: "二级 2-2",
            },
          ],
        },
        {
          id: 3,
          label: "一级 3",
          children: [
            {
              id: 7,
              label: "二级 3-1",
            },
            {
              id: 8,
              label: "二级 3-2",
            },
          ],
        },
      ],
      defaultProps: {
        children: "children",
        label: "label",
      },
    };
  },

  methods: {
    
    startDragH(e) {
      this.draggingH = true;
      this.startX = e.clientX;
      this.startLeftWidth = this.leftWidth;
      this.startRightWidth = this.rightWidth;
    },
    
    startDragV(e) {
      this.draggingV = true;
      this.startY = e.clientY;
      this.startTopHeight = this.topHeight;
      this.startBottomHeight = this.bottomHeight;
    },
    
    onDrag(e) {
      if (this.draggingH) {
        
        let delta = e.clientX - this.startX;
        
        this.leftWidth = this.startLeftWidth + delta;
        this.rightWidth = this.startRightWidth - delta;
      }
      if (this.draggingV) {
        
        let delta = e.clientY - this.startY;
        
        this.topHeight = this.startTopHeight + delta;
        this.bottomHeight = this.startBottomHeight - delta;
      }
    },
    
    endDrag() {
      this.draggingH = false;
      this.draggingV = false;
    },
    onresize() {
      this.leftWidth = window.innerWidth * 0.3 - 5
      this.rightWidth = window.innerWidth * 0.7 - 5
      this.topHeight = window.innerHeight * 0.5 - 5
      this.bottomHeight = window.innerHeight * 0.5 - 5
      console.log(window.screen);
    }
  },
  mounted() {

    
    document.addEventListener("mousemove", this.onDrag);
    document.addEventListener("mouseup", this.endDrag);
    window.addEventListener("resize", this.onresize);
    this.leftWidth = window.innerWidth * 0.2 - 5
    this.rightWidth = window.innerWidth * 0.8 - 5
    this.topHeight = window.innerHeight * 0.5 - 5
    this.bottomHeight = window.innerHeight * 0.5 - 5
    
  },
  beforeDestroy() {
    
    document.removeEventListener("mousemove", this.onDrag);
    document.removeEventListener("mouseup", this.endDrag);
  },
};
script>

style>
html,
body {
  width: 100%;
  height: 100%;
  margin: 0;
  padding: 0;
}

.container {
  display: flex;
  width: 100%;
  height: 100%;
  
}

.left {
  display: flex;
  flex-direction: column;
  background-color: lightblue;
  height: 100%;
  width: 30%;
}

.right {
  display: flex;
  flex-direction: column;
  background-color: lightgreen;
  height: 100%;
  width: 70%;

}

.top {
  background-color: blueviolet;
}

.bottom {
  background-color: bisque;
}

.divider-h {
  width: 10px;
  cursor: col-resize;
}

.divider-h span {
  display: block;
  margin-top: 290px;
}

.divider-v {
  height: 10px;
  cursor: row-resize;
  background-color: aliceblue;
}

.divider-v span {
  display: block;
  margin-left: 190px;
}

.tree {
  flex: 1;
  overflow: auto;
  cursor: pointer;
}style>

原文地址: Vue 实现可拖拽边界布局

    正文完
     0
    Yojack
    版权声明:本篇文章由 Yojack 于2024-10-29发表,共计6259字。
    转载说明:
    1 本网站名称:优杰开发笔记
    2 本站永久网址:https://yojack.cn
    3 本网站的文章部分内容可能来源于网络,仅供大家学习与参考,如有侵权,请联系站长进行删除处理。
    4 本站一切资源不代表本站立场,并不代表本站赞同其观点和对其真实性负责。
    5 本站所有内容均可转载及分享, 但请注明出处
    6 我们始终尊重原创作者的版权,所有文章在发布时,均尽可能注明出处与作者。
    7 站长邮箱:laylwenl@gmail.com
    评论(没有评论)