本插件为纯js代码,非react、vue组件。(不过可以自己稍做改动实现)
公司有几个老系统,界面有左右布局的,有上下布局,还有组合布局的。为了提升用户体验,提出了一个需求,就是操作区域可伸缩。
网上找了一些组件,大部分需要改现有布局代码或者库的方向不精准用起来别扭。想想这种场景还挺多的,索性自己实现了一个。
目标:
- 不动现有html容器元素代码,不改布局。
- 用法简单,全自动。
- 支持样式配置。
先看用法:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>example</title>
<script src="./flexible-bar.js"></script>
</head>
<body style="margin:0;padding:0;">
<div style="width:100%;height:100vh;display: flex;flex-direction: column;">
<div style="height:100px;background-color:#eee;">
Top Area
</div>
<flexible-bar></flexible-bar>
<div style="flex:1;display: flex;">
<!-- 左侧栏 -->
<div style="width:100px;background-color:#ffe;"> div 1 </div>
<flexible-bar size="10px" lineColor="#409eff" handleColor="white" hoverShadow="0 0 3px #333"></flexible-bar>
<!-- 右侧栏 -->
<div style="width:400px;background-color:#ffe;overflow: hidden;">
<!-- 右上 -->
<div style="width:100%;height:50%;background:#fef;"> div 2 </div>
<flexible-bar handleColor="#409eff"></flexible-bar>
<!-- 右下 -->
<div style="width:100%;height:50%;background:#eff;"> div 3 </div>
</div>
</div>
</div>
</body>
</html>
只需要在两个容器元素之间添加一行代码: <flexible-bar></flexible-bar>
效果:
这样就可以用鼠标拖拽对各区域进行尺寸控制了。
可配置属性:
lineColor:分隔条的颜色,默认#ddd
size: 分隔条的粗细值,默认3px
handle:是否显示手柄,默认yes
handleColor:手柄的颜色,默认white
hoverShadow:鼠标移动到分隔条时显示的阴影
用法示例:
<flexible-bar size="10px" lineColor="#409eff" handleColor="white" hoverShadow="0 0 3px #333"></flexible-bar>
组件源代码
/**
* flexible-bar.js 1.0 容器元素伸缩条
* thejava@163.com
*
* 用法示例:
* <flexible-bar size="10px" lineColor="#409eff" handleColor="white" hoverShadow="0 0 3px #333"></flexible-bar>
*
* 可配置属性:
* lineColor:分隔条的颜色,默认#ddd
* size: 分隔条的粗细值,默认3px
* handle:是否显示手柄,默认yes
* handleColor:手柄的颜色,默认white
* hoverShadow:鼠标移动到分隔条时显示的阴影
*/
(() => {
function flexibleBar(fb) {
const prev = fb.previousElementSibling // 上一个兄弟节点
const next = fb.nextElementSibling // 下一个兄弟节点
console.log('prev', prev)
console.log('next', next)
if (!prev || !next) {
throw new Error('<flexible-bar>必须放到两个元素之间')
}
/**
* 判断横向还是纵向,
* 若上一个的bottom大于等于下一个的top,则是纵向布局的,
* 若上一个的right大于等于下一个的left,则是左右布局的,
* 若两个情况同时满足,那可能是以下这种情况:
* ---------
* 口
* 口
* ---------
* 这种情况无法支持,抛出错误。
*
* 原理:
* 通过控件所放位置的兄弟节点来生成伸缩条,点住条子拖动时动态改变兄弟节点的宽度或高度实现伸缩效果。
* 点击伸缩条时在伸缩条dom里生成一个position:absolute的div节点,高宽度足够大,用来接收鼠标移动事件。全透明,无边框。鼠标按键松开时取消div样式。
* 可以在伸缩条上拖拽,也可以在中间手柄上拖拽,手柄也是一个position:absolute的div节点,做了鼠标事件转移,在上面按住鼠标时会mousedown事件转给伸缩条。
*
* 可配置属性:
* lineColor:分隔条的颜色,默认#ddd
* size: 分隔条的粗细值,默认3px
* handle:是否显示手柄,默认yes
* handleColor:手柄的颜色,默认white
* hoverShadow:鼠标移动到分隔条时显示的阴影
*/
let prevRect = prev.getBoundingClientRect()
let nextRect = next.getBoundingClientRect()
let direction = ''
if (prevRect.bottom <= nextRect.top) {
direction += 'column'
}
if (prevRect.right <= nextRect.left) {
direction += 'row'
}
if (direction.includes('column') && direction.includes('row')) {
throw new Error('布局不支持伸缩条')
}
let separatorBackground = fb.getAttribute('lineColor') || '#ddd'
let size = fb.getAttribute('size') || '3px'
let showHandle = fb.getAttribute('handle') || 'yes'
let handleBackground = fb.getAttribute('handleColor') || 'white'
let hoverShadow = fb.getAttribute('hoverShadow') || ''
let separatorCssText = `display:flex;justify-content: center;align-items: center;background: ${separatorBackground};position:relative;transition:box-shadow .5s ease;`
// pointer-events:none;
let handleCssText = `position:absolute;background:${handleBackground};border-radius:15px;box-shadow: 0 0 2px #000;display:flex;justify-content: center;align-items: center;`
let handleSymbolCssText = ``
// 分隔栏
const separator = document.createElement('div')
separator.style.position = 'absolute'
separator.style.width = '100%'
separator.style.height = '100%'
// separator.style.background = '#0008'
if (direction === 'column') {
// 取两个块最大的那个块的边作为伸缩柄的尺寸
const width = prevRect.width > nextRect.width ? prevRect.width : nextRect.width
separatorCssText += `width:${width}px;height:${size};cursor: ns-resize;`
handleCssText += `width:50px;height:10px;flex-direction: column;`
handleSymbolCssText = `width:60%;height:1px;background:#aaa;margin:1px;`
}
if (direction === 'row') {
// 取两个块最大的那个块的边作为伸缩柄的尺寸
const height = prevRect.height > nextRect.height ? prevRect.height : nextRect.height
separatorCssText += `width:${size};height:${height}px;cursor: ew-resize;`
handleCssText += `width:10px;height:50px;flex-direction: row;`
handleSymbolCssText = `width:1px;height:60%;background:#aaa;margin:1px;`
}
let mouseDownPageX = 0
let mouseDownPageY = 0
let mouseStatus = 'up'
let handleMouseDowned = false
separator.onmousedown = (e) => {
mouseStatus = 'down'
if (!handleMouseDowned) {
mouseDownPageX = e.pageX
mouseDownPageY = e.pageY
}
// console.log('mouseDownPageX: ', mouseDownPageX, 'mouseDownPageY: ', mouseDownPageY)
prevRect = prev.getBoundingClientRect()
nextRect = next.getBoundingClientRect()
separator.style.zIndex = 65535
if (direction === 'column') {
separator.style.width = '100%'
separator.style.height = '500px'
} else if (direction === 'row') {
separator.style.height = '100%'
separator.style.width = '500px'
}
e.preventDefault()
}
let cha = 0
let newPrevHeight = 0
let newNextHeight = 0
let newPrevWidth = 0
let newNextWidth = 0
separator.onmousemove = (e) => {
handleMouseDowned = false
// console.log(e.pageX, e.pageY)
if (mouseStatus !== 'down') {
return
}
if (direction === 'column') {
cha = e.pageY - mouseDownPageY
newPrevHeight = prevRect.height + cha
newNextHeight = nextRect.height - cha
if (newPrevHeight < 2 || newNextHeight < 2) {
return
}
prev.style.height = newPrevHeight + 'px'
next.style.height = newNextHeight + 'px'
} else if (direction === 'row') {
cha = e.pageX - mouseDownPageX
newPrevWidth = prevRect.width + cha
newNextWidth = nextRect.width - cha
if (newPrevWidth < 2 || newNextWidth < 2) {
return
}
prev.style.width = newPrevWidth + 'px'
next.style.width = newNextWidth + 'px'
}
}
const resetHandle = () => {
separator.style.width = '100%'
separator.style.height = '100%'
separator.style.zIndex = 'unset'
}
separator.onmouseup = (e) => {
mouseStatus = 'up'
handleMouseDowned = false
resetHandle()
e.preventDefault()
}
separator.onmouseenter = (e) => {
// console.log('separator.onmouseenter')
fb.style.boxShadow = hoverShadow
}
separator.onmouseleave = (e) => {
// console.log('separator.onmouseleave')
fb.style.boxShadow = 'unset'
mouseStatus = 'leave'
resetHandle()
e.preventDefault()
}
fb.style.cssText = separatorCssText
fb.appendChild(separator)
if (showHandle === 'yes') {
// 拖拽手柄
const handle = document.createElement('div')
const line1 = document.createElement('div')
const line2 = document.createElement('div')
line1.style.cssText = handleSymbolCssText
line2.style.cssText = handleSymbolCssText
handle.appendChild(line1)
handle.appendChild(line2)
handle.style.cssText = handleCssText
handle.onmousedown = (e) => {
e.preventDefault()
handleMouseDowned = true
mouseDownPageX = e.pageX
mouseDownPageY = e.pageY
var event = new MouseEvent('mousedown', {
bubbles: true, // 是否允许事件冒泡
cancelable: true, // 是否可以取消事件默认行为
view: window
});
separator.dispatchEvent(event);
}
handle.onmouseenter = (e) => {
fb.style.boxShadow = hoverShadow
}
handle.onmouseleave = (e) => {
fb.style.boxShadow = 'unset'
}
fb.appendChild(handle)
}
}
// 初始化
window.addEventListener('load', () => {
const flexibles = document.querySelectorAll('flexible-bar')
flexibles.forEach(fb => {
flexibleBar(fb)
})
})
// 若发生窗体变动则删除已生成的dom,重新初始化
window.addEventListener('resize', () => {
const flexibles = document.querySelectorAll('flexible-bar')
flexibles.forEach(fb => {
fb.childNodes.forEach(child => {
fb.removeChild(child)
})
flexibleBar(fb)
})
})
})()
这是一个快速实现的版本,代码不漂亮,能用吧,看不惯请自行优化。
最后:
大龄程序员求成都工作,目前base北京,全栈能力,质量高,要求低。