一个前端页面各布局块自由伸缩的js插件
可在任意两个元素之间插入伸缩控件,不需要改元素代码,添加插件代码即可。
效果:
用法:
引入js
<script src="./flexible-bar.js"></script>
在需要伸缩的两个元素之间添加伸缩块:
<flexible-bar size="10px" lineColor="#409eff" handleColor="white" hoverShadow="0 0 3px #333"></flexible-bar>
配置项:
lineColor:分隔条的颜色,默认#ddd
size: 分隔条的粗细值,默认3px
handle:是否显示手柄,默认yes
handleColor:手柄的颜色,默认white
hoverShadow:鼠标移动到分隔条时显示的阴影
示例页面代码:
<!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>
插件js源码:
/** * 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事件转给伸缩条。 * */ 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 = `overflow:visible;display:flex;justify-content: center;align-items: center;background: ${separatorBackground};position:relative;transition:box-shadow .5s ease;` // pointer-events:none; let handleCssText = `overflow:visible;position:absolute;background:${handleBackground};border-radius:15px;box-shadow: 0 0 2px #000;display:flex;justify-content: center;align-items: center;` let handleSymbolCssText = `overflow:visible;` // 分隔栏 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) }) }) })()