监听弹窗 - 动态优先级

监听弹窗 - 动态优先级

前言

最近接到新手引导的需求中,由于新手引导浮窗可以拖拽到页面任何位置,要求它的优先级必须高于页面元素,但又不能高于其他弹窗蒙层,否则会和其他弹窗共存。因此,需要监听当前是否有弹窗,来实现动态优先级,在有弹窗的时候,让新手引导浮窗优先级动态降低。

那么问题来了,怎么监听当前是否有弹窗弹出呢?

正文

首先我需要知道如何判断当前是否有元素遮盖,我查了下api发现了IntersectionObserver配置中的trackVisibility追踪可见性变化,正好符合需求。可以追踪到指定元素是否被其他元素覆盖。用法如下:

this.observe = new IntersectionObserver(() => {
  // 当监听的元素被其他元素覆盖或者不可见的时候就会触发
},{
  delay: 100, // 多久通知一次变化,默认为100毫秒通知一次,限制最低只能100,为了避免过于消耗性能
  trackVisibility: true, // 是否追踪可见性变化, 设置该属性后必须设置delay
})
this.observe.observe(element)

那么问题来了,应该监听哪个元素呢?经过我的试验,监听body没法检测到被其他弹框覆盖,并且,得是完全覆盖,才会触发,遮住元素一半也不会触发。最后我监听了头部导航栏,通常来说,能覆盖头部导航栏的一般都是弹窗。

但是还有问题,我如何得知当前覆盖弹窗的优先级呢?这要求我得先取到这个覆盖的节点。通过查阅发现document.elementFromPoint(x,y)可以获取指定坐标置顶元素。接以上代码如下:

let header = document.querySelector('.header')
this.observe = new IntersectionObserver(() => {
  // 当监听的元素被其他元素覆盖或者不可见的时候就会触发
  // 获取头部的顶层元素,之所以写1,1坐标是因为头部本身在最顶上,所以不需要去取具体位置
  // 直接取左上角的点即可
  let topElement = document.elementFromPoint(1, 1)
  // 如果头部是顶层元素,说明没有遮盖,消除设置的zIndex
  if(topElement==header){
      target.style = ''
      return
  }
  // 执行遮盖的逻辑
  // 取得置顶元素,也就是弹窗的样式
  let style = getComputedStyle(topElement)
  console.log(style.zIndex)
},{
  delay: 100, // 多久通知一次变化,默认为100毫秒通知一次,限制最低只能100,为了避免过于消耗性能
  trackVisibility: true, // 是否追踪可见性变化, 设置该属性后必须设置delay
})
this.observe.observe(header)

写到这,我产生了个疑问,覆盖头部的就一定是弹窗吗?显然不是,那么我们需要思考弹窗的特征有哪些?

我想了想,弹窗通常都是覆盖全屏,并且是fixed定位,于是我增加以下判断:

// ... 省略以上代码
// 取得置顶元素,也就是弹窗的样式
let style = getComputedStyle(topElement)
let bound = topElement.getBoundingClientRect() // 获取置顶元素布局信息
let isModal = bound.width>=window.innerWidth && bound.height>=window.innerHeight && style.position=='fixed'
if(isModal){
  // target 为新手引导,降低他的优先级,使其不会跟弹窗共存
  target.style.zIndex = style.zIndex - 1
}
// ... 省略以下代码

现在基本能判断弹窗了,但我在测试的过程中又发现了另一个问题,在其他页面的新手引导,与我写的全局新手引导浮层冲突了,出现了两个引导共存的现象,原因是因为,其他页面的新手引导是用box-shadow高亮当前元素,发散阴影到全屏,此时头部并没有覆盖元素,所以也没触发监听头部的可见性回调。因为盒阴影不是元素,不算遮盖。

于是问题变了,我还需要判断除了弹窗,是否还有全屏阴影覆盖,此时需要降低我的全局新手引导浮层优先级,使页面的优先展示。为此我想了以下方案做尝试:

  1. 利用mutationObserver监听所有dom插入删除,属性修改,只要有box-shadow属性修改,就降低我的全局新手引导浮层优先级。经过尝试无效,样式类直接应用不会触发mutationObserver,排除该方案。
  2. 尝试将弹窗前后的网页生成canvas截图来进行对比,取像素值进行比较,看是否一致,不一致则说明有元素覆盖。试验发现需要至少2s才能生成canvas,性能过低,放弃。
  3. 退而求其次,阴影这种特殊情况,看见一个加一个,通过阴影的类进行判断。无法覆盖未来增加的阴影类,也无法知道当前项目,有哪些类会产生覆盖全屏的阴影。即便能拿到,也不是最佳方案

最后,我想着,要不然就遍历所有dom,挨个判断是否有全屏阴影得了,内心对这个方案并不认可,只是想尝试一下,因为在我印象中,dom操作都是极其耗性能的,并且还是遍历的所有dom。但意外的发现:

console.time('total time')
document.body.querySelectorAll('*').forEach(el=>{
  let style = getComputedStyle(el)
})
console.timeEnd('total time')

结果输出:

> total time: 1.27490234375 ms

看到这,我有点意外,我遍历整个dom,并获取样式,整整1571个节点,只花了1毫秒。
但仔细想想,便明白了,操作dom比较耗性能的地方主要在插入和删除,以及一些css属性影响到浏览器渲染,导致重排重绘,重新生成dom渲染树。我在此处,仅仅是查询节点,并获取样式,并没有对浏览器渲染产生任何影响。甚至可能获取的节点及样式本身浏览器就有缓存,取下内存的数据。并不比普通的循环1571次相差太多性能,完全可以当成普通循环几千次。意味着我可以采用当前方案。代码如下:

this.checkShadowTimer && clearInterval(this.checkShadowTimer)
let timeStart = +new Date()
let nodes = document.body.querySelectorAll('*')
this.isRepeatShadow = false
this.checkShadowTimer = setInterval(()=>{
    let timeEnd = +new Date()
    // 如果顶层元素不是header 说明有弹框覆盖
    let isCover = header && document.elementFromPoint(1, 1) != header
    // 如果大于10秒还没检测到全屏引导阴影则可以关闭定时器
    if(timeEnd - timeStart > 10*1000){
        clearInterval(this.checkShadowTimer)
        return
    }

    // 如果当前被弹窗覆盖全屏,则无需检测是否有全屏阴影, isRepeatShadow确保只设置一次优先级,无需重复遍历设置
    if(isCover || this.isRepeatShadow){
        return
    }

    // 遍历所有节点,查找全屏阴影,对性能影响不大
    nodes.forEach((el)=>{
        let style = getComputedStyle(el)
        // 有可见覆盖全屏阴影,尺寸超过2000的
        if(
        style.boxShadow && style.boxShadow!='none' && 
        style.boxShadow.split(' ').some(v=> !isNaN(parseInt(v)) && parseInt(v)>=2000) && 
        style.opacity!=0 && 
        style.display!='none'){
            this.isRepeatShadow = true
            // target为全局的新手引导
            target.style.zIndex = 100
        }
    })
}, 100)

结束

至此,结合判断弹窗和阴影的方式,即可实现全局浮层的优先级控制,实现动态优先级,保持优先级始终在弹框和阴影之下 😃

  • 27
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
监听弹窗的关闭函数,可以使用以下方法: 1. 使用JavaScript实现:可以使用事件监听器来监听弹窗的关闭事件。首先,需要使用document.querySelector等方法获取到弹窗的关闭按钮元素,并将其赋值给一个变量。然后,使用addEventListener函数为该关闭按钮元素绑定一个click事件的监听器。在监听器的回调函数中,可以执行关闭弹窗的操作。 示例代码如下: ```javascript // 获取弹窗关闭按钮元素 const closeButton = document.querySelector('.close-button'); // 绑定关闭事件的监听器 closeButton.addEventListener('click', function() { // 执行关闭弹窗的操作 // 可以使用style.display = 'none'来隐藏弹窗 }); ``` 2. 使用jQuery实现:如果已经引入了jQuery库,可以使用它提供的方便方法来监听弹窗的关闭事件。首先,需要使用选择器选择到弹窗的关闭按钮元素,并将其赋值给一个变量。然后,使用jQuery的on函数为该关闭按钮元素绑定一个click事件的监听器。在监听器的回调函数中,同样可以执行关闭弹窗的操作。 示例代码如下: ```javascript // 获取弹窗关闭按钮元素 const $closeButton = $('.close-button'); // 绑定关闭事件的监听器 $closeButton.on('click', function() { // 执行关闭弹窗的操作 // 可以使用hide方法来隐藏弹窗 }); ``` 以上是两种常见的实现方法,具体使用哪种方法取决于具体的项目需求和使用的技术栈。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值