开发一个UI框架项目[7]-Popover
本文最后更新于:2020年10月12日 晚上
设计细节
- 触发方式。可设置通过trigger属性设置点击click或者覆盖hover触发弹出框。同时通过点击触发的弹出框,再次点击或者点击页面其它地方,将会关闭该弹出框,但点击弹出框区域不关闭。
- 弹出位置。可通过position属性设置弹出位置,有顶部top、底部bottom、左侧left和右侧right。
功能细节
- 弹出框元素层级。遇到的第一个坑,弹出层的容器是放在popover里面的,看下结构: - 1 
 2
 3
 4
 5
 6
 7
 8
 9
 10- <template>
 <div class='yv-popover' ref="popover">
 <div class="content-wrapper">
 <slot name="content"></slot>
 </div>
 <span ref="triggerWrapper">
 <slot></slot>
 </span>
 </div>
 </template>- 如果popover的父容器设置了 - overflow: hidden的样式值,弹出层的内容就会被隐藏。解决方案就是让弹出层放在body里面而不是popover里面,这样弹出框就不会受到包含了popover的元素的影响。
- 弹出框的定位值。这是继第一个坑之后的坑。弹出框是设置了绝对定位,在它出现在body里面之前,它是相对于popover容器定位,出现在body里面之后,就是相对于body定位,所以坑就出现了,如果当前位置超过了当前视口的大小而出现了滚动条,弹出框出现的位置一直是相对于body的位置而不能精准出现在当前视口,看下图示:  - 从图示可以看到popover弹出框出现的位置并没有在浏览器视口中,解决方案就是需要加上滚动条的高度,即 - window.scrollY,同理,左边需要加上- window.screenX,具体的设置请看弹出位置设置。
- 设置弹出框的位置。这里不管弹出位置在上下还是左右,都通过top和left来设置位置,所以写成一个对象,到时通过传入值来匹配: - 1 
 2
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15- let positions = {
 top: {
 top: '',
 left: ''
 },
 bottom: {
 // ...
 },
 left: {
 // ...
 },
 right: {
 // ...
 }
 }- 顶部位置就是一开始的设置: - 1 
 2
 3
 4- top: {
 top: top + window.scrollY,
 left: left + window.screenX,
 }- 底部位置:由于是出现在content底部,所以top值还需要加上content本身的高度: - 1 
 2
 3
 4- bottom: {
 top: top + height + window.scrollY,
 left: left + window.screenX
 }- 左侧位置和右侧位置:一般两个元素并列居中展示会看着比较舒服,如果让弹出层和content层居中展示呢?将top值加上用两个元素之间的差值除以2的值,得到的就是弹出层的位置: - 1 
 2
 3
 4- left: {
 top: top + window.scrollY + (height - contentHeight) / 2,
 left: left + window.screenX
 }- 同时由于在右侧弹出,所以右侧的left值还需加上content的宽度: - 1 
 2
 3
 4- right: {
 top: top + window.scrollY + (height - contentHeight) / 2,
 left: left + width + window.screenX
 }- 上面所用到的left、width等均来自: - 1 
 2
 3- const { contentWrapper, triggerWrapper } = this.$refs
 let { width, height, left, top } = triggerWrapper.getBoundingClientRect()
 let { height: contentHeight } = contentWrapper.getBoundingClientRect()- 然后根据用户传进来的 - position位置属性值,给弹出层- contentWrapper赋值:- 1 
 2- contentWrapper.style.top = positions[this.position].top + 'px'
 contentWrapper.style.left = positions[this.position].left + 'px'- 最后还有优化的地方,即给弹出框加上一个小尾巴,这个属于CSS范畴且样式代码比较多,同样也是要分成上下左右四部分设置,看源码即可。 
- 触发方式的处理。点击方式通过给document添加click监听事件,这里有个点需要注意,如果是给body添加click监听,有可能会出现因为body高度不够而点击不到的问题。hover触发方式是通过给 - popover添加mouseenter和mouseleave监听事件。- 1 
 2
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18- <script>
 export default {
 mounted() {
 if (this.trigger === 'click') {
 this.$refs.popover.addEventListener('click', (e) => {
 this.showPopover(e)
 })
 } else {
 this.$refs.popover.addEventListener('mouseenter', () => {
 this.open()
 })
 this.$refs.popover.addEventListener('mouseleave', () => {
 this.close()
 })
 }
 }
 }
 </script>
- 点击关闭弹出框的处理。对于点击后弹出的弹出框,需求是再次点击触发的区域或者页面其它地方,会关闭弹出框,点击弹出框自身不关闭。这个需求开发过程中也出现一些坑:第一个大坑是在第一次开启关闭弹出框之后,已经给document添加了监听事件,在第二次以及后面的点击弹出框时,由于事件冒泡机制,会依次触发:点击popover弹出弹出框 -> 点击document关闭弹出框,就会造成一个问题,弹出框刚弹出马上就被关闭了,看不到弹出框;第二个坑是关闭事件没有统一处理起来,每个需要关闭的地方都要写一遍,如果关闭的时候忘记对document取消监听click事件,就会导致后面弹出的弹出框关闭的时候会触发多次关闭事件。 - 针对第二个问题,先将关闭事件内聚,统一写成一个方法,将弹出框关闭,并移除对click事件的监听: - 1 
 2
 3
 4
 5
 6
 7
 8
 9
 10- <script>
 export default {
 methods: {
 close() {
 this.visible = false
 document.removeEventListener('click', this.eventHandler)
 }
 }
 }
 </script>- 针对第一个问题,本来是用 - click.stop加上修饰符- .stop来阻止事件冒泡,这样在点击popover的时候就不会再触发document的点击事件,但是这样会导致用户在包裹了popover的元素上自定义的click事件失效,这显然是不行的。最后的解决方案就是各自管各自的,即document只管自己的,popover只管popover的,具体通过click事件中的参数- event.target来判断:- 1 
 2
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15- <script>
 export default {
 methods: {
 eventHandler(e) {
 if (this.$refs.popover && (this.$refs.popover === e.target || this.$refs.popover.contains(e.target))) {
 return
 }
 if (this.$refs.contentWrapper && (this.$refs.contentWrapper === e.target || this.$refs.contentWrapper.contains(e.target))) {
 return
 }
 this.close()
 },
 }
 }
 </script>- 有两个判断,如果当前存在popover、contentWrapper,并且popover或者contentWrapper等于或包含了 - e.target,则不做任何处理直接返回。若不是,则调用close事件。
vuepress配置
在docs/.vuepress/components文件夹下增加popover-demovue文件,内容就是我们要展示的popover示例,然后在docs/components文件夹下增加popover的md文件,内容就是放置整个popover组件说明。
具体内容请访问这里。
本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!