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

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

一、示例代码

html 复制代码
<template>
  <div class="index">
    <div class="index-left" ref="indexLeftRef">
      <div class="index-left-box">

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

    <div class="index-middle" ref="indexRightRef">
      <div class="index-middle-box">
        <div class="left-view-more" @click="handleViewMoreLeftClick">
          <div class="left-view-more-false" v-if="!isExpandLeft" />
          <div class="left-view-more-true" v-else />
        </div>

        <div class="index-middle-box_main">

        </div>

        <div class="right-view-more" @click="handleViewMoreRightClick">
          <div class="right-view-more-false" v-if="!isExpandRight" />
          <div class="right-view-more-true" v-else />
        </div>
      </div>
    </div>

    <div class="index-right" ref="indexRightRef">
      <div class="right-resize-bar">⋮</div>
      <div class="index-right-box">
        
      </div>
    </div>
  </div>
</template>
 
<script setup>
import { onMounted, ref, getCurrentInstance } from 'vue'

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

// 左侧是否收起或展开
const isExpandLeft = ref(false)

/**
 * 左侧点击收起或展开事件句柄方法
 */
const handleViewMoreLeftClick = async () => {
  const indexLeftRef = await proxy.$refs.indexLeftRef
  isExpandLeft.value = !isExpandLeft.value
  if (isExpandLeft.value) {
    indexLeftRef.style.width = '0'
    indexLeftRef.style.borderRight = '0px solid #dcdfe6'
  } else {
    indexLeftRef.style.width = '400px'
    indexLeftRef.style.borderRight = '1px solid #dcdfe6'
  }
}

// 右侧是否收起或展开
const isExpandRight = ref(false)

/**
 * 右侧点击收起或展开事件句柄方法
 */
const handleViewMoreRightClick = async () => {
  const indexRightRef = await proxy.$refs.indexRightRef
  isExpandRight.value = !isExpandRight.value
  if (isExpandRight.value) {
    indexRightRef.style.width = '0'
    indexRightRef.style.borderRight = '0px solid #dcdfe6'
  } else {
    indexRightRef.style.width = '400px'
    indexRightRef.style.borderRight = '1px solid #dcdfe6'
  }
}

/**
 * 左侧拖动事件句柄方法
 */
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
      console.log('width =>', width)
      if (width >= 600) {
        width = 600 // 设置最大拉伸宽度为600
      } else if (width <= 0) {
        // 当拉伸宽度为小于或等于0,最小拉伸宽度为0,同时是否收起图标向右
        width = 0
        isExpandLeft.value = true
      } else {
        // 当拉伸宽度为大于0且小于600,是否收起图标向左
        isExpandLeft.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() // ReleaseCapture()函数用于释放该元素捕获的鼠标
    }
    leftResizeBar.setCapture && leftResizeBar.setCapture() // setCapture()函数用于设置该元素捕获的鼠标为空
    // 说明:一般情况下,SetCapture()和ReleaseCapture()函数是成对使用的。在使用SetCapture()函数捕获鼠标之后,需要在适当的时候调用ReleaseCapture()函数释放鼠标,否则可能会导致鼠标失去响应或者其他异常情况
    
    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"

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

      let width = wholeArea.clientWidth - eventMove.clientX
      if (width >= 600) {
        width = 600 // 设置最大拉伸宽度为600
      } else if (width <= 0) {
        // 当拉伸宽度为小于或等于0,最小拉伸宽度为0,同时是否收起图标向左
        width = 0
        isExpandRight.value = true
      } else {
        // 当拉伸宽度为大于0且小于600,是否收起图标向右
        isExpandRight.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() // ReleaseCapture()函数用于释放该元素捕获的鼠标
    }
    rightResizeBar.setCapture && rightResizeBar.setCapture() // setCapture()函数用于设置该元素捕获的鼠标为空
    // 说明:一般情况下,SetCapture()和ReleaseCapture()函数是成对使用的。在使用SetCapture()函数捕获鼠标之后,需要在适当的时候调用ReleaseCapture()函数释放鼠标,否则可能会导致鼠标失去响应或者其他异常情况
    
    return false
  }
}

onMounted(() => {
  handleDragLeftResizeBar()
  handleDragRightResizeBar()
})
</script>
 
<style lang="less" scoped>
  .index {
    display: flex;
    flex-direction: row;
    width: 100%;
    height: 100%;
    overflow: hidden;

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

      .index-left-box {
        flex: 1;
        display: flex;
        flex-direction: column;
        padding: 7px 0 7px 7px;
        overflow: hidden;
        background-color: #f3f6f8;

        .index-left-box_header {
          width: 100%;
          min-width: 400px - 14px;
          height: auto;

          .header__navbar {
            display: flex;
            width: 100%;
            align-items: center;
            font-size: 13px;
            text-align: center;
            margin-bottom: 7px;

            .header__navbar_panorama {
              flex: 1;
              margin-right: 5px;
              padding: 5px 0;
              border: 1px solid #dcdfe6;
              transition: all ease 0.3s;
              cursor: pointer;
            }

            .header__navbar_product {
              flex: 1;
              margin-left: 5px;
              padding: 5px 0;
              border: 1px solid #dcdfe6;
              transition: all ease 0.3s;
              cursor: pointer;
            }

            .header__navbar_panorama:hover,
            .header__navbar_product:hover
            {
              border: 1px solid #5e7ce0;
            }

            .header__navbar_actived {
              background-color: #5e7ce0;
              border: 1px solid #5e7ce0;
              color: #fff;
            }
          }

          .header__form {
            border-top: 1px solid #dcdfe6;
            padding-top: 7px;
          }
        }

        .index-left_content {
          flex: 1;
          overflow: hidden;
          border: 1px solid #dcdfe6;
        }
      }

      .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;
      height: 100%;
      position: relative;
      transition: all ease 0.3s;
      background-color: #ffffff;

      .index-middle-box {
        display: flex;
        position: relative;
        width: 100%;
        height: 100%;
        overflow: hidden;

        // ^ 是否收起左侧边栏的图标
        .left-view-more {
          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);
            }
          }
        }
        // / 是否收起左侧边栏的图标

        // ^ 是否收起右侧边栏的图标
        .right-view-more {
          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);
            }
          }
        }
        // / 是否收起右侧边栏的图标
      }
    }
    /* ---- / 中间 ---- */

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

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

      .index-right-box {
        flex: 1;
        display: flex;
        flex-direction: column;
        padding: 7px 7px 7px 0;
        overflow: hidden;
        background-color: #f3f6f8;
      }
    }
    /* ---- / 右边 ---- */
  }
</style>

二、效果如下 ~

相关推荐
编程猪猪侠25 分钟前
Tailwind CSS 自定义工具类与主题配置指南
前端·css
qhd吴飞28 分钟前
mybatis 差异更新法
java·前端·mybatis
YGY Webgis糕手之路1 小时前
OpenLayers 快速入门(九)Extent 介绍
前端·经验分享·笔记·vue·web
患得患失9491 小时前
【前端】【vueDevTools】使用 vueDevTools 插件并修改默认打开编辑器
前端·编辑器
ReturnTrue8681 小时前
Vue路由状态持久化方案,优雅实现记住表单历史搜索记录!
前端·vue.js
UncleKyrie1 小时前
一个浏览器插件帮你查看Figma设计稿代码图片和转码
前端
遂心_1 小时前
深入解析前后端分离中的 /api 设计:从路由到代理的完整指南
前端·javascript·api
你听得到111 小时前
Flutter - 手搓一个日历组件,集成单日选择、日期范围选择、国际化、农历和节气显示
前端·flutter·架构
风清云淡_A1 小时前
【REACT18.x】CRA+TS+ANTD5.X封装自定义的hooks复用业务功能
前端·react.js
@大迁世界1 小时前
第7章 React性能优化核心
前端·javascript·react.js·性能优化·前端框架