在做一个表格需求的时候遇到el-popover的一些坑点

坑点1:挂载位置

el-popover是弹出来就追加到全局的body里面的,并且关闭弹窗后不会销毁而是隐藏,在弹出框非常多的情况下,会给body下挂载非常多的dom元素。像这样子。

坑点2:v-model

给el-popover加上v-model属性时,所有依赖这个data的弹窗el-popover将会瞬间追加到body尾部,比如你有一个表格,表格里面每一行都有一个元素点击后有弹出框,如果恰好你用一个data来绑定v-model属性,所有行的弹出框都会瞬间追加到body底部,所以如果要使用v-model属性的话必须使用给每一个弹出框单独绑定一个data,并且这个id是随机生成的,不可控。在弹出框多的情况下非常难维护控制弹出框显示隐藏的数据结构,并且如果你对弹出框做了某些监听任务的话,会产生非常多的观察者类。

我的做法是只保留最新的el-popover,其它缓存的DOM全部销毁。因为我的业务只需要对当前的弹出框进行操作。

js 复制代码
let popDoms = Array.from(document.body.children).filter(function (el) {
  return el.id && el.id.indexOf('el-popover-') === 0
})
// el-popover创建弹出框是一直追加隐藏,不销毁的。
let popDom
if (popDoms.length > 1) {
  popDom = popDoms[1]
  popDoms[0].parentNode.removeChild(popDoms[0])
} else {
  popDom = popDoms[0]
}

坑点3:位置

el-popover的位置会跟随它最近的一个有滚动条的容器,但是如果容器外面还有滚动条,el-popover的位置将不会跟随最外层容器的滚动条,导致位置错乱。

如下图,在弹出框弹出的状态下,滚动内部的滚动条,弹出框位置会跟随,但是滚动外部的滚动条,内部的容器位置变了,但是弹出框的位置不会变。导致位置错乱。

我的做法是监听最外层容器的滚动事件,将弹出框的top属性减去最外层容器的scrollTop(滚动条离滚动轨道顶部的距离)的差值。这个差值是结束滚动时候的scrollTop减去开始滚动时候的scrollTop,因为如果外部滚动条已经滚动了一段距离后,弹出框再弹出,这个时候再滚动外部滚动条,如果单纯减去scrollTop的话,是不会符合预期的。

js 复制代码
scrollWarpDom.addEventListener(
  // 弹出框跟随大滚动条滚动
  'scroll',
  () => {
    popDom.style.top =
      this.popDomOffsetTop -
      (scrollWarpDom.scrollTop - this.scrollWarpDomScrollTop) +
      'px'
  }
)
// this.scrollWarpDomScrollTop 是当前滚动条的位置,每一次触发弹窗时需要记录初始外部滚动条的位置。
// this.popDomOffsetTop 是当前弹出框的offsetTop,在挂载在body的情况下等于style.top,每一次滚动内部滚动条的时候需要记录一下弹出框的位置。

坑点4:层级

由于el-popover是追加到body后面的,层级是在body下面的那一位,而不是和当前触发弹窗的元素一个层级,所以如果超出了当前元素容器的范围,不会自动消失,而是永远处于顶层的位置。

如下图,如果当前绑定弹出框的元素在表格里面,表格发生滚动,弹出框会跟随表格里的绑定元素移动,到溢出容器的部分,表格滚动容器的内容会被遮挡,但是由于弹出框是挂载在body下面的,层级非常高,所以弹出框是可视的,不符合预期。

我的做法是监听滚动容器的滚动事件,当超出容器范围的时候,先把当前弹出框元素display设置为none,然后再随便触发一个无关紧要的点击事件(el-popover可以通过ref来使用实例的doClose()方法来关闭弹窗,在控制台的确看到实例上有这个方法,但是我实际试了一下不管用,因此采取了这个方式来触发el-popover原始的关闭事件,因为这种情况下el-popover组件是个黑盒,我不确定是否还有其他配置需要修改,所以最稳妥的方式还是让逻辑在组件本身闭环)。如果单纯只隐藏元素的话,下一次需要点击两次才能触发弹窗,因为还欠了一次关闭的点击事件。

js 复制代码
// 表格滚动,要刷新当前基准位置
let scrollTable = document.querySelector(
  '.el-table__body-wrapper'
)
scrollTable.addEventListener('scroll', () => {
  // 刷新基准位置,因为滑动了内部的滚动条,之后,弹出框位置会发生变化
  this.popDomOffsetTop = parseInt(popDom.style.top)
  this.scrollWarpDomScrollTop = parseInt(scrollWarpDom.scrollTop)
  // 最外部滚动条滚动的距离+弹出框距顶部的距离+弹出框本身的高度,等于弹出框底部在页面上的位置
  let popBottomPos = scrollWarpDom.scrollTop + popDom.offsetTop + popDom.clientHeight
  // 超出了表格区域,弹出框消失。
  let scrollTableRect = scrollTable.getBoundingClientRect()
  if (
    (popBottomPos <
      scrollTableRect.top + this.scrollWarpDomScrollTop ||
      popBottomPos >
      scrollTableRect.bottom + this.scrollWarpDomScrollTop) &&
      popBottomPos !== scrollWarpDom.scrollTop // 这里是异步原因,当popDom刚生成的时候element有个生成动画,相关属性会为0
  ) {
    popDom.style.display = 'none' // 直接关闭避免抖动
    scrollWarpDom.click() // 触发点击事件让el-popoer再关闭一次
  }
  // 框离顶部或者底部很近的话直接消失,因为这个系统的最大容器是#app, 弹出框和app是同级的,会把body撑大,然后会又出现一个滚动
  if (
    parseInt(popDom.style.top) < 20 ||
    parseInt(popDom.style.top) + popDom.clientHeight > 900
  ) {
    popDom.style.display = 'none' // 直接关闭避免抖动
    scrollWarpDom.click() // 触发点击事件让el-popoer再关闭一次
  }
})
相关推荐
yanlele9 分钟前
我用爬虫抓取了 25 年 5 月掘金热门面试文章
前端·javascript·面试
中微子1 小时前
React状态管理最佳实践
前端
烛阴1 小时前
void 0 的奥秘:解锁 JavaScript 中 undefined 的正确打开方式
前端·javascript
中微子2 小时前
JavaScript 事件与 React 合成事件完全指南:从入门到精通
前端
Hexene...2 小时前
【前端Vue】如何实现echarts图表根据父元素宽度自适应大小
前端·vue.js·echarts
天天扭码2 小时前
《很全面的前端面试题》——HTML篇
前端·面试·html
xw52 小时前
我犯了错,我于是为我的uni-app项目引入环境标志
前端·uni-app
!win !2 小时前
被老板怼后,我为uni-app项目引入环境标志
前端·小程序·uni-app
Burt2 小时前
tsdown vs tsup, 豆包回答一坨屎,还是google AI厉害
前端
群联云防护小杜3 小时前
构建分布式高防架构实现业务零中断
前端·网络·分布式·tcp/ip·安全·游戏·架构