11.Vue技术栈开发实战-从SplitPane组件谈Vue中如何“操作”DOM
本节代码对照:
https://github.com/lison16/vue-cource/blob/master/src/components/split-pane/split-pane.vue
https://github.com/lison16/vue-cource/blob/master/src/views/split-pane.vue
创建组件的文件夹叫做split-pane
在路由列表里面注册一下
添加这个vue页面
创建组件的vue文件,设置name为SplitPane
创建index.js.把组件引进来,并导出去
引入这个组件,然后注册这个组件。
容器中分成两个区域,通过拖动来改变两个区域的比例。
还可以上下,嵌套使用。
分为三个部分,左边的区域,右边的区域,然后是中间可拖动的部分
我们首先来定义三个div。它的宽和高应该和父容器是一样的,所以这里我们设置它的高度和宽度是100%
宽度其实可以不设置,div是一个块级元素,它本身的宽度就是和它容器的宽度是一样的
less里面的写法,有层级管理的关系的class类型,可以嵌套着写。他们有一个共同的class是pane。左边是pane-left右边是pane-right
它们的高度也是和父容器一样高
&-left就是拼接了父级别的pane。所以这里就相当于是.pane-left
我们给左边加了一个背景色,可以看到它的颜色和父容器是一样宽的。
再给右侧的div设置一个背景色是橘黄色的,可以看到俩div成了上下布局了,因为他俩的宽度都是100%和父容器的宽度一样。所以右侧的就被挤下来了
想让这俩div在一排上显示。给.pane设置宽度为50%。值是宽度变窄了, 还没有在一排上显示。
可能有个默认的样式是居中的,
src根目录下的 App.vue 把这里注释掉。因为设置了居中显示,所以导致了上面的问题。
这里改成float:left。这样就上到一行里面显示了。
如果想设置左边是30%的宽度,右边是70%的宽度
把float:left和width都去掉。
给他俩一个position,都设置为绝对定位
设置绝对定位后,它会往上找,去他的父元素一级一级的找,它会根据有定位属性的父元素来定位。如果没有它就一直找到html标签。
所以这里地方我们要给wrapper一个相对定位的属性。这样又不会使wrapper的元素受到影响。
让他俩的父元素的top位置都是0,左边设置宽度为30%。右边这个呢?也想让他用30%的这个值,top:0我们在.pane已经设置了,这里会继承过来,
这样设置右边就是70%的宽度,用到了同样的变量30%。这样就是两边加起来就是100%
把左边改成20%
右边的left也改成20%。这样他俩加起来就是100%
30%的这个值,想通过js变量去控制,这样这个值就不能写在css里面了。我们给它绑定一个变量,变量在data里面定义。
30%通过计算属性计算出来。
这样就把左边的30%的css属性注释掉。
右边是通过left的值,修改它的宽度。所以右边我们绑定的css属性是left
通过一个按钮改变这个变量的值来试一下
把这个按钮放在了左侧的面板里面
通过点击左侧按钮的面板,改变左侧pane的宽度
每次点击宽度变小。Vue里面,这样通过数据驱动样式的改变,这样避免了数据和视图不对应
定义trigger中间拖动条
它要显示在中间也是需要设置绝对定位。设置了top为0,它显示在了最左侧。
也是给中间的条绑定style属性。但是绑定上left后,视图中红色的调不见了。
中间的条,实际上在这里,说明可能是被元素遮住了。
通过设置一个z-index来看下
红色的条 实际上是盖在,右侧的
我们希望他在最中间显示。
首先中间条的宽度我们通过属性传递进来,默认是8.我们定义个props属性 triggerWidth
首先是向左偏移30%和left的宽度是一样的,然后它需要减少4个像素,来达到在最中间,这里我们用到css3的一个方法calc(),它允许你在里面做一些计算。
30%的偏移再减去4像素,就可以这么写
减去的是triggerWidth除以2
return `calc(${this.leftOffset * 100}% - ${this.triggerWidth / 2}px)`
这里绑定的是 triggerWidh 后面是px
<div class="pane pane-trigger-con" :style="{ left: triggerLeft, width: `${triggerWidth}px` }"></div>
中间只用到了width属性
说明我们这里的计算属性并没有生效
我们这里把计算出来的值打印出来看一下
这里的单词拼错, 应该是calc
红色的条被左侧遮住了一半
被右侧也遮住了一半
鼠标拖动中间trigger条
这就用到了鼠标的三个事件了
首先绑定一个mousemove事件
我们会用到参数里面的pageX就是鼠标距离页面左侧的像素,
只要能算出来鼠标距离左侧有多少像素,我们只要知道这个容器距离页面左侧多少,用鼠标的距离减去容器的距离页面的距离,就是左侧left的宽度了。
获取dom元素用ref
那么怎么获取父容器距离左边的距离呢?获取dom容器呢 我们用ref属性,给父容器div加一个ref属性为outer
getBoundingClientRect
理解:getBoundingClientRect用于获取某个元素相对于视窗的位置集合。集合中有top, right, bottom, left等属性。
2.返回值类型:
rectObject.top:元素上边到视窗上边的距离;
rectObject.right:元素右边到视窗左边的距离;
rectObject.bottom:元素下边到视窗上边的距离;
rectObject.left:元素左边到视窗左边的距离;
获取到dom后,它有个方法叫做getBoundingClientRect,这个方法返回一个对象,
这个方法返回一个对象,对象里面返回一个属性,
先来看下它返回的值
对象里面包含了这些属性,这些属性在IE9下也是支持的,所以兼容性是没有问题的。
所以这里我们减去left
鼠标的事件,有时候鼠标会超过这个框,应该绑定在页面级别。
所以我们把mouseMove事件绑定子在document上。就是鼠标在中间的条上,按下的时候绑定这个事件。
所以这里中间的条的事件,改成mousedown事件
鼠标按下我们给document绑定一个事件,它的回调事件是下面的handleMousemove事件
当点击这个条的时候,然后鼠标在页面上移动,console内不断的输出的
偏移量除以容器的总宽度,再乘以100,就是百分比。
outerRect.width是容器的宽度,也就是dom对象的总的宽度
乘以100 去掉
this.leftOffset = (offset / outerRect.width)
现在这个条就随着鼠标的移动而移动了
我们希望按下鼠标的时候 开始移动,松开鼠标的时候就应该停止了。
所以我们就需要有个状态,鼠标是按下还是抬起
只有为true的时候才能移动。
mouseup的事件
这样只有鼠标按下才能移动,鼠标抬起就不能移动了。
点击中间的条,靠左半边点,或者靠右半边的时候点击会出现这个偏移量的问题。
在鼠标按下的时候,有一个瞬间的偏移。出现问题的原因是我们在注册事件的时候忽略了 中间的条 也有个宽度。
所以在条上有个初始的偏移量,它的初始是0
我们就要给这个偏移量设置一个值,当我们按下的时候。我要把这个宽度也计算在内。
减去这个条距离页面左侧的距离。就是鼠标距离这个条的左侧距离。
因为这个事件是发生在这个条上的,
this.initOffset = event.pageX - event.srcElement.getBoundingClientRect().left
在move的时候,我们要减去这个值
还需要再加上条一半的宽度
这个时候再去拖动,就不会有一瞬间的移动了
设置最大值和最小值。
拖动最右边应该有个头,这里完全拖出去了。
设置最小的百分比值,是0.1
当你的拖动到百分之0.1的时候,左侧的值就不会再变了。
除法的逻辑加到上面去
如果偏移量小于这个最小值的话,那么就用这个最小值。
到了最小值 再拖,就拖不动了。
我们再来设置一个最大值,最大值设置为0.9
超过最大值,就是最大值。
拖动过程中,选中的问题
拖动过程中,偶尔会做了一些选中的操作
通过设置一个样式。这样他就不会在这个元素上选中操作了。
初始偏移量
设置初始的偏移
调用的时候就可以设置初始的偏移了 。
这里就注释掉,不用了。
这里就改成value的形式
我们是通过拖动来修改这个值的,那么有个问题就是。子组件如果想改变父组件的值,不能直接通过这种赋值的形式。还是要通过事件来触发 ,告诉父组件,父组件内做事件的响应,
再父组件内,把这个传入百分比值改成变量
然后给它绑定一个事件
回调函数里面有个参数,我们接收这个参数,并赋值给偏移量的变量值
在这里我们要抛出这个事件
拖动和修改都没有问题了。
<split-pane :value="offset" @input="handleInput"></split-pane>
另外一种方式vue提供的语法糖
v-model简写的形式
.sync修饰符
子组件内,事件也不抛出去了。改用update:value
这么写首先给value绑定值offset,同时会给你绑定一个事件会更新这个offset的值,
子组件内要触发一个事件,固定的就叫做update。
update后面的:value就是我们这里父组件的属性名
最后 就是你要更新的值
在区域内填充内容
就用到了我们之前讲到的slot插槽的概念
右边的right给压到中间的条下面了。给右侧绑定一个triggerWidth除以2
加一个paddingLeft的属性 是中间调的宽度除以2
这样它的内容就不会被压到了。
同理left也有一个paddingRight属性
鼠标的样式
鼠标放上去,要让用户知道这是一个可以拖动的,我们就需要给它设置一个css的演示
鼠标的样式记不住呢,推荐大家一个网站
http://css-cursor.techstream.org/
鼠标放上去会显示对应的鼠标样式。
左右拖动的效果就是这个了