在做一个表格需求的时候遇到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再关闭一次
  }
})
相关推荐
崔庆才丨静觅6 小时前
hCaptcha 验证码图像识别 API 对接教程
前端
passerby60616 小时前
完成前端时间处理的另一块版图
前端·github·web components
掘了6 小时前
「2025 年终总结」在所有失去的人中,我最怀念我自己
前端·后端·年终总结
崔庆才丨静觅6 小时前
实用免费的 Short URL 短链接 API 对接说明
前端
崔庆才丨静觅7 小时前
5分钟快速搭建 AI 平台并用它赚钱!
前端
崔庆才丨静觅7 小时前
比官方便宜一半以上!Midjourney API 申请及使用
前端
Moment7 小时前
富文本编辑器在 AI 时代为什么这么受欢迎
前端·javascript·后端
崔庆才丨静觅8 小时前
刷屏全网的“nano-banana”API接入指南!0.1元/张量产高清创意图,开发者必藏
前端
剪刀石头布啊8 小时前
jwt介绍
前端
爱敲代码的小鱼8 小时前
AJAX(异步交互的技术来实现从服务端中获取数据):
前端·javascript·ajax