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