[TOC] >[success] # 正式分装章节 ~~~ 1.上个章节讲了一下思路,这个章节会封装一个splitpane的布局组件 ~~~ >[danger] ##### 基础面板 ~~~ 1.根据上章节封装一个基础面板 ~~~ ~~~ <template> <div class="split-pane-wrapper"> <div class="pane pane-left" :style="{width:leftOffsetPercent}"> <button @click="handleClick">点击减少左侧宽度</button> </div> <div class="pane-trigger-con" :style="{left:triggerLeft, width: `${triggerWidht}px`}"></div> <div class="pane pane-right" :style="{left:leftOffsetPercent}"></div> </div> </template> <script> export default { name: "split-pane", props:{ triggerWidht:{ type:Number, default:8, }, }, data(){ return{ // 在这定义一个值。这样用户可以直接指定占比的值 // 在页面css 布局使用的值 使用计算属性拼接即可 leftOffset:0.3, } }, methods:{ handleClick(){ this.leftOffset -= 0.02 } }, computed:{ // 动态属性去拼接生成css 实际需要的代%形式的数据 leftOffsetPercent(){ return `${this.leftOffset * 100}%` }, triggerLeft(){ return `calc(${this.leftOffset * 100}% - ${this.triggerWidht/2}px)` }, }, } </script> <style lang="less"> .split-pane-wrapper{ width: 100%; height: 100%; position: relative; .pane{ position: absolute; height: 100%; top:0; &-left{ /*width: 30%;*/ background: brown; } &-right{ right: 0; bottom: 0; /*left: 30%;*/ background: chartreuse; } &-trigger-con{ z-index: 100; height: 100%; background: red; position: absolute; top: 0; } } } </style> ~~~ >[info] ## 增加事件 ~~~ 1.接下来需要做通过给红色条添加事件,可以通过数据去操作布局 2.了解几个方法'event.pageX' -- 鼠标x轴的坐标距离(准确或是) 3.'this.$refs.outer.getBoundingClientRect()' -- 获取绑定dom元素的一些 距离浏览器的基本信息: DOMRect {x: 8, y: 8, width: 400, height: 200, top: 8, …} bottom:208 height:200 left:8 right:408 top:8 width:400 x:8 y:8 3.常见想得到某个元素具体的位置,一般采用鼠标距离减去dom元素距 离浏览器距离 4.'event'事件中隐藏的两个快捷方法'event.srcElement' -- 获取当前dom 5.'event'事件中隐藏的两个快捷方法'event.srcElement.getBoundingClientRect()' -- 获取当前dom位置信息 6.去除css 拖住样式'user-select: none;' ~~~ >[danger] ##### 绑定鼠标点击红条时候触发事件 ~~~ 1.想在鼠标点击的时候触发一个事件,可以得到红色条距离dom的位置 ,后期根据这个位置去触发让左侧和右侧距离重新计算。 2.点击事件应该绑定在哪里,如果将点击事件绑定在红条区域,会出现 当鼠标移出红条的位置的时候,整个动态布局停止 'event.target.addEventListener('mousemove',this.handleMousemove)' 3.因此不能写成上面的写法,而是应该绑定在整个document上 'document.addEventListener('mousemove',this.handleMousemove)' 4.在点击事件上,增加的鼠标移动后重新计算的事件分开写方便维护 5.增加移入事件后,要对应的加入移除事件的后续操作,因此在data 中声明一个变量'canMove'用来记录当前点击还是未点击来触 发'handleMousemove' 事件 6.由于'mousemove' 是绑定在全局的,因此'mouseup' 也要绑定全局 7.按照第六条的思路需要在页面上绑定两个事件,但实际可以整合在 '@mousedown="handleMousedown" ' 中,这里面已经绑定了一个鼠标 移入事件,因此直接在绑定一个鼠标抬起事件 'handleMousup' 8.当我们计算'leftOffset'也就是左面区域的百分比的时候,鼠标到左侧边框 距离除以整体距离,就会出现一个效果,一瞬间区域发生变化并且鼠标 在整个红色区域,为了避免这个问题定位计算的时候直接从红条中心 位置计算,对应代码地方的g公式如下: 'const offset = event.pageX - this.initOffset + this.triggerWidth / 2 - outerRect.left;' ~~~ ~~~ <template> <div class="split-pane-wrapper" ref="outer"> <div class="pane pane-left" :style="{width:leftOffsetPercent}"> <button @click="handleClick">点击减少左侧宽度</button> </div> <div class="pane-trigger-con" @mousedown="handleMousedown" :style="{left:triggerLeft, width: `${triggerWidth}px`}"></div> <div class="pane pane-right" :style="{left:leftOffsetPercent}"></div> </div> </template> <script> export default { name: "split-pane", props:{ triggerWidth:{ type:Number, default:8, }, }, data(){ return{ // 在这定义一个值。这样用户可以直接指定占比的值 // 在页面css 布局使用的值 使用计算属性拼接即可 leftOffset:0.3, // 增一个判断 鼠标移入移除 canMove :false, // 去除误差值 initOffset: 0 } }, methods:{ handleClick(){ this.leftOffset -= 0.02 }, handleMousemove(event){ // pageX 是鼠标距离浏览器x轴的距离 // 想获取 红条在布局页面的准确位置需要用鼠标距离 - 容器对浏览器的距离 // 因此给 这个外部的div 绑上ref 属性用来获取,获取可以使用getBoundingClientRect() //console.log(event.pageX -this.$refs.outer.getBoundingClientRect().left) if(!this.canMove) return const outerRect = this.$refs.outer.getBoundingClientRect(); // 整个leftOffset 的像素距离其实是其实就是pageX鼠标距离减去最外层整理距离浏览器的距离 //根据上面可以得到一个公式就是 event.pageX-outerRect.left // 但要注意实际上中间区域是盖住了部分左右区域,也就是说只要我们进行了改变 // 就会自动到整个中间区域的中间位置,因此需要在点击的时候需要得到整中间区域最右面的位置 // 根据上面得到一个公式减去 this.initOffset也就是扣除无用区域获得中间区域最右面 // 为了去除因为中间区域覆盖的影响加上中间区域一半的宽度 const offset = event.pageX - this.initOffset + this.triggerWidth / 2 - outerRect.left; this.leftOffset = (offset/outerRect.width) }, handleMousup(){ // 抬起后 重新对canMove 赋值,让红条不能移动 this.canMove = false }, handleMousedown(event){ //event.target.addEventListener('mousemove',this.handleMousemove) document.addEventListener('mousemove',this.handleMousemove) document.addEventListener('mouseup',this.handleMousup) this.initOffset = event.pageX - event.srcElement.getBoundingClientRect().left this.canMove = true; }, }, computed:{ // 动态属性去拼接生成css 实际需要的代%形式的数据 leftOffsetPercent(){ return `${this.leftOffset * 100}%` }, triggerLeft(){ return `calc(${this.leftOffset * 100}% - ${this.triggerWidth/2}px)` }, }, } </script> <style lang="less"> .split-pane-wrapper{ width: 100%; height: 100%; position: relative; .pane{ position: absolute; height: 100%; top:0; &-left{ /*width: 30%;*/ background: brown; } &-right{ right: 0; bottom: 0; /*left: 30%;*/ background: chartreuse; } &-trigger-con{ z-index: 100; height: 100%; background: red; position: absolute; top: 0; } } } </style> ~~~ >[danger] ##### 最后的封装 ~~~ 1.上面已经可以实现整体使用,但是需要注意拖拽是有限度的,因此也需要设置拖拽的最大值最小值 2.上面的代码是我们指定'leftOffset'也是百分比占比,如果让组件去控制,就要有一个问题, 当我么拖拽的时候会重新改变这个值因此就是涉及到,父传子和子传父同时出发,想同时 出发两种方法在使用组件的时候使用v-model 和 使用语法糖sync ~~~ * 在使用组件的时候 ~~~ <template> <div class="split-pane-con"> <split-pane :value.sync="offset"> <div slot="left">left</div> <div slot="right">right</div> </split-pane> </div> </template> <script> import SplitPane from '_c/split-pane' export default { components: { SplitPane }, data () { return { offset: 0.8 } }, methods: { // handleInput (value) { // this.offset = value // } } } </script> <style lang="less"> .split-pane-con{ width: 400px; height: 200px; background: papayawhip; } </style> ~~~ * 组件封装代码 ~~~ <template> <div class="split-pane-wrapper" ref="outer"> <div class="pane pane-left" :style="{ width: leftOffsetPercent, paddingRight: `${this.triggerWidth / 2}px` }"> <slot name="left"></slot> </div> <div class="pane-trigger-con" @mousedown="handleMousedown" :style="{ left: triggerLeft, width: `${triggerWidth}px` }"></div> <div class="pane pane-right" :style="{ left: leftOffsetPercent, paddingLeft: `${this.triggerWidth / 2}px` }"> <slot name="right"></slot> </div> </div> </template> <script> export default { name: 'SplitPane', props: { value: { type: Number, default: 0.5 }, triggerWidth: { type: Number, default: 8 }, min: { type: Number, default: 0.1 }, max: { type: Number, default: 0.9 } }, data () { return { // leftOffset: 0.3, canMove: false, initOffset: 0 } }, computed: { leftOffsetPercent () { return `${this.value * 100}%` }, triggerLeft () { return `calc(${this.value * 100}% - ${this.triggerWidth / 2}px)` } }, methods: { handleClick () { this.leftOffset -= 0.02 }, handleMousedown (event) { document.addEventListener('mousemove', this.handleMousemove) document.addEventListener('mouseup', this.handleMouseup) this.initOffset = event.pageX - event.srcElement.getBoundingClientRect().left this.canMove = true }, handleMousemove (event) { if (!this.canMove) return const outerRect = this.$refs.outer.getBoundingClientRect() let offsetPercent = (event.pageX - this.initOffset + this.triggerWidth / 2 - outerRect.left) / outerRect.width if (offsetPercent < this.min) offsetPercent = this.min if (offsetPercent > this.max) offsetPercent = this.max // this.$emit('input', offsetPercent) this.$emit('update:value', offsetPercent) }, handleMouseup () { this.canMove = false } } } </script> <style lang="less"> .split-pane-wrapper{ height: 100%; width: 100%; position: relative; .pane{ position: absolute; top: 0; height: 100%; &-left{ // width: 30%; background: palevioletred; } &-right{ right: 0; bottom: 0; background: paleturquoise; } &-trigger-con{ height: 100%; background: red; position: absolute; top: 0; z-index: 10; user-select: none; cursor: col-resize; } } } </style> ~~~