需求
实现一个左右布局,其中左侧边栏可以通过拖拽中间的分割线改变宽度。
原理
在位置确定方面
我们拖动的左右交接的那根线(图中浅蓝色那个竖线),并非左右块的边界,而是一个比较窄的div
,我这里设置宽度是4px
;
如果,我们假设左侧边栏的宽度是X,那么有以下推论:
- 在
absolute
布局下,左侧边栏的位置就可以设置为top:0,left:0,bottom:0,width:X
; - 右侧内容部分的位置就可以设为
top:0,bottom:0,right:0,left:X
; - 拖拽条的位置是
top:0,bottom:0,left:X-2,width:4px
所以,我们只需要一个React的状态变量X
就可以确定所有的内容位置和大小。
在拖拽动作方面
- 我们在拖拽条上点击鼠标左键
- 然后进入拖拽状态(整个内容区域被mask遮盖)
- 在拖拽状态下(鼠标左键没有松开),我们左右移动鼠标,通过
e.pageX
获取鼠标的位置,从而确定X
的值。 - 当鼠标左键抬起,退出拖拽状态,mask消失,拖拽过程结束。
这个过程涉及三个动作:
- 拖拽条上的鼠标左键点击
- mask上的鼠标拖拽
- mask上的鼠标左键抬起
到此为止,我们的原理已经非常明白了,我们需要定义两个状态变量,一个是确定左侧宽度的X,另一个是表示进入拖拽状态的Flag。
代码
以下是完整代码:
index.js
* {
padding: 0;
margin: 0;
box-sizing: border-box;
}
.resizeable-layout {
width: 100%;
height: 100vh;
}
.left {
position: absolute;
left: 0;
top: 0;
bottom: 0;
background-color: antiquewhite;
}
.right {
position: absolute;
top: 0;
bottom: 0;
right: 0;
background-color: beige;
}
.slidebar {
position: absolute;
top: 0;
bottom: 0;
width: 4px;
cursor: col-resize;//鼠标箭头提示可拖拽
}
.dragging-mask {
position: absolute;
left: 0;
top: 0;
right: 0;
bottom: 0;
background-color: rgba(0, 0, 0, 0.1);//拖拽过程中页面微微变灰
cursor: col-resize;
}
App.js
import { useState } from 'react'
import './index.css'
export default function ResizeableLayout() {
const [leftWidth, setLeftWidth] = useState(parseInt(localStorage.getItem('leftWidth')) || 150)
const [dragging, setDragging] = useState(false)
const slidebarColorOnDragging = 'lightblue'
const slidebarColorDefault = 'rgba(0,0,0,0)'
const [slidebarColor, setSlidebarColor] = useState(slidebarColorDefault)
function handleLeftKeyDown(e) {
if (e.button === 0) {//判断是鼠标左键按下
setDragging(true)
setSlidebarColor(slidebarColorOnDragging)
}
}
function handleMouseMove(e) {
if (dragging) {
setLeftWidth(e.pageX)
}
}
function handleMouseUp(e) {
setDragging(false)
setSlidebarColor(slidebarColorDefault)
localStorage.setItem('leftWidth', leftWidth)
}
return (
<div className="resizeable-layout">
<div className="left" style={{ width: leftWidth }}>
left
</div>
<div className="right" style={{ left: leftWidth }}>
right
</div>
<div className='slidebar' style={{ left: leftWidth - 2, background: slidebarColor }} onMouseDown={(e) => handleLeftKeyDown(e)}></div>
{dragging && <div className='dragging-mask' onMouseMove={(e) => handleMouseMove(e)} onMouseUp={(e) => handleMouseUp(e)} ></div>}
</div>
)
}