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>
监听事件
用户按下鼠标并移动鼠标指针,会触发 mousedown
、mousemove
事件,松开鼠标会触发 mouseup
事件,于是拖动的时机和顺序是:
mousedown
-> 拖动 flag truemousemove
-> 检查拖动 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
}
}
})