一个前端页面各布局块自由伸缩的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)
        })
    })

})()

 

posted @ 2024-07-17 17:49  jsper  阅读(82)  评论(0编辑  收藏  举报