本篇文章介紹了一個在開發(fā)中遇到的詭異的問題检盼,排查問題過程頗為艱辛肯污,不過最終結(jié)果還是值得的,因為鞏固了一些基礎知識和好的調(diào)試方法吨枉,它們是:
- fixed定位的特點
- 樣式優(yōu)先級的判定
- 如何使用瀏覽器的DOM還原一閃即逝的錯誤效果
1. 問題說明
在獵蘿卜-獵頭端蹦渣,測試提出這樣一個bug(禪道編號12828):
在獵蘿卜的 訂單 - 我的職位 - 已接單 入口,點擊右側(cè)職位列表的任意職位貌亭,在彈出職位預覽之前(圖2)柬唯,會先看到職位詳情的底部串到左下角位置(圖1)
2. 調(diào)用過程
首先介紹下觸發(fā)整個動作的邏輯,為了方便理解圃庭,借用上面的線框圖來說明:
-
projectList.vue
是職位列表锄奢,其中每個職位對應的條目封裝成了order-project-item.vue
失晴,點擊order-project-item.vue
后,會觸發(fā)事件全局的expandOrderPreview
-
order-project-preview.vue
作為一個常駐在界面右側(cè)的組件拘央,默認是收起在屏幕右側(cè)的涂屁,在偵聽到了expandOrderPreview
事件以后,會從右側(cè)彈出 -
next-bar-affix.vue
作為order-project-preview.vue
底部的子組件灰伟,會一起出現(xiàn)和消失
3. 具體的代碼邏輯
在下圖中的邏輯代碼拆又,整理成如下描述:
order-project-item.vue
觸發(fā)點擊事件expandOrderPreview
,被order-project-preview.vue
偵聽到栏账,然后進行如下處理:
-
第一步
emit showOrderPreview
事件- 父級
app-frame.vue
收到showOrderPreview
事件帖族,給引用的order-project-preview.vue
添加一個.previewing
的樣式 -
.previewing
會使order-project-preview.vue
的寬度從0變到特定寬度從而在右側(cè)展示
- 父級
-
第二步 調(diào)用
refresh
方法- 將
loading
設置為2,order-project-preview.vue
中詳情面板受到loading==0
的v-if
控制挡爵,因此會隱藏竖般;loading組件發(fā)現(xiàn)loading>0
因此會有加載效果 - 異步調(diào)用
getProjectDetail
和getOrders
這兩個方法,直到結(jié)果返回茶鹃,loading
值會遞減至0捻激,詳情才開始正常展示,加載效果才會去除
- 將
4. 一些基礎知識
解決問題之前前计,通過查看樣式代碼胞谭,我們鞏固了一些基礎知識。
如下是抽取出來的DOM結(jié)構(gòu)和樣式設置:
<div class="app-frame">
<div class="order-project-preview">
<div class="next-bar-affix">
<div class="h-affix" style="bottom:0"></div>
</div>
</div>
</div>
.order-project-preview {
position: fixed;
top: 0;
bottom:0;
right: 0;
width: 300px;
}
.next-bar-affix {
position: fixed;
bottom: 0;
}
.next-bar-affix .h-affix {
position: fixed;
height: 60px;
}
-
.order-project-preview
進行position: fixed
設置后男杈,DOM的位置會相對視口固定丈屹,通過left
,right
,top
,bottom
來設置定位 - 在上述示例代碼中,
.order-project-preview
寬度300px
伶棒,高度撐滿屏幕旺垒,底部欄.next-bar-affix
底部內(nèi)容區(qū)的高度為30px
- 其中
.next-bar-affix
部分的代碼是由heyui
的affix
組件生成的,一個有意思的啟發(fā)是肤无,在.h-affix
中也添加了一個bottom:0
的屬性先蒋。可以嘗試將該屬性去除宛渐,會發(fā)現(xiàn)底部的內(nèi)容區(qū)域會跑到最底部之下竞漾,變得不可見。 - 綜上窥翩,可以從上述效果圖推測的一個結(jié)論是:
如果一個子容器設置了
fixed
定位业岁,它的父容器也是fixed
,那么在不指定子容器的left
,right
,top
,bottom
時寇蚊,子容器的邊界會以父容的top
left
作為邊界笔时。進一步可以推斷,在我們的問題中仗岸,必然是有什么因素使得作為父容器的.order-project-preview
失去了作為子容器定位基準的條件**允耿,從而跑到了視口的左下角借笙。
5. 具體推測
做過的一些不靠譜的推測:一個DOM樣式的變化 A->A+B
實際上是 A -> 無 -> A+B
,即order-project-preview.vue
增加 .previewing
的過程中较锡,容器樣式到無
的中間過程暫時使得next-bar-affix.vue
丟失了left
的基準提澎,從而導致在中過程中串到了視口左下角。
this.$emit('showOrderPreview');
this.refresh();
基于上述推測念链,為了解決這個問題盼忌,考慮將上述兩行代碼對調(diào):
- 先讓
refresh
的邏輯去觸發(fā)order-project-preview.vue
的隱藏,自然也就隱藏了next-bar-affix.vue
- 再觸發(fā)
showOrderPreview
事件來order-project-preview.vue
的展開 - 等
refresh
觸發(fā)的異步調(diào)用結(jié)束以后掂墓,order-project-preview.vue
中的具體內(nèi)容才連同next-bar-affix.vue
被展示出來
但結(jié)果不如預期的那樣谦纱,猜想也許是refresh
成功隱藏next-bar-affix.vue
之前,this.$emit('showOrderPreview');
的邏輯引起的彈出生效了君编,于是添加了如下代碼(事實上純屬沒有依據(jù)的HardCode):
this.$emit('showOrderPreview');
this.$nextTick(() => {
this.refresh();
})
運行跨嘉,沒在出現(xiàn)如上所示的問題,似乎是解決了吃嘿。
6. 問題又重現(xiàn)了
然后祠乃,提交代碼后,測試同學告知兑燥,該問題又重現(xiàn)了亮瓷,重現(xiàn)步驟姑且略過,這時候聯(lián)想到之前排錯的過程中降瞳,就猜想可能跟瀏覽器的渲染順序有關(guān)系嘱支,然后把瀏覽器的工作原理的基礎介紹翻閱了一遍,但并沒有解答這個問題挣饥。
又想起團隊的caoq同學分享過如何監(jiān)聽DOM上的屬性變化除师。問題難點無非在于無法讓瀏覽器在某個異常的時刻停下來,如果能停下來觀測DOM的結(jié)構(gòu)和樣式扔枫,差不多就可以排查到問題根源汛聚。于是給order-project-preview.vue
的根節(jié)點設置了attribute modification
的斷點(設置方法如下圖所示):
這時候意外地發(fā)現(xiàn)在異常發(fā)生時,主界面order-proejct-preview.vue
的內(nèi)容已經(jīng)加載出來了(但卻沒顯示)短荐,即不存在next-bar-affix.vue
丟失默認的left基準的問題倚舀。
倒是發(fā)現(xiàn)了該容器上添加了一個 .h-loading-parent
的樣式,這個樣式定位為 relative
搓侄。由此基本能確定了在heyui
的loading
組件的渲染中瞄桨,會修改所在父容器order-project-preview.vue
的樣式,給它添加上.h-loading-parent
讶踪。
因為 order-project-preview.vue
最外圍默認的樣式是 fixed
, 被.h-loading-parent
覆蓋之后變成了 relative
(它們具有相同的優(yōu)先級,后出現(xiàn)的relative
覆蓋前出現(xiàn)的fixed
)泊交,從而導致了next-bar-affix.vue
的left
基準丟失乳讥。我們只要把給彈出的面板order-project-preview.vue
加上更高優(yōu)先級的fixed
定位即可柱查。
//loaing組件提供 樣式
.h-loading-parent {
position: relative;
}
// 自書寫的樣式
.order-project-preview-page {
// 兩個class累積的優(yōu)先級為2,大于.h-loading-parent的1:
&.previewing {
position: fixed;
}
}
7. 再深入一些
問題:斷點過程已經(jīng)渲染好的面板卻看不到內(nèi)容,只看到一個錯位的底部欄
通過查看heyui
的<loading>
組件源碼的樣式可以發(fā)現(xiàn):
-
heyui
組件在loading
完畢后云石,會設置500ms
的timeout
來清除.h-loading-parent
的樣式(參加下圖1)唉工,即內(nèi)容渲染出來了,但是樣式500ms
內(nèi)還沒去除汹忠,也就導致了relative
引起的錯位 -
relative
的屬性同樣影響到了.order-project-preview-page.previewing
的高度淋硝,因為它的各個子元素無一例外都absolute
的,因此這樣的文檔流無高度的宽菜,而被覆蓋的fixed
谣膳,通過了top',
bottom`賦予了它和視口相同的高度。
問題2 我們在本地代碼上無法重現(xiàn)該問題
在本地代碼運行在瀏覽器的樣式發(fā)現(xiàn):.order-project-preview-page
上設置的fixed
的優(yōu)先級高于 h-loading-parent
的relative
铅乡。
因此可以做一個猜測
- 在開發(fā)環(huán)境中庫的樣式被先加載继谚,自己組件內(nèi)地樣式后加載,如果計算的優(yōu)先級的值一樣的話阵幸,自己組件的樣式自然就失效了花履。
- 在線上環(huán)境,樣式的代碼經(jīng)過壓縮以后挚赊,并不能保留開發(fā)環(huán)境中的出現(xiàn)順序诡壁,因此需要人為的設置來保證所期待的優(yōu)先級。