// 為什么叫《大事記》?
// 以前總有面試官問這樣一個問題:“你在項目中遇到過最頭疼的問題是什么,是怎么解決的?”
// 當時總覺得,已解決的問題都算不上頭疼,所以回答總是不盡人意。
// 最近遇到微信端的這個問題,非常讓人頭疼,正好有小伙伴和我聊到面試經驗,靈機一動,《大事記》由此而生
問題描述:
在安卓系統的微信瀏覽器里面,<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> 的問題由來已久,開發團隊也曾經說過要解決,但最后都不了了之
這大約都是時辰的錯