在这篇文章中,我们将添加一个元素来调整给定元素的子元素的大小。基本结构如下:
<div style="display: flex">
<div>Left</div>
<div class="resizer" id="dragMe"></div>
<div>Right</div>
</div>
为了将 left
、resizer
和 right
元素放在同一行,我们将 display: flex
样式添加到父级。
在我们的例子中,可以水平拖动 resizer
。首先,当用户开始单击调整器时,我们必须存储鼠标位置和左侧的宽度:
const resizer = document.getElementById('dragMe')
const leftSide = resizer.previousElementSibling
const rightSide = resizer.nextElementSibling
// 鼠标的当前位置
let x = 0
let y = 0
// 左侧宽度
let leftWidth = 0
// 处理当用户拖动大小调整器时触发的 mousedown 事件
const mouseDownHandler = function (e) {
// 获取当前鼠标位置
x = e.clientX
y = e.clientY
leftWidth = leftSide.getBoundingClientRect().width
// 将侦听器附加到 document
document.addEventListener('mousemove', mouseMoveHandler)
document.addEventListener('mouseup', mouseUpHandler)
}
// 连接处理程序
resizer.addEventListener('mousedown', mouseDownHandler)
从标签的结构来看,左侧和右侧是 resizer
的上一个兄弟和下一个兄弟。它们可以被检索如上所述:
const leftSide = resizer.previousElementSibling
const rightSide = resizer.nextElementSibling
接下来,当用户移动鼠标时,我们确定鼠标移动了多远,然后更新左侧的宽度:
const mouseMoveHandler = function (e) {
// 鼠标移动了多远
const dx = e.clientX - x
const dy = e.clientY - y
const newLeftWidth =
((leftWidth + dx) * 100) / resizer.parentNode.getBoundingClientRect().width
leftSide.style.width = `${newLeftWidth}%`
}
这里需要注意两点:
- 左侧的宽度是根据父级宽度的百分比数设置的。它保持了左侧和侧边宽度的比例,并在用户调整浏览器大小时使两侧看起来很好。
- 如果我们总是强制其采用剩余宽度,则无需更新右侧的宽度:
<div style="display: flex">
<!-- ... -->
<div style="flex: 1 1 0%;">Right</div>
</div>
当用户移动大小调整器时,我们应更新其光标:
const mouseMoveHandler = function (e) {
// ...
resizer.style.cursor = 'col-resize'
}
但它引发了另一个问题。只要用户移动鼠标,我们就会看到默认的鼠标光标,因为鼠标不在大小调整器的顶部。用户将看到屏幕闪烁,因为光标不断变化。
为了解决这个问题,我们为整个页面设置了光标:
const mouseMoveHandler = function (e) {
// ...
document.body.style.cursor = 'col-resize'
}
我们还设置 user-select
和 pointer-events
为 none
,以防止防止两侧的鼠标事件和文本选择:
const mouseMoveHandler = function (e) {
// ...
leftSide.style.userSelect = 'none'
leftSide.style.pointerEvents = 'none'
rightSide.style.userSelect = 'none'
rightSide.style.pointerEvents = 'none'
}
这些样式在用户停止移动鼠标后立即移除:
const mouseUpHandler = function () {
resizer.style.removeProperty('cursor')
document.body.style.removeProperty('cursor')
leftSide.style.removeProperty('user-select')
leftSide.style.removeProperty('pointer-events')
rightSide.style.removeProperty('user-select')
rightSide.style.removeProperty('pointer-events')
// 移除 mousemove 和 mouseup 的处理程序
document.removeEventListener('mousemove', mouseMoveHandler)
document.removeEventListener('mouseup', mouseUpHandler)
}
很容易支持垂直拆分侧面。我们现在更新顶部的高度,而不是更新左侧的宽度:
const prevSibling = resizer.previousElementSibling
let prevSiblingHeight = 0
const mouseDownHandler = function (e) {
const rect = prevSibling.getBoundingClientRect()
prevSiblingHeight = rect.height
}
const mouseMoveHandler = function (e) {
const h =
((prevSiblingHeight + dy) * 100) /
resizer.parentNode.getBoundingClientRect().height
prevSibling.style.height = `${h}%`
}
当用户移动大小调整器元素时,我们也会更改光标:
const mouseMoveHandler = function (e) {
// ...
resizer.style.cursor = 'row-resize'
document.body.style.cursor = 'row-resize'
}
假设右侧希望拆分为两个可调整大小的元素。
我们目前有两个 resizer
元素。为了指示每个缩放器的拆分方向,我们添加了一个自定义属性 data-direction
:
<div style="display: flex">
<div>Left</div>
<div class="resizer" data-direction="horizontal"></div>
<div style="display: flex; flex: 1 1 0%; flex-direction: column">
<div>Top</div>
<div class="resizer" data-direction="vertical"></div>
<div style="flex: 1 1 0%">Bottom</div>
</div>
</div>
稍后,我们可以从 resizer
元素中检索属性:
const direction = resizer.getAttribute('data-direction') || 'horizontal'
设置前一个兄弟的宽度或高度的逻辑取决于方向:
const mouseMoveHandler = function (e) {
switch (direction) {
case 'vertical':
const h =
((prevSiblingHeight + dy) * 100) /
resizer.parentNode.getBoundingClientRect().height
prevSibling.style.height = `${h}%`
break
case 'horizontal':
default:
const w =
((prevSiblingWidth + dx) * 100) /
resizer.parentNode.getBoundingClientRect().width
prevSibling.style.width = `${w}%`
break
}
const cursor = direction === 'horizontal' ? 'col-resize' : 'row-resize'
resizer.style.cursor = cursor
document.body.style.cursor = cursor
// ...
}