1 开发目标
分隔窗体组件旨在提供灵活的窗体分隔功能,支持横向分割与纵向分隔两种类型,并具备拖拽调整窗体比例的功能,同时提供最小比例设置,以防止窗体被过度缩小:
2 详细需求
2.1 分隔窗体类型
(1)横向分割:
- 用户可以在窗体顶部或底部添加一个横向分割条,将窗体分割成上下两部分。
- 分割条的位置可以通过拖拽调整,以改变上下两部分窗体的高度比例。
(2)纵向分隔:
- 用户可以在窗体左侧或右侧添加一个纵向分割条,将窗体分割成左右两部分。
- 分割条的位置同样可以通过拖拽调整,以改变左右两部分窗体的宽度比例。
2.2 鼠标样式切换
(1)鼠标靠近状态:
- 当鼠标指针移动到分割条附近的一定范围内时,分割条应自动变为拖拽样式。
- 拖拽样式可以通过视觉上的变化来体现,例如改变分割条的颜色、形状或添加拖拽图标等。
(2)鼠标远离状态:
- 当鼠标指针离开分割条附近的范围时,分割条应自动恢复到默认样式。
- 默认样式应简洁明了,以便在不需要拖拽调整时保持窗体的整体美观性。
2.3 拖拽调整窗体比例
(1)拖拽过程:
- 用户点击并拖动拖拽样式的分割条时,应能够实时改变窗体的比例。
- 拖拽过程中,应提供平滑的过渡效果,确保窗体布局的调整连贯且自然。
(2)横向分割调整:
- 在横向分割模式下,拖动分割条将改变上下两部分窗体的高度比例。
- 用户可以通过向上或向下拖动分割条来调整上下窗体的相对大小。
(3)纵向分隔调整:
- 在纵向分隔模式下,拖动分割条将改变左右两部分窗体的宽度比例。
- 用户可以通过向左或向右拖动分割条来调整左右窗体的相对大小。
2.4 最小比例设置
(1)设置功能:
- 组件应提供设置最小比例的功能,允许用户自定义窗体在分割调整时的最小比例限制。
- 用户可以通过配置项或API接口来设置最小比例值。
(2)横向分割调整:
- 当用户尝试通过拖拽将窗体调整到小于最小比例时,应阻止进一步的调整操作。
- 此时,可以通过视觉反馈(如提示信息、分割条位置固定等)来告知用户已达到最小比例限制。
3 代码实现
首先创建一个 neat_spliterwidget.js 文件,该文件用于本组件的工具类、目录处理函数的代码构建。
(1)创建分隔窗体的基类:
首先,定义核心数据变量:
class NeatSpliterWidget {
constructor(container,para) {
this.container = container;
this.para = para;
this.wid1 = null;
this.wid2 = null;
this.spliterRatio = para.spliterRatio ?? 0.3;
this.minSpace = 20;
this.spliterSpace = 3; // 切换鼠标样式的间距
this.dragReadyFalg = false;
this.dragActiveFlag = true;
this.dragFalg = false;
this.dragStart = 0;
this.render();
}
接下来,进行基础类型的渲染,包括创建子窗体:
render() {
this.container.style.display = 'flex';
this.wid1Tmp = document.createElement('div');
this.wid2Tmp = document.createElement('div');
this.widSpliter = document.createElement('div');
this.wid1 = document.createElement('div');
this.wid1.style.width = '100%';
this.wid1.style.height = '100%';
this.wid1Tmp.appendChild(this.wid1);
this.wid2 = document.createElement('div');
this.wid2.style.width = '100%';
this.wid2.style.height = '100%';
this.wid2Tmp.appendChild(this.wid2);
}
最后,定义鼠标事件,计算拖拽时的初始位置:
initSpliterEvent() {
let that = this;
if (!that.dragActiveFlag) {
return;
}
this.container.addEventListener("mousedown", function (event) {
if (!that.dragActiveFlag) {
return;
}
if('column' == that.container.style.flexDirection){
that.dragStart = event.clientY - event.currentTarget.offsetTop;
}else{
that.dragStart = event.clientX - event.currentTarget.offsetLeft;
}
if (that.dragReadyFalg) {
that.dragFalg = true;
this.onselectstart = function () { return false; };
} else {
that.dragFalg = false;
this.onselectstart = function () { return true; };
}
});
this.container.addEventListener("mouseup", function (event) {
if (!that.dragActiveFlag) {
return;
}
if('column' == that.container.style.flexDirection){
that.dragStart = event.clientY - event.currentTarget.offsetTop;
}else{
that.dragStart = event.clientX - event.currentTarget.offsetLeft;
}
that.dragFalg = false;
this.onselectstart = function () { return true; };
});
}
}
(2)接下来,开始定义纵向分割窗体的组件(支持水平拖拽):
class NeaterHSpliterWidget extends NeatSpliterWidget {
constructor(container,para) {
super(container,para);
}
render() {
super.render();
this.wid1Tmp.style.width = (this.spliterRatio * 100).toString() + '%';
this.wid1Tmp.style.height = '100%';
this.wid2Tmp.style.width = '10px';
this.wid2Tmp.style.height = '100%';
this.wid2Tmp.style.flex = 1;
this.widSpliter.style.height = '100%';
this.widSpliter.style.width = '1px';
this.widSpliter.style.borderLeft = '1px solid #CACDD1';
this.container.appendChild(this.wid1Tmp);
this.container.appendChild(this.widSpliter);
this.container.appendChild(this.wid2Tmp);
this.initSpliterEvent();
}
上面代码定义了 NeaterHSpliterWidget 的渲染方式,主要是将子窗体以及分割条做水平布局,接下来是处理水平拖拽事件:
initSpliterEvent() {
super.initSpliterEvent();
let that = this;
if (!that.dragActiveFlag) {
return;
}
this.container.addEventListener("mousemove", function (event) {
let clientX = event.clientX - event.currentTarget.offsetLeft;
if (that.dragFalg) {
let dragOffset = clientX - that.dragStart;
let spliterWidth1 = that.wid1Tmp.offsetWidth + dragOffset;
if (spliterWidth1 < that.minSpace || (that.container.offsetWidth - spliterWidth1) < that.minSpace) {
return;
}
that.spliterRatio = spliterWidth1 / that.container.offsetWidth;
that.wid1Tmp.style.width = spliterWidth1 + 'px';
that.dragStart = clientX;
} else {
if (clientX > that.wid1Tmp.offsetWidth - that.spliterSpace && clientX < that.wid1Tmp.offsetWidth + that.spliterSpace + 1) {
that.container.style.cursor = "col-resize";
that.dragReadyFalg = true;
} else {
that.container.style.cursor = "default";
that.dragReadyFalg = false;
}
}
});
}
}
上面代码的核心逻辑是计算更换鼠标样式的位置以及计算拖拽时分隔比例的变化。
(3)然后,定义横向分割窗体的组件(支持垂直拖拽):
class NeaterVSpliterWidget extends NeatSpliterWidget {
constructor(container,para) {
super(container,para);
}
render() {
super.render();
this.container.style.flexDirection = 'column';
this.wid1Tmp.style.height = (this.spliterRatio * 100).toString() + '%';
this.wid1Tmp.style.width = '100%';
this.wid2Tmp.style.height = '10px';
this.wid2Tmp.style.width = '100%';
this.wid2Tmp.style.flex = 1;
this.widSpliter.style.width = '100%';
this.widSpliter.style.borderBottom = '1px solid #CACDD1';
this.container.appendChild(this.wid1Tmp);
this.container.appendChild(this.widSpliter);
this.container.appendChild(this.wid2Tmp);
this.initSpliterEvent();
}
上面代码定义了 NeaterVSpliterWidget 的渲染方式,主要是将子窗体以及分割条做垂直布局,接下来是处理垂直拖拽事件:
initSpliterEvent() {
super.initSpliterEvent();
let that = this;
if (!that.dragActiveFlag) {
return;
}
this.container.addEventListener("mousemove", function (event) {
let clientY = event.clientY - event.currentTarget.offsetTop;
if (that.dragFalg) {
let dragOffset = clientY - that.dragStart;
let spliterHeight1 = that.wid1Tmp.offsetHeight + dragOffset;
if (spliterHeight1 < that.minSpace || (that.container.offsetHeight - spliterHeight1) < that.minSpace) {
return;
}
that.spliterRatio = spliterHeight1 / that.container.offsetHeight;
that.wid1Tmp.style.height = spliterHeight1 + 'px';
that.dragStart = clientY;
} else {
if (clientY > that.wid1Tmp.offsetHeight - that.spliterSpace && clientY < that.wid1Tmp.offsetHeight + that.spliterSpace + 1) {
that.container.style.cursor = "row-resize";
that.dragReadyFalg = true;
} else {
that.container.style.cursor = "default";
that.dragReadyFalg = false;
}
}
});
}
}
上面代码的核心逻辑是计算更换鼠标样式的位置以及计算拖拽时分隔比例的变化。
至此,整个分割窗体组件构建结束。
(4)完成目录导航功能的组件的代码编写后,可以创建 neat_spliterwidget.html 文件,调用该组件:
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta http-equiv="X-UA-Compatible" content="ie=edge" />
<title>spliter widget</title>
<style>
html {
height: 100%;
}
body {
margin: 0;
height: 100%;
}
</style>
</head>
<body>
<div id="divMain" style="height: 400px;width: 600px;margin: 20px;border: 1px solid #aaa;"></div>
</body>
<script src="./neat_spliterwidget.js"></script>
<script>
let para = {
spliterRatio:0.3,
}
let hSpliterWidget = new NeaterHSpliterWidget(document.getElementById('divMain'), para);
para.spliterRatio = 0.7;
let vSpliterWidget = new NeaterVSpliterWidget(hSpliterWidget.wid2, para);
</script>
</html>