[js] 实现侧边菜单可用鼠标拖动调整大小的功能

16 篇文章 0 订阅

element-ui 提供了一个 nav-menu 组件,默认情况下该组件没有拖动调整大小的功能。下面通过代码为 nav-menu 添加这个功能。

样式问题

鼠标悬于 nav-menu 上时,鼠标指针默认为 pointer,为了更好的指示调整能够调整菜单大小的功能,可以设置鼠标样式为 cursor: col-resize。当然显示为列调整大小的鼠标指针不能为一整个菜单,我这里的实现方案时给菜单添加一个不可见的 div,然后鼠标悬于这个 div 上时,才显示col-resize指针。

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
  <style>
    body {
      margin: 0;
      position: relative;
      height: 100vh;
    }

    .menu-wrapper {
      position: absolute;
      left: 0;
      top: 0;
      height: 100%;
      width: 300px;
      background-color: #ccc;
    }

    .resize-bar {
      position: absolute;
      top: 0;
      right: 0px;
      width: 10px;
      height: 100%;
      cursor: col-resize;
      background-color: orange;
    }

    .content {
      margin-left: 300px;
      height: 100%;
      background-color: #999;
    }
  </style>
</head>

<body>
  <div class="menu-wrapper">
    <div class="resize-bar"></div>
  </div>
  <div class="content"></div>
</body>

</html>

监听事件

用户按下鼠标并移动鼠标指针,会触发 mousedownmousemove 事件,松开鼠标会触发 mouseup 事件,于是拖动的时机和顺序是:

  • mousedown -> 拖动 flag true
  • mousemove -> 检查拖动 flag 是否为 true,如果是执行拖动逻辑
  • mouseup -> 设置拖动 flag 为 false,并且可以取消事件监听等方法

resize-bar 添加 mousedown 监听,因为只有在这个元素上按下鼠标才表示要调整菜单大小

window 添加 mousemove 监听,能够避免移动鼠标时,元素宽度没更上鼠标指针而导致的移动中断

同理给 window 添加 mouseup 监听。

let resizing = false
resizeBarElement.addEventListener('mousedown', () => {
	resizing = true
})

window.addEventListener('mousemove', handleResizeMenu) // 见下文
window.addEventListener('mouseup', () => {
	resizing = false
})

大小控制

实际上控制 menu 的大小只需依赖鼠标的偏移值,在 mousemove 的回调中,获取 e.screenX 就能得知当前鼠标的位置,把菜单的大小设置为鼠标的位置即可。

function handleResizeMenu(e) {
	if(!resizing) {
		return ;
	}
	
	const { screenX } = e
	
	// 加上这两行代码,避免移动鼠标时选择界面元素
	e.preventDefault()
	e.stopPropagation()

	menu.style.width = screenX + 'px'
}

全部代码

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
  <style>
    body {
      margin: 0;
      position: relative;
      height: 100vh;
    }

    .menu-wrapper {
      position: absolute;
      left: 0;
      top: 0;
      height: 100%;
      width: 300px;
      background-color: #ccc;
    }

    .resize-bar {
      position: absolute;
      top: 0;
      right: 0px;
      width: 10px;
      height: 100%;
      cursor: col-resize;
      background-color: orange;
    }

    .content {
      margin-left: 300px;
      height: 100%;
      background-color: #999;
    }
  </style>
</head>

<body>
  <div class="menu-wrapper">
    <div class="resize-bar"></div>
  </div>
  <div class="content"></div>

  <script>
    let resizing = false
    const menu = document.querySelector('.menu-wrapper')
    const resizeBar = document.querySelector('.resize-bar')

    resizeBar.addEventListener('mousedown', () => {
      resizing = true
    })

    window.addEventListener('mousemove', (e) => {
      if (resizing) {
        e.preventDefault()
        e.stopPropagation()
        const { screenX } = e
        menu.style.width = screenX + 'px'
      }
    })

    window.addEventListener('mouseup', () => {
      resizing = false;
    })
  </script>
</body>

</html>

这里并没有动态调整 content 的 margin-left,感兴趣的同学可以加上。

优化

上面的代码没有限制鼠标移动的监听频率,会有性能浪费。我们可以在 mousemove 监听中计算鼠标的偏移量,如果偏移量大于指定值才调整大小。具体的实现思路是初始化一个 prevCursorOffset = -1,然后在监听代码中判断:

let prevCursorOffset = -1
window.addEventListener('mousemove', (e) => {
  if (resizing) {
   	e.preventDefault()
    e.stopPropagation()
    const { screenX } = e
    
  	if(prevCursorOffset === -1) {
		prevCursorOffset = screenX
	// 鼠标偏移量大于十时执行一次大小调整
	} else if (Math.abs(prevCursorOffset - screenX) >= 10) {
    	menu.style.width = screenX + 'px'
    	prevCursorOffset = screenX
    }
  }
})
  • 3
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值