基于Vue3实现鼠标按下某个元素进行拖动,实时改变左侧或右侧元素的宽度,以及点击收起或展开的功能

简介: 本文介绍了如何在Vue3项目中实现一个鼠标拖动调整元素宽度的功能,并展示了点击按钮收起或展开侧边栏的效果,提供了完整的实现代码和操作演示。

前言

其原理主要是利用JavaScript中的鼠标事件来控制CSS样式。大致就是监听某个DOM元素的鼠标按下事件,以及按下之后的移动事件和松开事件。在鼠标按下且移动过程中,可实时获得鼠标的X轴坐标的值,通过简单计算,可计算出目标元素的宽度,然后再用CSS赋值就实现该效果了。

一、示例代码

(1)/src/views/Example/MouseResizeWidth/index.vue

<template>
  <div class="index">
    <div class="index-left" ref="indexLeftRef">
      <div class="left-area-box"></div>

      <div class="left-resize-bar"></div>

      <div class="left-view-more" @click="handleViewMoreLeftClick">
        <div class="left-view-more-content">
          <div class="left-view-more-false" v-if="!isCollapseLeft" />
          <div class="left-view-more-true" v-else />
        </div>
      </div>
    </div>

    <div class="index-middle" ref="indexRightRef">
      <div class="middle-area-box">
        <div class="middle-area-box_main">
          <div class="middle-area-box_main_up">
            <div class="middle-area-box_main_up_wrapper">
              <!-- ^ 工具栏 -->
              <div class="index-tools-container">
                <el-form :inline="true" style="display: flex">
                  <div class="tools-left"></div>

                  <div class="tools-right">
                    <el-form-item style="margin: 0 0 7px 7px">
                      <el-button size="small" type="primary">
                        <el-icon :size="12" style="margin-right: 5px"><ElementPlus /></el-icon>
                        <small>ElementPlus</small>
                      </el-button>
                    </el-form-item>
                  </div>
                </el-form>
              </div>
              <!-- / 工具栏 -->

              <!-- ^ 内容区 -->
              <div class="index-table-container">
                <el-table
                  border
                  size="small"
                  row-key="id"
                  ref="tableRef"
                  height="100%"
                  highlight-current-row
                  :data="tableList"
                >

                  <el-table-column fixed type="selection" :resizable="false" width="30" reserve-selection align="center" />
                  <el-table-column prop="name" label="英雄名称" align="center" width="200" show-overflow-tooltip />
                  <el-table-column prop="description" label="英雄描述" align="center" width="auto" show-overflow-tooltip />
                  <el-table-column prop="firstSkill" label="一技能" align="center" width="200" show-overflow-tooltip />
                  <el-table-column prop="secondSkill" label="二技能" align="center" width="200" show-overflow-tooltip />
                  <el-table-column prop="thirdSkill" label="三技能" align="center" width="200" show-overflow-tooltip />

                  <template #empty v-if="tableList == undefined || tableList.length == 0">Nothing ~</template>
                </el-table>
              </div>
              <!-- / 内容区 -->
            </div>
          </div>
        </div>
      </div>
    </div>

    <div class="index-right" ref="indexRightRef">
      <div class="right-view-more" @click="handleViewMoreRightClick">
        <div class="right-view-more-content">
          <div class="right-view-more-false" v-if="!isCollapseRight" />
          <div class="right-view-more-true" v-else />
        </div>
      </div>

      <div class="right-resize-bar"></div>

      <div class="right-area-box"></div>
    </div>
  </div>
</template>

<script setup>
import {
    
     h, onMounted, onUnmounted, ref, getCurrentInstance, reactive, watch, nextTick } from 'vue'

// 代理对象
const {
    
     proxy } = getCurrentInstance()

// 是否收起左侧
const isCollapseLeft = ref(false)
// 左侧模块箭头点击事件句柄方法
const handleViewMoreLeftClick = async () => {
    
    
  const indexLeftRef = await proxy.$refs.indexLeftRef
  isCollapseLeft.value = !isCollapseLeft.value
  if (isCollapseLeft.value) {
    
    
    indexLeftRef.style.width = '23px'
  } else {
    
    
    indexLeftRef.style.width = '400px'
  }
}

// 表格实例
const tableRef = ref(null)

// 表格数据
const tableList = ref([])

// 是否收起右侧
const isCollapseRight = ref(false)
// 右侧模块箭头点击事件句柄方法
const handleViewMoreRightClick = async () => {
    
    
  const indexRightRef = await proxy.$refs.indexRightRef
  isCollapseRight.value = !isCollapseRight.value
  if (isCollapseRight.value) {
    
    
    indexRightRef.style.width = '23px'
  } else {
    
    
    indexRightRef.style.width = '400px'
  }
}

/**
 * 左侧拖动改变宽度事件句柄方法
 */
const handleDragLeftResizeBar = () => {
    
    
  var leftResizeBar = document.getElementsByClassName("left-resize-bar")[0]
  var wholeArea = document.getElementsByClassName("index")[0]
  var leftArea = document.getElementsByClassName("index-left")[0]
  var middleArea = document.getElementsByClassName("index-middle")[0]
  var rightArea = document.getElementsByClassName("index-right")[0]
  console.log('leftResizeBar =>', leftResizeBar)
  console.log('wholeArea =>', wholeArea)
  console.log('leftArea =>', leftArea)
  console.log('middleArea =>', middleArea)
  console.log('rightArea =>', rightArea)

  // 鼠标按下事件
  leftResizeBar.onmousedown = function (eventDown) {
    
    
    // 颜色提醒
    leftResizeBar.style.backgroundColor = "#5e7ce0"
    leftResizeBar.style.color = "#ffffff"

    // 鼠标拖动事件
    document.onmousemove = function (eventMove) {
    
    

      let width = eventMove.clientX + 20
      console.log('width =>', width)
      if (width >= 800) {
    
    
        width = 800 // 设置最大拉伸宽度为800
      } else if (width <= 23) {
    
    
        // 当拉伸宽度为小于或等于23,最小拉伸宽度为23,同时是否收起图标向右
        width = 23
        isCollapseLeft.value = true
      } else {
    
    
        // 当拉伸宽度为大于23且小于600,是否收起图标向左
        isCollapseLeft.value = false
      }
      leftArea.style.width = width + 'px'
    }

    // 鼠标松开事件
    document.onmouseup = function (evt) {
    
    
      // 颜色恢复
      leftResizeBar.style.backgroundColor = "#ffffff"
      leftResizeBar.style.color = "#40485c"

      document.onmousemove = null
      document.onmouseup = null
      leftResizeBar.releaseCapture && leftResizeBar.releaseCapture();
    }

    leftResizeBar.setCapture && leftResizeBar.setCapture();
    return false
  }
}

/**
 * 右侧拖动改变宽度事件句柄方法
 */
const handleDragRightResizeBar = () => {
    
    
  var rightResizeBar = document.getElementsByClassName("right-resize-bar")[0]
  var wholeArea = document.getElementsByClassName("index")[0]
  var leftArea = document.getElementsByClassName("index-left")[0]
  var middleArea = document.getElementsByClassName("index-middle")[0]
  var rightArea = document.getElementsByClassName("index-right")[0]
  console.log('rightResizeBar =>', rightResizeBar)
  console.log('wholeArea =>', wholeArea)
  console.log('leftArea =>', leftArea)
  console.log('middleArea =>', middleArea)
  console.log('rightArea =>', rightArea)

  // 鼠标按下事件
  rightResizeBar.onmousedown = function (eventDown) {
    
    
    // 颜色提醒
    rightResizeBar.style.backgroundColor = "#5e7ce0"
    rightResizeBar.style.color = "#ffffff"

    // 开始x坐标
    // let startX = eventDown.clientX
    // console.log('startX =>', startX)

    // 鼠标拖动事件
    document.onmousemove = function (eventMove) {
    
    
      // 方式一:基于移动距离方式实现
      // const endX = eventMove.clientX // 结束坐标
      // const len = startX - endX // 移动距离
      // rightArea.style.width = rightArea.clientWidth + len + 'px' // 改变宽度
      // startX = endX // 重新对开始x坐标赋值

      // 方式二:基于总长度和结束x坐标方式实现
      let width = wholeArea.clientWidth + 20 - eventMove.clientX
      if (width >= 800) {
    
    
        width = 800 // 设置最大拉伸宽度为800
      } else if (width <= 23) {
    
    
        // 当拉伸宽度为小于或等于23,最小拉伸宽度为23,同时是否收起图标向左
        width = 23
        isCollapseRight.value = true
      } else {
    
    
        // 当拉伸宽度为大于23且小于600,是否收起图标向右
        isCollapseRight.value = false
      }
      rightArea.style.width = width + 'px'
    }

    // 鼠标松开事件
    document.onmouseup = function (evt) {
    
    
      // 颜色恢复
      rightResizeBar.style.backgroundColor = "#ffffff"
      rightResizeBar.style.color = "#40485c"

      document.onmousemove = null
      document.onmouseup = null
      rightResizeBar.releaseCapture && rightResizeBar.releaseCapture();
    }

    rightResizeBar.setCapture && rightResizeBar.setCapture();
    return false
  }
}

onMounted(() => {
    
    
  handleDragLeftResizeBar()
  handleDragRightResizeBar()
})

onUnmounted(() => {
    
    
  // ...
})
</script>

<style lang="less" scoped>
  .index {
    
    
    display: flex;
    flex-direction: row;
    width: 100%;
    height: 100%;
    overflow: hidden;

    /* ---- ^ 左边 ---- */
    :deep(.index-left) {
    
    
      position: relative;
      z-index: 2;
      display: flex;
      flex-direction: row;
      width: 400px;
      border-right: 1px solid #dcdfe6;

      // ^ 左侧区域
      .left-area-box {
    
    
        flex: 1;
        display: flex;
        flex-direction: column;
        overflow: hidden;
        background-color: #f8f8f8;
      }
      // / 左侧区域

      // ^ 是否收起左侧边栏的图标
      .left-view-more {
    
    
        position: relative;
        width: 15px;
        height: 100%;
        background-color: #f3f6f8;
        border-left: 1px solid #dcdfe6;

        .left-view-more-content {
    
    
          width: 12px;
          height: 30px;
          background-color: #ccc;
          border-bottom-right-radius: 4px;
          border-top-right-radius: 4px;
          position: absolute;
          display: block;
          margin: auto;
          left: 0;
          top: 0;
          bottom: 0;
          cursor: pointer;
          z-index: 1;
          transition: all ease 0.3s;

          &:hover {
    
    
            background-color: #5e7ce0;
          }

          .left-view-more-true {
    
    
            width: 100%;
            height: 10px;
            position: absolute;
            display: block;
            margin: auto;
            left: 0;
            right: 0;
            top: 0;
            bottom: 0;

            &::before {
    
    
              display: block;
              height: 2px;
              width: 10px;
              content: "";
              position: absolute;
              left: 0;
              top: 0;
              background-color: #fff;
              transform: rotate(70deg);
            }

            &::after {
    
    
              display: block;
              height: 2px;
              width: 10px;
              content: "";
              position: absolute;
              left: 0;
              bottom: 0;
              background-color: #fff;
              transform: rotate(-70deg);
            }
          }

          .left-view-more-false {
    
    
            width: 100%;
            height: 10px;
            position: absolute;
            display: block;
            margin: auto;
            left: 0;
            right: 0;
            top: 0;
            bottom: 0;

            &::before {
    
    
              display: block;
              height: 2px;
              width: 10px;
              content: "";
              position: absolute;
              left: 0;
              top: 0;
              background-color: #fff;
              transform: rotate(-70deg);
            }

            &::after {
    
    
              display: block;
              height: 2px;
              width: 10px;
              content: "";
              position: absolute;
              left: 0;
              bottom: 0;
              background-color: #fff;
              transform: rotate(70deg);
            }
          }
        }
      }
      // / 是否收起左侧边栏的图标

      // ^ 左侧拖动条
      .left-resize-bar {
    
    
        display: flex;
        align-items: center;
        width: 7px;
        height: 100%;
        background-color: rgb(255, 255, 255);
        cursor: col-resize;
        user-select: none;
        transition: all ease 0.3s;
        font-size: 20px;
        color: #40485c;

        &:hover {
    
    
          color: #fff !important;
          background-color: #5e7ce0 !important;
        }
      }
      // / 左侧拖动条
    }
    /* ---- / 左边 ---- */

    /* ---- ^ 中间 ---- */
    :deep(.index-middle) {
    
    
      position: relative;
      z-index: 1;
      flex: 1;
      overflow: hidden;
      position: relative;
      transition: all ease 0.3s;
      background-color: #f3f6f8;

      // ^ 中间区域
      .middle-area-box {
    
    
        display: flex;
        position: relative;
        width: 100%;
        height: 100%;
        overflow: hidden;

        .middle-area-box_main {
    
    
          position: relative;
          flex: 1 1;
          display: flex;
          flex-direction: column;
          width: 100%;

          .middle-area-box_main_up {
    
    
            flex: 1;
            display: flex;
            overflow: hidden;
            flex-direction: column;
            background-color: #fff;

            .middle-area-box_main_up_wrapper {
    
    
              flex: 1;
              display: flex;
              flex-direction: column;
              padding: 7px;
              overflow: auto;

              .index-tools-container {
    
    
                .tools-left {
    
    
                  flex: 1;
                }
                .tools-right {
    
    
                  height: auto;
                }
              }

              .index-table-container {
    
    
                flex: 1;
                overflow: auto;
              }

              .el-table {
    
    

                th .cell {
    
    
                  padding: 2.5px;
                  font-weight: normal;
                  font-size: 13px;
                }

                td .cell {
    
    
                  padding: 2.5px 0;
                  color: #000;
                  font-size: 13px;
                }

                .el-table__cell {
    
    
                  padding: 0;
                }

                /* ^ 表格复选框 */
                .el-table-column--selection {
    
    

                  .cell {
    
    
                    width: 100%;
                    display: block;

                    .el-checkbox {
    
    

                      .el-checkbox__inner {
    
    
                        transform: scale(1.2);
                        border-radius: 50%;
                        border: 1px solid #bbb;
                      }

                      .el-checkbox__input.is-checked .el-checkbox__inner,
                      .el-checkbox__input.is-indeterminate .el-checkbox__inner {
    
    
                        border: 1px solid #5e7ce0;
                      }
                    }
                  }
                }
                /* / 表格复选框 */
              }
            }
          }

          .middle-area-box_main_down {
    
    
            flex: 0;
          }
        }
      }
      // / 中间区域
    }
    /* ---- / 中间 ---- */

    /* ---- ^ 右边 ---- */
    :deep(.index-right) {
    
    
      position: relative;
      z-index: 2;
      display: flex;
      flex-direction: row;
      width: 400px;
      border-left: 1px solid #dcdfe6;

      // ^ 是否收起右侧边栏的图标
      .right-view-more {
    
    
        position: relative;
        width: 15px;
        height: 100%;
        background-color: #f3f6f8;
        border-right: 1px solid #dcdfe6;

        .right-view-more-content {
    
    
          width: 12px;
          height: 30px;
          background-color: #ccc;
          border-bottom-left-radius: 4px;
          border-top-left-radius: 4px;
          position: absolute;
          display: block;
          margin: auto;
          right: 0;
          top: 0;
          bottom: 0;
          cursor: pointer;
          z-index: 1;
          transition: all ease 0.3s;

          &:hover {
    
    
            background-color: #5e7ce0;
          }

          .right-view-more-true {
    
    
            width: 100%;
            height: 10px;
            position: absolute;
            display: block;
            margin: auto;
            left: 0;
            right: 0;
            top: 0;
            bottom: 0;

            &::before {
    
    
              display: block;
              height: 2px;
              width: 10px;
              content: "";
              position: absolute;
              left: 0;
              top: 0;
              background-color: #fff;
              transform: rotate(-70deg);
            }

            &::after {
    
    
              display: block;
              height: 2px;
              width: 10px;
              content: "";
              position: absolute;
              left: 0;
              bottom: 0;
              background-color: #fff;
              transform: rotate(70deg);
            }
          }

          .right-view-more-false {
    
    
            width: 100%;
            height: 10px;
            position: absolute;
            display: block;
            margin: auto;
            left: 0;
            right: 0;
            top: 0;
            bottom: 0;

            &::before {
    
    
              display: block;
              height: 2px;
              width: 10px;
              content: "";
              position: absolute;
              right: 0;
              top: 0;
              background-color: #fff;
              transform: rotate(70deg);
            }

            &::after {
    
    
              display: block;
              height: 2px;
              width: 10px;
              content: "";
              position: absolute;
              right: 0;
              bottom: 0;
              background-color: #fff;
              transform: rotate(-70deg);
            }
          }
        }
      }
      // / 是否收起右侧边栏的图标

      // ^ 右侧拖动条
      .right-resize-bar {
    
    
        position: relative;
        display: flex;
        align-items: center;
        width: 7px;
        height: 100%;
        background-color: rgb(255, 255, 255);
        cursor: col-resize;
        user-select: none;
        transition: all ease 0.3s;
        font-size: 20px;
        color: #40485c;

        &:hover {
    
    
          color: #fff !important;
          background-color: #5e7ce0 !important;
        }
      }
      // / 右侧拖动条

      // ^ 右侧区域
      .right-area-box {
    
    
        flex: 1;
        display: flex;
        flex-direction: column;
        overflow: hidden;
        background-color: #f8f8f8;
      }
      // / 右侧区域
    }
    /* ---- / 右边 ---- */
  }
</style>

二、运行效果

目录
相关文章
|
24天前
|
缓存 JavaScript UED
Vue3中v-model在处理自定义组件双向数据绑定时有哪些注意事项?
在使用`v-model`处理自定义组件双向数据绑定时,要仔细考虑各种因素,确保数据的准确传递和更新,同时提供良好的用户体验和代码可维护性。通过合理的设计和注意事项的遵循,能够更好地发挥`v-model`的优势,实现高效的双向数据绑定效果。
126 64
|
24天前
|
前端开发 JavaScript 测试技术
Vue3中v-model在处理自定义组件双向数据绑定时,如何避免循环引用?
Web 组件化是一种有效的开发方法,可以提高项目的质量、效率和可维护性。在实际项目中,要结合项目的具体情况,合理应用 Web 组件化的理念和技术,实现项目的成功实施和交付。通过不断地探索和实践,将 Web 组件化的优势充分发挥出来,为前端开发领域的发展做出贡献。
29 8
|
23天前
|
存储 JavaScript 数据管理
除了provide/inject,Vue3中还有哪些方式可以避免v-model的循环引用?
需要注意的是,在实际开发中,应根据具体的项目需求和组件结构来选择合适的方式来避免`v-model`的循环引用。同时,要综合考虑代码的可读性、可维护性和性能等因素,以确保系统的稳定和高效运行。
23 1
|
23天前
|
JavaScript
Vue3中使用provide/inject来避免v-model的循环引用
`provide`和`inject`是 Vue 3 中非常有用的特性,在处理一些复杂的组件间通信问题时,可以提供一种灵活的解决方案。通过合理使用它们,可以帮助我们更好地避免`v-model`的循环引用问题,提高代码的质量和可维护性。
33 1
|
27天前
|
JavaScript API 开发者
Vue是如何进行组件化的
Vue是如何进行组件化的
|
29天前
|
JavaScript 前端开发 开发者
vue 数据驱动视图
总之,Vue 数据驱动视图是一种先进的理念和技术,它为前端开发带来了巨大的便利和优势。通过理解和应用这一特性,开发者能够构建出更加动态、高效、用户体验良好的前端应用。在不断发展的前端领域中,数据驱动视图将继续发挥重要作用,推动着应用界面的不断创新和进化。
|
3天前
|
JavaScript 关系型数据库 MySQL
基于VUE的校园二手交易平台系统设计与实现毕业设计论文模板
基于Vue的校园二手交易平台是一款专为校园用户设计的在线交易系统,提供简洁高效、安全可靠的二手商品买卖环境。平台利用Vue框架的响应式数据绑定和组件化特性,实现用户友好的界面,方便商品浏览、发布与管理。该系统采用Node.js、MySQL及B/S架构,确保稳定性和多功能模块设计,涵盖管理员和用户功能模块,促进物品循环使用,降低开销,提升环保意识,助力绿色校园文化建设。
|
1月前
|
JavaScript 前端开发 开发者
vue学习第一章
欢迎来到我的博客!我是瑞雨溪,一名热爱前端的大一学生,专注于JavaScript与Vue,正向全栈进发。博客分享Vue学习心得、命令式与声明式编程对比、列表展示及计数器案例等。关注我,持续更新中!🎉🎉🎉
36 1
vue学习第一章
|
1月前
|
JavaScript 前端开发 索引
vue学习第三章
欢迎来到瑞雨溪的博客,一名热爱JavaScript与Vue的大一学生。本文介绍了Vue中的v-bind指令,包括基本使用、动态绑定class及style等,希望能为你的前端学习之路提供帮助。持续关注,更多精彩内容即将呈现!🎉🎉🎉
26 1
vue学习第三章
|
1月前
|
缓存 JavaScript 前端开发
vue学习第四章
欢迎来到我的博客!我是瑞雨溪,一名热爱JavaScript与Vue的大一学生。本文介绍了Vue中计算属性的基本与复杂使用、setter/getter、与methods的对比及与侦听器的总结。如果你觉得有用,请关注我,将持续更新更多优质内容!🎉🎉🎉
35 1
vue学习第四章
下一篇
DataWorks