基于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>

二、效果如下 ~

相关推荐
吕彬-前端40 分钟前
使用vite+react+ts+Ant Design开发后台管理项目(五)
前端·javascript·react.js
学前端的小朱43 分钟前
Redux的简介及其在React中的应用
前端·javascript·react.js·redux·store
guai_guai_guai1 小时前
uniapp
前端·javascript·vue.js·uni-app
bysking2 小时前
【前端-组件】定义行分组的表格表单实现-bysking
前端·react.js
王哲晓2 小时前
第三十章 章节练习商品列表组件封装
前端·javascript·vue.js
fg_4112 小时前
无网络安装ionic和运行
前端·npm
理想不理想v2 小时前
‌Vue 3相比Vue 2的主要改进‌?
前端·javascript·vue.js·面试
酷酷的阿云2 小时前
不用ECharts!从0到1徒手撸一个Vue3柱状图
前端·javascript·vue.js
微信:137971205872 小时前
web端手机录音
前端
齐 飞2 小时前
MongoDB笔记01-概念与安装
前端·数据库·笔记·后端·mongodb