- // 为什么叫《大事记》?
- // 以前总有面试官问这样一个问题:"你在项目中遇到过最头疼的问题是什么, 是怎么解决的?"
- // 当时总觉得, 已解决的问题都算不上头疼, 所以回答总是不尽人意.
- // 最近遇到微信端的这个问题, 非常让人头疼, 正好有小伙伴和我聊到面试经验, 灵机一动,《大事记》由此而生
问题描述:
在安卓系统的微信浏览器里面,<video> 标签触发了 play() 事件, 即开始播放之后
<video> 标签的层级会变成 MAX 级别, 无论如何设置 z-index, 都会遮挡别的脱离文档流的元素
分析原因:
微信的 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> 的 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> 的问题由来已久, 开发团队也曾经说过要解决, 但最后都不了了之
这大约都是时辰的错
来源: https://www.cnblogs.com/wisewrong/p/10276947.html