日常工作開發中,遇到哪些坑是讓你印象深刻且具有挑戰的,它們是怎麼產生的,我們該如何避免?本期我們帶來與前端開發相關的三個問題:一次網頁資源加載問題的定位過程;CSS中的z-index層疊覆蓋問題;CSS3 transform 屬性對 position 的影響,希望能爲你的技術提升助力。
01
一次網頁資源加載問題的定位過程
今天討論的話題比較聚焦,就是有用戶報Web頁面資源或局部區域加載不出來的問題,該如何查?又該如何避免?
1.1 問題描述
用戶反饋頁面內的推薦列表加載不出來,而且能穩定復現。
1.2 分析
第一反應就是自己復現,但是不行啊,團隊沒有同學能復現,也無法接觸到用戶。那麼先理清楚此區域的實現邏輯,該區域的HMTL雖然已經下載,但默認不可見,而是依賴c.js(文件名簡化了)展現。基於此,我們懷疑:
-
c.js 以及其依賴的a.js, b.js等 加載非常慢或直接失敗;
-
c.js 以及其依賴邏輯發生JS異常。
1.3 監控實現
那麼該如何佐證,我們需要做下監控。
做監控需要考慮到實際場景,由於Head和Body都有外鏈請求,因此監控代碼要置於頂部且內聯,且代碼要精簡,從而可以最大程度拿到信息和減少首屏影響。
window.addEventListener('error', event => {
if (event.srcElement !== window) {
console.log('資源加載失敗,加載資源的元素是:', event.srcElement);
send();
}
else {
console.log('JS報錯:', event.message);
send();
}
}, true);
window.addEventListener('unhandledrejection', event => {
const error = event.reason || {};
console.log('JS報錯:', error);
send();
});
上述可以監控到全局的 資源加載失敗 和 JS異常,包括未捕獲的Promise Rejection。此外,還需定位資源,通過向上遍歷獲取元素的XPath,XPath上帶上id或class屬性,避免全都是`div>div>div>div`,而摸不着頭腦。
上線後發現,有不少錯誤都是script error,這是因爲外鏈JS都是CDN域名。瀏覽器對於腳本執行遵守 same-origin policy ,爲避免域名信息泄漏給宿主域名,特意隱藏錯誤信息。
解決方案是給所有外鏈JS加上響應頭。
access-control-allow-origin: *
JS資源若託管在雲上CDN服務,那麼其CDN控制檯都會提供增加Header功能。
若是自有服務,就需要主動設置Header。這裏假設使用Koa2 框架,設置如下:
ctx.set('Access-Control-Allow-Origin', '*');
然後給所有該CDN域名的script元素增加crossorigin屬性,如:
<script src="https://cdn.a.com/static/a.js" crossorigin="anonymous"></script>
但發現其在Chrome瀏覽器是生效的,可以拿到真實錯誤堆棧。可惜,Safari瀏覽器不支持此機制,依然只能拿到script error。爲了Safari拿到真實錯誤,外鏈需要是同域。因此有個辦法是在www.a.com的Nginx做一層代理轉發,即 https://www.a.com/static/a.js 轉發到 https://cdn.a.com/static/a.js 。不過如此一來,就喪失CDN就近訪問的優勢,不適合常態化。但可以在定位用戶問題時,將用戶訪問臨時切換成 https://www.a.com/static/a.js。
那如何監控加載慢,可以在onload事件後獲取慢資源。
const list = performance.getEntriesByType('resource');
const len = list.length;
const slowList = [];
for (let i = 0; i < len; i++) {
const timing = list[i];
// 大於1s
if (timing.duration > 1000) {
slowList.push(timing);
}
}
send(slowList);
上報信息也包含了每個請求的TCP、下載等耗時,詳細可見Resource_Timing_API規範(https://developer.mozilla.org/en-US/docs/Web/API/Resource_Timing_API/Using_the_Resource_Timing_API)。
要注意的是,該API信息受到CORS (https://developer.mozilla.org/en-US/docs/Glossary/CORS)影響,必須給資源設置如下響應頭才能拿到數據,否則很多字段取值都是0。
Timing-Allow-Origin: *
通過分析這些數據,我們可以知道資源耗時過程,這裏可以充分參照Chrome開發者工具提供的Timing子面板,瞭解關鍵時段耗時。比如同域名6個併發請求限制,若資源請求較多,則可能處於stalled(掛起等待)狀態。這些階段可能原因分析參考Chrome說明。
由於有些圖片在頁面內不好確定位置,因此需要獲取其XPath,如何獲取呢?對於img標籤加載圖片,可以快速獲取:
// timing.name就是資源地址,適合
const img = document.body.querySelector('img[src="' + timing.name + '"]');
const xpath = getxpath(img);
額外信息獲取,其實除了問題信息本身外,用戶當時環境信息非常重要,如
1. performance.memory:獲取內存使用信息;
2. navigator.connection:用戶連接情況,可惜,此API不怎麼準;
如果是APP內頁面,可以調用端能力獲取到用戶網絡類型、網速。
1.4 數據分析
上線後,用戶依然訪問慢。根據日誌發現,用戶未有JS報錯,但根據慢資源日誌,發現有大量資源加載超過1s,有些資源甚至達到10s,其花費在建連階段耗時較大。看來問題還不是這麼簡單,那如何解釋用戶資源加載如此慢?用戶所在局域網環境差?接入運營商質量差?插件影響?CDN問題?
這些問題只有CDN可以去排查,我們首先拿到用戶訪問 https://www.a.com 的服務器端日誌,獲取到用戶IP。聯合雲CDN服務,根據用戶IP,竟然未查到任何CDN訪問日誌。但從日誌看,用戶確實請求了大量JS,並不是全部本地緩存。所以用戶IP可能不是訪問CDN的IP,有一種情況是使用代理。
所以想通過其他信息撈取用戶的CDN日誌,來確定訪問CDN的IP。思路是按照用戶UA、請求地址等非精確信息撈取CDN日誌,但由於撈取日誌量非常大,無法確定哪些日誌是該用戶的;而:
1.CDN域名一般是無cookie域名,沒有TRACEID;
2.Web頁面無法給script/link資源請求頭設置Traceid;
3.前端目前無法拿到CDN TCP建連IP;
綜上,除了用戶IP,種種精確定位信息都無效。因此這一步舉步維艱。
好在能聯繫上用戶,諮詢是否使用代理,結果:確實使用了代理,且根據用戶代理,實測代理非常慢,甚至加載超時,其代理請求走了香港服務器。通過代理出口IP,最終從海外CDN撈取到了用戶訪問日誌。同時也確認,用戶將所有 *.a.com 請求配置不使用代理,所以服務端能拿到的IP是用戶IP,而CDN拿到的是代理出口IP。
1.5 總結
Web能力終是有限,但通過足夠的輔助信息,以及全鏈路協作,在定位Case過程中也會事半功倍。該Case雖然歸因於用戶不合理代理配置,但也可基於此場景做技術優化。如任何首屏內容不依賴外鏈展現,減少核心邏輯JS體積,增加資源緩存率等。
02
CSS中的z-index層疊覆蓋問題
2.1 含義
z-index屬性指定了元素及其子元素的【z順序】,而【z順序】可以決定當元素髮生覆蓋的時候,哪個元素在上面。通常一個較大的z-index值的元素會覆蓋較低的那一個。
屬性指定兩件事:
-
當前元素的堆疊順序
-
當前元素是否建立新的堆疊上下文
2.2 屬性值
-
默認值:z-index:auto;
-
整數值:z-index:<integer>;
-
繼承:z-index:inherit;
2.3 基本特性
-
在CSS2.1時代,需要和定位元素配合使用;
-
如果定位元素z-index沒有發生嵌套(並列的):
-
後來者居上的準則;
-
哪個大哪個上(z-index大小比較);
-
-
如果定位元素z-index發生嵌套:
-
祖先優先原則(前提:z-index是數值,不是auto)
-
以上爲z-index的基本介紹。
當業務越來越複雜,多種彈窗、toast、浮層各種組件,多人協同業務開發的情況下:
-
老業務寫了個z-index:5000;
-
B同學調用一個全局彈層,原本設置爲100,想要覆蓋全局,z-index改爲10000;
-
C同學調用一個toast,原本設置爲2000,想要覆蓋彈層,z-index改爲100000;
-
...
-
層層覆蓋的情況下,不能無限改下去,爲了避免這樣的情況發生,減少不斷覆蓋的情況,那麼應該如何規定z-index的值呢;
const KEY = '_tbv_z_index_';
function initZIndex$() {
return (window[KEY] = 10000);
}
// 初始化,只能被調用一次
function once(fn) {
const flag = true;
return function () {
if (flag) {
flag = false;
const args = Array.prototype.slice.call(arguments, 0);
fn.apply(args);
}
};
}
const initZIndex = once(initZIndex$);
// 外部調用&支持重置
function zIndex$(zIndex) {
if (zIndex) {
return (window[KEY] = zIndex);
}
return (window[KEY] += 1);
}
// 組件mount時觸發+1
const zIndex = zIndex$();
注意:還是要規範z-index的配置,不要亂用濫用隨意賦值,根據依賴規則合理使用;
03
CSS3 transform 屬性對 position 的影響
CSS3中引入了transform,定義了在二維或三維空間中元素的旋轉、縮放、平移等行爲,還能利用合成層原理開啓GPU加速,提升頁面動畫的流暢度。然而transform也不是「省油的燈」(並沒有說它不好的意思,我就很喜歡它),增強了頁面交互效果的同時它也有一些「副作用」容易讓人踩坑。
position: fixed 實現了固定定位的效果,元素不追隨滾動條進行滾動,普通元素的overflow屬性也無法對其進行裁剪,因此在一些需要固定頭部、固定懸浮按鈕的場景中十分好用。
但fixed遇上transform時表現的就不再那麼「強硬」,反而退化成了position: absolute的效果。在外層沒有transform影響時,固定定位元素的包含塊是根元素,可以近似認爲是<html>元素,因此fixed元素可以實現相對視口定位的效果。而當元素設置了transform時,便會創建一個新的包含塊(containing block),如果該元素的內部有元素設置了fixed定位,那麼該fixed元素的包含塊便不再是根元素,而變成了被設置了transform的元素。如果在開發過程中發現設置了position:fixed的元素隨着頁面滾動了,就可以看下fixed的元素外層是否有元素設置了transfrom。
除了包含塊之外,transform還會生成新的層疊上下文(stack context),使得元素內部和外部的z-index相互獨立,出現低z-index元素層級比高z-index元素還高的情況: