Split 面板分割

概述

1 .将一片区域,分割为可以拖拽调整宽度或高度的两部分
2 .传入参数:
3 .父组件

<template>
    <div class="split">
        <div v-if="id==0" class="split_wraper">
//根据传入的id来实现对应的面板分割的布局
            <div v-if="num==1" class="split_one">
                111
            </div>
            <div v-else-if="num>=2" class="split_more">
                <div class="split_more_left" :style="computed_left">
                    <QA />
//左边的模板
                </div>
                <div class="split_tool_shu" draggable 
                    @dragend="handleDragEnd"
                    @dragover="handleDrag">
                </div>
//拖拽的分割线,加的是drag事件,其中最主要的是dragend事件,现在绑了俩来确保一定不会出问题
                <div class="split_more_right" ref="split_more_right">
                    <SplitSon 
                        v-for="(c,index) in num-1" 
                        :index="index"
                        :parent_height="right_height" 
                        :height_arr="right_arr_height">
                        <QQ />
//通过slot传入自定义的组件,在子组件的情况下
                        </SplitSon>
                </div>
            </div>
        </div>
    </div>
</template>
<script>
import QA from '../qaclient/qq'
import SplitSon from './son/split_son'
export default {
    props:{
        id:{
            type:Number,
            default:0,
            // 根据不同的代号编辑不同的效果,默认代号是0,实现左右各自一半,但是右边要开小宫格。就是要避面页面来回切换的问题
        },
        num:{
            type:Number,
            default:5,
            // 要分割的数目,展现一种自适应的效果,
        }
    },
    data:function(){
        return {
            width:80,
            right_height:0,
//右边父元素的高度
            right_arr_height:[]
//右边高度的数组分布情况。其实主要就是在操作这个数组
        }
    },
    methods:{
        handleDrag(e){  
            console.log(e)       
            this.width=Math.floor(((e.clientX/window.innerWidth)*100))
        },
        handleDragEnd(e){
            this.width=Math.floor(((e.clientX/window.innerWidth)*100))
        }
//左右是根据flex,自定义的情况来布局
    },
    computed:{
        computed_left(){
            return{
                width:`${this.width}%`
            }
        }
    },
    components:{
        QA,SplitSon
    },
    mounted(){
        if(this.num>2){
            this.right_height=this.$refs.split_more_right.clientHeight
            const item=Math.floor(100/(this.num-1))
            for(let i=1;i<this.num;i++){
                this.right_arr_height.push(item*i)
            }
           //根据宽度和当前需要显示的元素,来一个初始化元素的高度
        }
    }
}
</script>
<style lang="less" src="./index.less">
</style>

4 .子元素代码

//这里是一个竖向的拖拽子组件
<template>
    <div :style="computedHeight" class="split_son">
//每一个组件的高度和宽度,拖拽其实就是在改变这个东西
        <slot></slot> 
//传进来的组件就显示在slot里面,
        <div 
            v-if="this.index+1<this.height_arr.length"
//拖拽的那条线,最后一个元素要保证他没有这条线
            draggable
            @dragend="handleDragEnd"
            @dragover="handleOver"
            class="split_son_heng">
            
        </div>
    </div>
</template>
<script>
export default {
    props:{
        index:{
            type:Number,
            required:true,
        },
        height_arr:{
            type:Array,
//所有元素的相对位移偏移保存数组
        },
        parent_height:{
            type:Number
        }
    },
    computed:{
          computedHeight(){
            if(this.index==0&&this.height_arr[0]){
                var height_pre=this.height_arr[0]
                return {
                    width:"100%",
                    height:`${height_pre}%`
                 }
            }else if(this.height_arr[1]){
                var height_pre=this.height_arr[this.index]-this.height_arr[this.index-1]
                return {
                    width:"100%",
                    height:`${height_pre}%`
                }
            }       
        } 
    },
    methods:{
        handleDragEnd(e){
            let offsetY=e.offsetY
            if(offsetY>0){
                // 向下拖拽              
                let newHeight=this.$parent.right_arr_height[this.index]+Math.floor((offsetY/this.parent_height)*100)
                if(this.index<this.height_arr.length){
                        if(newHeight>=this.$parent.right_arr_height[this.index+1]){
                            newHeight=this.$parent.right_arr_height[this.index+1]-1
                            // 防止拖拽超过最后一个
                        }
                }else{
                    if(newHeight>=100){
                        newHeight=100
                        // 防止拖拽超过最后一个
                    }
                }
                
                this.$parent.right_arr_height.splice(this.index,1,newHeight)
            }else{
                // 向上拖拽,向上和向下还是有点不一样的

                const newOffset=-(offsetY)
                let newHeight=Math.floor((newOffset/this.parent_height)*100)
                if(this.index==0){
                    if(newHeight>this.height_arr[0]){
                        newHeight=1
                    }else{
                        newHeight=this.$parent.right_arr_height[0]-newHeight
                    }
                    // 拖拽第一个,并且拖拽小于0的时候,让他最小只能缩小到1%的高度
                }else{
                    if(this.$parent.right_arr_height[this.index]-newHeight>this.$parent.right_arr_height[this.index-1]){
                        newHeight=this.$parent.right_arr_height[this.index]-newHeight
                    }else{
                        newHeight=this.$parent.right_arr_height[this.index-1]+1
                    }
                }
                
                this.$parent.right_arr_height.splice(this.index,1,newHeight)
            }
        },
        handleOver(){

        },
        
    }
}
</script>
<style lang="less" src="./split_son.less"></style>

5 .核心思想

1 .[20,55,75,100]:基础数据就是这个,算的时候就是每个元素拖拽的那条线的位置相对于整个父元素的位置,这个很好求。
2 .拖拽的时候其实是改变这个数据里面的元素的大小
3 .最后渲染的高度计算其实就是当前元素减掉前一个,比如第一个元素的高度占总体高度的25%,第二个元素就是55-20=35,那么第二个元素的高度就是35%,非常容易理解

升级版

<template>
    <div class="li-split" ref="splitWrapper" :class="wrapperClasses">
         {{offset}}
       <div v-if="isHorizontal" :class="`${prefix}-horizontal`">
           <div class="li-split-left-pane" :style="computedLeft">
               <slot name="left"/>
           </div>
           <div class="li-split-trigger-heng-con" 
                @mousedown="handleMouseDown"
                :style="computedTrigger"           
            >
                <div class="li-split-trigger-heng-con-wrapper">
                    <i class="li-split-trigger-heng-con-bar"></i>
                    <i class="li-split-trigger-heng-con-bar"></i>
                    <i class="li-split-trigger-heng-con-bar"></i>
                    <i class="li-split-trigger-heng-con-bar"></i>
                    <i class="li-split-trigger-heng-con-bar"></i>
                    <i class="li-split-trigger-heng-con-bar"></i>
                    <i class="li-split-trigger-heng-con-bar"></i>
                    <i class="li-split-trigger-heng-con-bar"></i>
                </div>       
           </div>
           <div class="li-split-right-pane" :style="computedRight">
               <slot name="right"/>
           </div>
       </div>
       <div v-else :class="`${prefix}-vertical`">

       </div>
    </div>
</template>
<script>
import {on,off}from "../../../utils/dom"

export default {
    name:"Split",
    // 我要把横竖两种分成两个组件,因为这个想要兼容多个,就是可以支持分割2个以上的间隔
    props:{
        min:{
            type:Number,
            default:10,
        },
        max:{
            type:Number,
            default:99,
        },
        isHorizontal:{
            type:Boolean,
            default:true
        }
    },
    data:function(){
        return {
            prefix:"li-split",
            offset:50,
            isMouse:false,
            wrapperHeight:0,
            wrapperWidth:0,
            wrapperLeft:0,
            wrapperRight:0,
            wrapperTop:0,
            wrapperBottom:0,
            left:100,
            right:100,
            minDistance:null,
            maxDistance:null,
        }
    },
    methods:{
        handleMouseDown(e){
            this.isMouse=true;
            this.initOffset=this.isHorizontal?e.pageX:e.pageY
            on(document,'mousemove',this.handleMove)
            on(document,'mouseup',this.handleUp)
            this.$emit("on-move-satrt")
        },
        handleMove(e){
            if(this.isMouse){
               if(this.isHorizontal){
                    // 只改变x位置
                    const left=e.pageX
                    if(left<this.wrapperLeft||left>this.wrapperRight){
                        return
                    }
                    if(this.minDistance&&left<this.minDistance){
                        this.offset=this.min
                        return 
                    }
                    if(this.maxDistance&&left>this.maxDistance){
                        this.offset=this.max
                        return 
                    }
                    this.left=left
                    this.offset=(left-this.wrapperLeft)/this.wrapperWidth*100
               }else{
                   const top=e.pageY
                   this.top=top
                //    只改变y位置
               }
               this.$emit("on-move")
            }else{
                // 没有按下鼠标,这里不会跑到
            }
        },
        handleUp(e){
            this.isMouse=false
            off(document,'mousemove',this.handleMove)
            off(document,'mouseup',this.handleUp)
            this.$emit("on-move-end")
        },
        initWrapper(){
            const dom=this.$refs.splitWrapper.getBoundingClientRect()
            this.wrapperHeight=dom.height
            this.wrapperWidth=dom.width
            this.wrapperLeft=dom.left+document.documentElement.scrollLeft
            this.wrapperRight=this.wrapperLeft+dom.width
            this.wrapperTop=dom.top+document.documentElement.scrollLeft
            this.wrapperBottom=this.wrapperTop+dom.height

            if(this.min&&this.isHorizontal){
                // 有最小值,并且方向是横向,所以要以宽度进行计算
                this.minDistance=this.wrapperWidth*this.min/100
                console.log(this.minDistance)
            }

            if(this.max&&this.isHorizontal){
                this.maxDistance=this.wrapperWidth*this.max/100
            }

            if(this.min&&!this.isHorizontal){
                this.minDistance=this.wrapperHeight*this.min/100
            }

            if(this.max&&!this.isHorizontal){
                this.maxDistance=this.wrapperHeight*this.max/100
            }
        }
    },
    computed:{
        wrapperClasses(){
            return [

            ]
        },
        computedLeft(){
            return {
                "width":`${this.offset}%`
            }
        },
        computedRight(){
            return {
                "width":`${100-this.offset}%`
            }
        },
        computedTrigger(){
            return {
                position:"absolute",
                "left":`${this.left}px`,
            }
        }
    },
    components:{
        
    },
    mounted(){
        this.initWrapper()
    }
}
</script>
<style lang="less" src="./split.less">

</style>


@name:.li-split;
@trigger-color:#f8f8f9;
@trigger-border-color:#dcdee2;;

@{name}{
    position: relative;
    width:100%;
    height:100%;

    &-horizontal{
        height: 100%;
        display: flex;
        flex-direction: row;
    }

    &-trigger-heng-con{
        width:6px;
        height:100%;
        background:@trigger-color;
        border:1px solid @trigger-border-color;
        border-top:none;
        border-bottom:none;
        cursor: col-resize;
        box-sizing: border-box;
        display: flex;
        flex-direction: column;

        align-items: center;
        justify-content: center;

        &-wrapper{
            // left:1px;
            // top:50%;
            // height:32px;
            // transform:translateY(-50%);
            display: flex;
            flex-direction: column;
        }

        &-bar{
            width: 4px;
            height: 1px;
            margin-top:3px; 
            background-color:rgba(23,35,61,.25) ;      
        }
    }
}

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

相关阅读更多精彩内容

  • abandon, desert, forsake, leave, give up abandon :强调永远或完全...
    sunxiaohang阅读 8,103评论 0 3
  • 专业考题类型管理运行工作负责人一般作业考题内容选项A选项B选项C选项D选项E选项F正确答案 变电单选GYSZ本规程...
    小白兔去钓鱼阅读 13,111评论 0 13
  • 冬日清晨,今天是这座城市难得的好天气,暖洋洋的日光洒在肩膀,驱散了寒日里的冰冷,惬意又舒服,可当走进这条逼仄的巷子...
    我嘉禾阅读 6,125评论 0 1
  • 文/夏上水 那一年 我坐在左边你坐在右边 你喜欢扎马尾穿蓝裙子 我喜欢留长发反穿校服 那一年 我不会英语你不会数学...
    夏上水阅读 2,967评论 14 35
  • 下雨前,鱼会游到水面上呼吸,。因为下雨前大气气压很低,水里缺少氧气,靠近水面的地方氧气多一些,于是它们就游到水面呼吸了。
    12张宇鹏阅读 3,785评论 0 0

友情链接更多精彩内容