概述
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) ;
}
}
}