Vue之可拖动容器大小(ResizableContainer)

发布时间:2024-11-28 16:01

Vue之ResizableContainer

效果

\"Vue之可拖动容器大小(ResizableContainer)_第1张图片\"

代码

<template>
    <div class=\"container\">
        <div class=\"resizable-container\" v-panel-resize:vertical=\"handleResize\">
            <div class=\"item item-1 wrapper\" weight=\"0.3\">1div>
            <div class=\"item item-2 wrapper\" weight=\"0.35\">2div>
            <div class=\"item item-3 wrapper\" weight=\"0.35\">3div>
        div>
    div>
template>
<script>
import PanelResize from \'@/directive/resizer\'

export default {
    directives: {
        PanelResize
    },
    data: () => ({
    	
    }),
    mounted() {
    	
    },
    methods: {
        handleResize(weights) {
            console.log(weights)
        }
    }
};
</script>
<style lang=\"scss\">
.resizable-container {
    display: flex;
    flex: 1;
    flex-direction: column;
    align-items: stretch;
    min-width: 0;
    min-height: 0;
    height: 100%;

    &.horizontal {
        flex-direction: row;
    }

    .wrapper {
        display: flex;
        flex: 1;
        flex-direction: column;
        align-items: stretch;
        overflow: hidden;
        min-width: 20px;
        min-height: 20px;

        &.horizontal {
            flex-direction: row;
        }
    }
}

.ide-divider {
    position: absolute;
    z-index: 97;

    &:after {
        position: absolute;
        background-color: #505050;
        content: \"\";
    }

    &.horizontal {
        right: 0;
        top: 0;
        width: 7px;
        height: 100%;
        margin: 0 -3px;
        cursor: ew-resize;

        &:after {
            top: 0;
            bottom: 0;
            left: 3px;
            width: 1px;
        }
    }

    &.vertical {
        left: 0;
        bottom: 0;
        width: 100%;
        height: 7px;
        margin: -3px 0;
        cursor: ns-resize;

        &:after {
            left: 0;
            right: 0;
            top: 3px;
            height: 1px;
        }
    }
}


.container {
    width: 400px;
    height: 450px;
    margin: 50px auto;
    border: 2px solid rgb(18, 78, 243);
}
.item {
}
.item-1 {
    background-color: pink;
}
.item-2 {
    background-color: palegoldenrod;
}
.item-3 {
    background-color: peachpuff;
}
style>

指令

import { hasClass } from \"../../utils\";

const resizer = {
    bind(el, binding) {
        binding.arg = binding.arg || \'vertical\'

        const handleResizer = typeof binding.value === \'function\' ? binding.value : () => {}

        const getChildren = (el) => {
            let childs = el.childNodes || []
            let children = []
            childs.forEach(child => {
                if (child.nodeType === 1 && !hasClass(child, \'ide-divider\') && child.hasAttribute(\'weight\')) {
                    children.push(child)
                }
            })
            return children
        }

        const createDivider = (el, binding) => {
            el.style.position = \'relative\'
            let divider = document.createElement(\'div\')
            divider.setAttribute(\'class\', \'ide-divider \' + binding.arg)
            el.appendChild(divider)
            return divider
        }

        const handleResize = (targetElement, clientX, clientY, index, nextIndex, weights) => {
            const horizontal = binding.arg === \'horizontal\' ? true : false;
            const { left, top } = targetElement.getBoundingClientRect();
            const { offsetWidth, offsetHeight } = targetElement;
            const position = horizontal ? clientX - left : clientY - top;
            const containerSize = horizontal ? offsetWidth : offsetHeight;

            /**
             * 1.计算总共的比重
             * 2.计算resize前,当前容器它之前的容器比重之和
             */
            let totalWeight = 0;
            let subtotalWeight = 0;

            weights.forEach((weight, i) => {
                totalWeight += weight;

                if (i < nextIndex) subtotalWeight += weight;
            });

            // console.log(\'totalWeight\', totalWeight, subtotalWeight, index)

            /**
             * 计算resize后的新比重
             *
             *   newWeight        position
             * ———————————— = ————————————————
             *  totalWeight     containerSize
             */

            const newWeight = (position / containerSize) * totalWeight;
            let deltaWeight = newWeight - subtotalWeight;

            /**
             * 能调整的最大最小的比重
             */
            deltaWeight = Math.max(deltaWeight, -weights[index]);
            deltaWeight = Math.min(deltaWeight, weights[nextIndex]);

            /**
             * 前一个容器  +=
             * 当前容器    -=
             */
            weights[index] += deltaWeight;
            weights[nextIndex] -= deltaWeight;

            return weights
        }

        let target
        
        const handleMouseDown = (e) => {
            target = e.target
            e.preventDefault()
            document.addEventListener(\'mousemove\', handleMouseMove, false)
            document.addEventListener(\'mouseup\', handleMouseUp, false)
        }

        const handleMouseMove = (e) => {
            let dx = e.clientX
            let dy = e.clientY
            let children = getChildren(el)
            let weights = []
            let index = 0, nextIndex

            children.forEach((child, i) => {
                let weight = +(child.getAttribute(\'weight\') || (1 / children.length))
                weights.push(weight)
                if (target && target.parentElement === child) {
                    index = i
                    nextIndex = i + 1
                }
            })

            let newWeights = handleResize(el, dx, dy, index, nextIndex, weights)

            children.forEach((child, i) => {
                let weight = newWeights[i]
                child.style.flexGrow = weight
                child.setAttribute(\'weight\', weight)
            })

            handleResizer(weights)
        }

        const handleMouseUp = () => {
            target = null
            document.removeEventListener(\'mousemove\', handleMouseMove)
            document.removeEventListener(\'mouseup\', handleMouseUp)
        }

        const init = () => {
            if (!el) {
                return
            }
            const children = getChildren(el)

            // console.log(\'children\', children)

            children.forEach((child, index) => {
                let weight = +(child.getAttribute(\'weight\') || (1 / children.length))
                child.style.flexGrow = weight

                if (index < children.length - 1) {
                    let divider = createDivider(child, binding)
                    divider.addEventListener(\'mousedown\', handleMouseDown, false)
                }
            })
        }

        init()
    }
}

resizer.install = Vue => Vue.directive(\'panel-resize\', resizer)

export default resizer

ItVuer - 免责声明 - 关于我们 - 联系我们

本网站信息来源于互联网,如有侵权请联系:561261067@qq.com

桂ICP备16001015号