一、写前需要思考的
1、 它有两个需求,一是点它出现一个div,二是div里可以出现一些内容;
2、 思路:首先想象一下用户该如何用我们的组件(也就是在template里面他该怎么写)
3、 popover最难的一点,你的元素div要如何出现在卡片上方;
4、 它难就难在要考虑很多浏览器方面的样式行为,也就是css方面;
二、先实现最简单的功能(点击按钮外部关闭内容元素)
1、 思路:当你点击按钮,使内容出现,与此同时,你需要给document绑定一个点击事件,当你第二次点击按钮或者popover外面的区域时,就要执行此事件,并同时把此事件从document上解绑定;你不解绑的话,事件越绑越多;
- 默认的插槽是不需要包起来的;
- 一定要异步给document添加函数(用setTimeout),否则同步的话,直接运行下来又把visible变成false了,内容永远都不会出来;
-
()=>{}
相当于function(){}.bind(this)
,而且要清楚function x(){}
和function x(){}.bind(this)
是两个不同的函数!!!!!!! - 阻止冒泡,主要是阻止冒到document上面触发x函数;但是阻止冒泡是个很危险的选择,一般我们应该让用户自己抉择是否冒泡,不应该在轮子组件中阻止冒泡;
2、在popover组件上加阻止冒泡会有bug,像下面的代码,<d-popover>外上的点击事件是无法触发到的;
<div @click="yyy">
<d-popover>
<template v-slot:content>
<div>这是一段内容</div>
<div>nctwayv内容</div>
</template>
<button>点我</button>
</d-popover>
</div>
3、不能把内容元素跟按钮写在一起,一旦按钮div上有个overflow:hidden
,那么这个元素div就会被遮盖住;所以我们给它一个ref,通过ref获取到元素,再把元素append到document里;
- 这里又要注意一点,一旦你把内容元素append到document里,它就脱离了d-popover的作用域,所以css中,不能把content-wrapper写在popover里了,应该挪出来;而且要获取按钮的top、left来给内容元素赋值top、left;
- 当你移动vue组件中一个元素的位置时,是不会影响这个元素的功能的,只是改变了位置;
4、不是单单直接赋值top、left就可以了,有一种情况,当document的长度和宽度均超过视图时,就不能单单只是赋值top、left了,所以垂直方向上需要再加上window.scrollY(水平方向同理);
垂直方向.png
5、到此还有bug,就是当你点击按钮关闭内容元素时,它不会去执行document上的那个x函数去关闭,而是直接执行的this.visible = !this.visible
这句话去关闭的元素,然后document上的x函数一直没有执行也没有被删除;
- 所以我们要获取用户点击的哪里,点击按钮或按钮外部去关闭执行的函数不一样;
- 怎么获取你点击的是哪个元素?把点击事件本身打印出来看,
e.target
; - 因为我们把popover上的阻止冒泡去掉了,所以现在点击内容元素的话它还是会消失,所以我们需要给document的绑定事件判断一下条件,当点击的是内容元素时,我们什么都不要做;
- 把关闭入口收拢,做到高内聚,低耦合!!!!
- 不要使用
this.visible = !this.visible
,通过this.visible = true / false
控制各个条件下的操作; - 需要给document的绑定函数一个执行范围,当它发现点击的范围在内容区域时,就不做任何操作
documentEvent(e){
if(this.$refs.contentWrapper && this.$refs.contentWrapper.contains(e.target) ){return}
this.close()
},
close(){
//只要关闭内容元素,就要把document上的事件移除
this.visible = false
document.removeEventListener('click', this.documentEvent)
},
tips.png
- 为什么可以做到点击第二个按钮时,第一个按钮的内容元素会关掉?因为点击第二个按钮的时候回同时触发document上的事件,就会把第一个按钮的内容元素关闭了!!!!
三、支持四个位置
1、先优化内容元素的样式;
- 给contentWrapper设置
max-width: 20em; word-break: break-all;
(英文网站最好不要加word-break: break-all;
)
2、但是仔细看,三角箭头哪里是没有阴影的,所以就不能用box-shadow
了,用filter: drop-shadow(0 0 3px rgba(51,51,51,0.4)); background-color: white;
;
3、表驱动编程!!!
把多个
if ....else if....
写成excel表一一对应的形式;表驱动编程.png
四、动态绑定一个事件(click/hover)
1、添加props: trigger
,hover时对应mouseenter、mouseleave
事件;
2、你没有办法直接在div上给它v-bind
一个事件变量,因为v-bind
只接受固定的事件名,所以你只能在mounted钩子函数中通addEventListener
来触发不同事件;
3、你在mounted钩子函数中添加事件,vue是不知道的,所以你还需要手动在beforeDestory钩子里面移除事件;(!!注意:不能在destoryed钩子里,因为destoryed时DOM已完全消除完毕,是获取不到元素的)
4、到此还有一个小bug,hover时,放到那个三角箭头那里时,会有问题;
image.png
五、其他知识点
1、 v-if和v-show区别: v-if会改变它是否存在于DOM树中,而v-show只改变它的display样式(v-show是一直存在于DOM树中的);
2、有时让插槽内容能够访问子组件中才有的数据是很有用的,具体的可见文档:作用域插槽https://cn.vuejs.org/v2/guide/components-slots.html;
//d-user组件内
<span>
<slot v-bind:user="user">
{{ user.lastName }}
</slot>
</span>
export default {
data(){
return {
user: 'doudou'
}
}
}
//使用组件
<d-user>
<template v-slot:default="slotProps"> //当然slotProps这个名字你可以自己随便取
{{slotProps.user}}
</template>
</d-user>
网友评论