如何创建可调整大小的拆分视图,类似 Gitlab Code Review 界面
这篇文章介绍如何创建一个可调整大小的拆分视图,什么意思呢?请看如下示例。
如上例所示,拖地中间的竖条,就可以改变视图的大小。
实现方案
先定义好 HTML 结构。
<div style="display: flex">
<!-- 左侧视图元素 -->
<div>Left</div>
<!-- 拖动条 -->
<div class="resizer" id="dragMe"></div>
<!-- 右侧元素 -->
<div>Right</div>
</div>
定义一个容器元素,使用 flex 布局,让所有子元素都在同一行展示。定义三个子元素,中间元素为拖动条,左右两边为视图。
实现拖动
拖动元素的方式在之前的文章 如何实现一个自定义的 range slider?元素拖动其实很简单 有描述过,有兴趣的 JYM 可以看看。
我们需要记录可拖动元素的初始位置以及拖动的距离,所以需要使用到 mousedown 事件。
const resizer = document.getElementById('dragMe');
const leftSide = resizer.previousElementSibling;
const rightSide = resizer.nextElementSibling;
// 鼠标默认位置
let x = 0;
let y = 0;
// 左侧元素的宽度
let leftWidth = 0;
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);
注意是在 document 元素上监听 mousemove
和 mouseup
事件。
查找一个元素的兄弟元素,我们可以使用 previousElementSibling
和 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}%`;
};
左侧元素的宽度使用百分比值,而不是绝对像素值。
因为我们给父级容器元素设置了 flex 布局,所以我们可以很轻松的让右侧元素自适应宽度。
<div style="display: flex">
<!-- Left element -->
...
<!-- The resizer -->
...
<!-- Right element -->
<div style="flex: 1 1 0%;">Right</div>
</div>
如上示例,使用 flex: 1 1 0%;
即可让右侧元素跟随左侧宽度自适应。
一些优化
当我们移动鼠标的时候,我们需要更新鼠标的展现形式。
const mouseMoveHandler = function (e) {
// ...
resizer.style.cursor = 'col-resize';
};
但是这样做会导致一个问题,当我们移动鼠标的时候,我们会看到鼠标的展示形式在闪烁。这是因为鼠标在拖动的过程中离开了 .resizer
元素,然后又回到此元素上,反复如此。
所以我们需要在鼠标拖动的时候,给 document 元素也加上鼠标展示样式。
const mouseMoveHandler = function (e) {
// ...
document.body.style.cursor = 'col-resize';
};
然后在拖动过程中,我们还需要阻止内容的选中。
const mouseMoveHandler = function (e) {
// ...
leftSide.style.userSelect = 'none';
leftSide.style.pointerEvents = 'none';
rightSide.style.userSelect = 'none';
rightSide.style.pointerEvents = 'none';
};
使用 userSelect
和 pointerEvents
属性可以做到这点,我们需要在左右两侧的元素上都加上这个样式属性。
然后,当松开鼠标时,也就是 mouseup
事件中,将上述优化给移还原。
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');
document.removeEventListener('mousemove', mouseMoveHandler);
document.removeEventListener('mouseup', mouseUpHandler);
};
全文完,如果觉得这篇文章对你有用,欢迎 点赞 👍、评论 ✍️、收藏 👀
转载自:https://juejin.cn/post/7134720399119482916