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">
          <!-- <span>==</span> -->
        </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">
          <!-- <span>==</span> -->
        </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%;
  /* border: 1px solid black; */
}

.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>

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/195409.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【开源】基于JAVA的农村物流配送系统

项目编号&#xff1a; S 024 &#xff0c;文末获取源码。 \color{red}{项目编号&#xff1a;S024&#xff0c;文末获取源码。} 项目编号&#xff1a;S024&#xff0c;文末获取源码。 目录 一、摘要1.1 项目介绍1.2 项目录屏 二、功能模块2.1 系统登录、注册界面2.2 系统功能2.2…

西南科技大学数字电子技术实验二(SSI逻辑器件设计组合逻辑电路及FPGA实现 )FPGA部分

一、实验目的 1、掌握用SSI(小规模集成电路)逻辑器件设计组合电路的方法。 2、掌握组合逻辑电路的调试方法。 3、学会分析和解决实验中遇到的问题。 4、学会用FPGA实现本实验内容。 二、实验原理 包括:原理图绘制和实验原理简述 1、1位半加器 2、1位全加器 3、三…

leetcode 1670

leetcode 1670 解题思路 使用2个deque作为类的成员变量 code class FrontMiddleBackQueue { public:deque<int> left;deque<int> right;FrontMiddleBackQueue() {}void pushFront(int val) {left.push_front(val);if(left.size() right.size()2){right.push_fr…

2021年06月 Scratch图形化(四级)真题解析#中国电子学会#全国青少年软件编程等级考试

Scratch等级考试(1~4级)全部真题・点这里 一、单选题(共10题,每题2分,共20分) 第1题 执行下列程序,输出的结果为? A:12 B:24 C:8 D:30 答案:B 第2题 执行下列程序,角色说出的内容是? A:2 B:3 C:4 D:5 答案:A 第3题 执行下列程序,输出结果为?

C++基础 -6-二维数组,数组指针

二维数组在内存中的存放方式和一维数组完全相同 下表把二维数组抽象成了行列形式方便理解 a[0]指向第一行首元素地址 a指向第一行的首地址 所以a地址和a[0]地址相同,因为起点相同 但a[0]1往右偏移 但a1往下方向偏移 方便理解 an控制行 a[0]n控制列(相当于*an) 数组指针指向二…

聊聊VMware vSphere

VMware vSphere是一种虚拟化平台和云计算基础设施解决方案&#xff0c;由VMware公司开发。它为企业提供了一种强大的虚拟化和云计算管理平台&#xff0c;能够在数据中心中运行、管理和保护应用程序和数据。vSphere平台与VMware ESXi虚拟化操作系统相结合&#xff0c;提供了完整…

Linux fork笔试练习题

1.打印结果&#xff1f; #include <stdio.h> #include <unistd.h> #include <stdlib.h>int main() {int i0;for(;i<2;i){fork();printf("A\n");}exit(0); } 结果打印 A A A A A A 2.将上面的打印的\n去掉,结果如何? printf("…

HarmonyOS 应用模型开发指南介绍

一、基本概念解析 新版文档中的知识点&#xff0c;介绍更全面&#xff0c;逻辑更清晰&#xff0c;提供了各类基本概念解析&#xff0c;帮助开发者更快学习、掌握系统能力。以下是新版文档部分概念展示。 1、HAP是什么&#xff1f; 开发者通过DevEco Studio把应用程序编译为一…

入门级认证 | Salesforce管理员认证最新备考指南!

Salesforce管理员认证是其他任何管理员认证的基础。通过此考试将展示你对Salesforce功能和最佳实践的了解&#xff0c;使用自定义功能来管理、维护和扩展Salesforce平台。 Admin认证对备考者的要求 Salesforce管理员认证验证了备考者在管理客户和简档、维护数据质量、配置自动…

C/C++ Zlib实现文件压缩与解压

在软件开发和数据处理中&#xff0c;对数据进行高效的压缩和解压缩是一项重要的任务。这不仅有助于减小数据在网络传输和存储中的占用空间&#xff0c;还能提高系统的性能和响应速度。本文将介绍如何使用 zlib 库进行数据的压缩和解压缩&#xff0c;以及如何保存和读取压缩后的…

2、XFP 与 SFP+:有什么区别?

在光纤网络领域&#xff0c;光模块是促进数据顺利传输的重要组件。市场继续接受10G XFP和10G SFP等10G光模块&#xff0c;促使人们对XFP与SFP进行更仔细的审视。他们有什么区别&#xff1f;XFP和SFP的定义是什么&#xff1f;他们的应用场景又如何呢&#xff1f;在下文中寻找所有…

MySQL用得好好的,为何要转ES?

MySQL是一种关系型数据库&#xff0c;它可以高效地存储和查询结构化的数据。 ES是一种分布式搜索引擎&#xff0c;它可以快速地对海量的非结构化或半结构化的数据进行全文检索和分析。 MySQL 和 ES 的数据存储方式也不同。MySQL 中的数据通常是以关系型表的形式存储在磁盘上&…

8.0 泛型

通过之前的学习&#xff0c;读者可以了解到&#xff0c;把一个对象存入集合后&#xff0c;再次取出该对象时&#xff0c;该对象的编译类型就变成了Object类型&#xff08;尽管其在运行时类型没有改变&#xff09;。集合设计成这样&#xff0c;提高了它的通用性&#xff0c;但是…

MySQL(免密登录)

简介: MySQL免密登录是一种允许用户在没有输入密码的情况下直接登录到MySQL服务器的配置。这通常是通过在登录时跳过密码验证来实现的。 1、修改MySQL的配置文件 使用vi /etc/my.cnf&#xff0c;添加到【mysqld】后面 skip-grant-tables #配置项告诉mysql跳过权限验证&#…

OpenCV快速入门【完结】:总目录——初窥计算机视觉

文章目录 前言目录1. OpenCV快速入门&#xff1a;初探2. OpenCV快速入门&#xff1a;像素操作和图像变换3. OpenCV快速入门&#xff1a;绘制图形、图像金字塔和感兴趣区域4. OpenCV快速入门&#xff1a;图像滤波与边缘检测5. OpenCV快速入门&#xff1a;图像形态学操作6. OpenC…

【计算机组成原理】存储系统

&#x1f384;欢迎来到边境矢梦的csdn博文&#x1f384; &#x1f384;本文主要梳理计算机组成原理中 存储系统的知识点和值得注意的地方 &#x1f384; &#x1f308;我是边境矢梦&#xff0c;一个正在为秋招和算法竞赛做准备的学生&#x1f308; &#x1f386;喜欢的朋友可以…

Docker可视化工具Portainer(轻量)或者Docker容器监控之 CAdvisor+InfluxDB+Granfana(重量)

Docker轻量级可视化工具Portainer 是什么 Portainer 是一款轻量级的应用&#xff0c;它提供了图形化界面&#xff0c;用于方便地管理Docker环境&#xff0c;包括单机环境和集群环境。 安装 官网 https://www.portainer.io/ https://docs.portainer.io/v/ce-2.9/start/instal…

【密码学】【安全多方计算】浅析隐私求交PSI

文章目录 隐私求交的定义隐私求交方案介绍1. 基于DH的PSI方案2. 基于OT的PSI方案3.基于OPRF的PSI方案 总结 隐私求交的定义 隐私集合求交使得持有数据参与方通过计算得到集合的交集数据&#xff0c;而不泄露任何交集以外的数据信息。 隐私求交方案介绍 1. 基于DH的PSI方案 …

如何通过内网穿透实现公网远程ssh连接kali系统

文章目录 1. 启动kali ssh 服务2. kali 安装cpolar 内网穿透3. 配置kali ssh公网地址4. 远程连接5. 固定连接SSH公网地址6. SSH固定地址连接测试 简单几步通过[cpolar 内网穿透](cpolar官网-安全的内网穿透工具 | 无需公网ip | 远程访问 | 搭建网站)软件实现ssh远程连接kali 1…

docker安装Sentinel

文章目录 引言I Sentinel安装1.1 运行容器1.2 DOCKERFILE 参考1.3 pom 依赖1.4 .yml配置(整合springboot)II 资源保护2.1 Feign整合Sentinel2.2 CommonExceptionAdvice:限流异常处理类引言 I Sentinel安装 Sentinel 分为两个部分: 核心库(Java 客户端)不依赖任何框架/库,能…