// 爲何叫《大事記》?css
// 之前總有面試官問這樣一個問題:「你在項目中遇到過最頭疼的問題是什麼,是怎麼解決的?」node
// 當時總以爲,已解決的問題都算不上頭疼,因此回答老是不盡人意。web
// 最近遇到微信端的這個問題,很是讓人頭疼,正好有小夥伴和我聊到面試經驗,靈機一動,《大事記》由此而生面試
問題描述:後端
在安卓系統的微信瀏覽器裏面,<video> 標籤觸發了 play() 事件,即開始播放以後瀏覽器
<video> 標籤的層級會變成 MAX 級別,不管如何設置 z-index,都會遮擋別的脫離文檔流的元素微信
分析緣由:app
微信的 X5 內核爲了統一 <video> 在不一樣的手機上的呈現形式,對 <video> 進行了改造ide
這樣的改造在 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> 的問題由來已久,開發團隊也曾經說過要解決,但最後都不了了之
這大約都是時辰的錯