欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

大事记 - 安卓微信浏览器 video 标签层级过高

程序员文章站 2022-07-05 15:35:31
// 为什么叫《大事记》? // 以前总有面试官问这样一个问题:“你在项目中遇到过最头疼的问题是什么,是怎么解决的?” // 当时总觉得,已解决的问题都算不上头疼,所以回答总是不尽人意。 // 最近遇到微信端的这个问题,非常让人头疼,正好有小伙伴和我聊到面试经验,灵机一动,《大事记》由此而生 问题描 ......

// 为什么叫《大事记》?

// 以前总有面试官问这样一个问题:“你在项目中遇到过最头疼的问题是什么,是怎么解决的?”

// 当时总觉得,已解决的问题都算不上头疼,所以回答总是不尽人意。

// 最近遇到微信端的这个问题,非常让人头疼,正好有小伙伴和我聊到面试经验,灵机一动,《大事记》由此而生

 

问题描述:

在安卓系统的微信浏览器里面,<video> 标签触发了 play() 事件,即开始播放之后

<video> 标签的层级会变成 max 级别,无论如何设置 z-index,都会遮挡别的脱离文档流的元素

大事记 - 安卓微信浏览器 video 标签层级过高

 

 

分析原因:

微信的 x5 内核为了统一 <video> 在不同的手机上的呈现形式,对 <video> 进行了改造

这样的改造在 ios 系统上一切正常,但在安卓系统就会有各种问题,比如这里的层级太高

 

 

解决方案:

当测试的同事将这个 bug 提给我的时候,我根本没想到,我即将面对一场苦不堪言的角斗

 

第一回合:隐藏 video

最初暴露的问题并不是页面底部的按钮,而是一个弹窗

在了解了问题的原因之后,我当时的思路是:

打开弹窗的时候,将 <video> 标签隐藏掉,关闭弹窗的时候再显示 <video>

隐藏标签的方法有很多:display:none;   visibility: hidden;  z-index: -1;   left: -9999px;  opacity: 0;

但 display:none 没有占位,visibility 和 z-index 不起作用,opacity 虽然不显示元素,但依旧点不到下面的元素

所以只有用定位的办法了

  let tag = document.createelement('style')
  tag.id = id
  tag.innerhtml = `video { position: relative; left: -9999; }`
  body.appendchild(tag)

在打开弹窗的时候,通过上面的代码添加一个带有特殊 id 的 <style> 标签,然后在关闭时候根据 id 删除节点

为了防止多级弹窗的时候重复创建 <style>,在方法前面需要验证是否存在该 id

想通了这一系列逻辑之后,我猛然发现,页面底部的按钮也会被遮挡!

 

第二回合:跳转到单独页面播放

深思熟虑之后,我得出结论:遮挡问题无解

但问题还是要解决,于是我向 pm 提出,单独写一个播放页面,点击 <video> 的时候跳转到这个页面进行播放

经过一番唇枪舌剑的交锋,pm 妥协了,但要求尽量优化体验,打开的播放页看起来要像全屏播放一样

“这都不是事儿!” 我如是回道

 

播放页面确实不是事儿,可 <video> 真不是省油的灯

我原本想的是,全局添加一个 addeventlistener('click'),如果点击的是 <video> 标签,就保存视频信息,并跳转到播放页面

document.addeventlistener('click', (e) => {
  let target = e.target
  if (target.nodename.touppercase() === 'video') {
      this.setvideourl({
        url: target.src,
        poster: target.poster
    })
    this.$router.push(`/video`)
  }
})

这下跳转是没问题了,但在点击的时候,实际上还触发了 <video> 的 play() 事件

从理论上来说,已经跳转页面了,这个 play() 事件并不需要阻止,但为了逻辑严谨,我还是做了尝试

e.preventdefault()
e.stoppropagation()
e.cancelbubble()
return false

然而这并不能阻止播放事件 play()

那就不阻止了

然后又了新的 bug:部分机型从播放返回之后,<video> 是播放的状态,而且有层级问题

 

第三回合:禁用 controls

我重新回到那个问题:如何阻止播放事件?

稍作挣扎,我就换了一个思路:如果没有播放按钮,那就不需要阻止播放事件了

于是我给 <video> 添加了 controls=""

这样就没有播放工具栏,之后只需要手动添加一个三角形的播放图标,一切就完美了

 

页面上的 <video> 是作为描述内容的一部分,包含在一段富文本里面,从后端返回的

这样一来,<video> 相关的 dom 节点只能通过 js 修改,成本太高,所以我打算只用 css 来解决播放图标的问题

 

然后我画了一个播放的图标,给 <video> 添加了一个伪元素 :before,在伪元素里写好了样式,但毫无作用

原来 <video> 并不支持伪元素

“如果无法解决问题,那就让问题不存在”

我脑海中闪过这段话,然后有了新的方案:

大事记 - 安卓微信浏览器 video 标签层级过高

我又画了一张图,然后将 <video> 的 poster 改成了这张图,问题解决了!

然后产品小姐姐跑过来:你对我的视频封面图做了什么?

 

决战:js 王道

既然 poster 不能改,那就只有通过 js 去操作 dom,给 <video> 添加一个兄弟节点 <i class="video-play_btn"> 作为播放按钮

然后将 <video> 和播放按钮一起包在一个容器 <div class="video-wrapper"> 中

setvideowrapper () {
  this.$nexttick(() => {
    let v = document.getelementsbytagname('video')
    if (v && v[0]) {
      // 产品规定 页面中只会有一个 <video>
      let target = v[0]
      // 防止重复创建 wrapper 
      if (target.parentnode.classname === 'video-wrapper') return
      // 清除 <video> 播放工具栏
      target.controls = ''
      target.classname = 'video-hack'
      // 创建播放按钮
      let btn = document.createelement('i')
      btn.classname = 'video-play_btn'
      // 创建容器
      let wrap = document.createelement('div')
      wrap.classname = 'video-wrapper'
      wrap.appendchild(btn)
      wrap.appendchild(target.clonenode())
      // 插入容器并删除原本的 <video>
      target.parentnode.insertbefore(wrap, target)
      target.parentnode.removechild(target)
    }
  })
}

再添加对应的 less 样式:

.video {
  &-wrapper {
    position: relative;
    font-size: 0;
  }
  &-play {
    &_btn {
      position: absolute;
      top: 0;
      left: 0;
      right: 0;
      bottom: 0;
      background: rgba(0, 0, 0, .1) url('img/url') center/80px 80px no-repeat;
    }
  }
}

终于,<video> 的问题彻底解决了,皆大欢喜,普天同庆

但我还是要吐槽一下,微信 <video> 的问题由来已久,开发团队也曾经说过要解决,但最后都不了了之

这大约都是时辰的错