Skip to content

Latest commit

 

History

History
217 lines (166 loc) · 5.85 KB

创建可调整大小的拆分视图.md

File metadata and controls

217 lines (166 loc) · 5.85 KB

创建可调整大小的拆分视图

在这篇文章中,我们将添加一个元素来调整给定元素的子元素的大小。基本结构如下:

<div style="display: flex">
  <div>Left</div>
  <div class="resizer" id="dragMe"></div>
  <div>Right</div>
</div>

为了将 leftresizerright 元素放在同一行,我们将 display: flex 样式添加到父级。

拖动 resizer 元素时更新左侧的宽度

在我们的例子中,可以水平拖动 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-selectpointer-eventsnone,以防止防止两侧的鼠标事件和文本选择:

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

  // ...
}

查看效果