絕大多數網頁會採用JavaScript程式碼實現圖片延遲載入,在實現方案中,要一直偵聽onscroll事件並計算圖片位置,圖片數量越多越容易造成卡頓。為此Chromium提出在瀏覽器層面實現的圖片延遲載入(設計文件),其底層原理是透過IntersectionObserver(監聽某個元素與其祖先或者視口的交叉狀態確定其是否進入可視區域)控制延遲載入的時機。

文件中僅僅給出瞭解決思路,目前chromium中並沒有相關實現程式碼,我們根據該思路做了實現,效果如下:

圖片:

Web基礎體驗改進四:lazy load

https://www。zhihu。com/video/982298280482410496

CSS背景圖片:

Web基礎體驗改進四:lazy load

https://www。zhihu。com/video/982298354574872576

frame:

Web基礎體驗改進四:lazy load

https://www。zhihu。com/video/982298421713047552

以下是實現方案。

1. 圖片的延遲載入

對於標籤,透過lazyload屬性判斷是否需要延遲載入;對於CSS背景中的圖片,透過background-image-lazyload: true實現延遲載入。

在請求圖片資源時,為了在保持頁面佈局的情況下推遲圖片的載入,首先進行部分請求(range request),即請求下載圖片資源的前2KB。如果圖片資源服務端支援並且從返回的2KB資料中能夠解析得到源圖片的尺寸及大小資訊,則根據此尺寸及大小資訊構造一個PlaceholderImage來替代源圖片。同時透過IntersectionObserver監聽該圖片元素與視口的交叉狀態,在該圖片進入視口區域後重新載入顯示源圖片。

1.1 當前Chromium的支援

如果圖片資源服務端不支援或者無法解析獲取圖片尺寸資訊,則按照原有載入流程載入源圖片。

在Chromium中有相應的設定項支援對圖片進行部分請求,圖片資源請求引數類FetchParameters中的列舉型別PlaceholderImageRequestType表明是否允許請求PlaceholderImage:

Web基礎體驗改進四:lazy load

載入圖片資源時,會呼叫LocalFrame::MaybeAllowImagePlaceholder()方法:

Web基礎體驗改進四:lazy load

若當前設定允許對圖片進行range request則透過FetchParameters::SetAllowImagePlaceholder()方法對請求引數進行設定,在HTTP請求頭欄位中設定range欄位為“bytes=0-2047”,表明只請求資源的一部分:

Web基礎體驗改進四:lazy load

1.2 具體實現

每個圖片資源都對應一個ImageResourceContent物件,ImageResourceContent擁有實際的影象(blink::Image),img圖片標籤以及透過css設定的背景圖片分別透過ImageLoader和CSSImageVlaue、CSSImageSetValue請求對應的ImageResourceContent。當請求的圖片資源資料返回時,會呼叫ImageResource::UpdateImage(),進而呼叫ImageResourceContent::UpdateImage()。在ImageResourceContent::UpdateImage()中根據返回資料建立實際的Image物件,若允許顯示PlaceholderImage且從返回的資源資料中可以解析得到源圖片的尺寸資訊,則實際建立PlaceholderImage替代原圖並監聽該圖片資源對應的元素(img標籤或者設定了該圖片為背景圖的其他標籤)。因此,新增一個LazyImagesDelegate類並在ImageResourceContent中暴露一個公開的設定方法ImageResourceConent::SetLazyImagesDelegate()。當在ImageResourceCongent::UpdateImage()中建立了PlaceholderImage後透過LazyImagesDelegate對相應的元素進行監聽。LazyImagesDelegate為純虛類,子類LazyImagesDelegateImpl具體實現相關方法,基本實現如下:

Web基礎體驗改進四:lazy load

當透過ImageResourceContent::Fetch()方法獲取ImageResourceContent後,呼叫靜態方法LazyImagesDelegateImpl::LoadFullImageWhenElementIsAlmostVisible()記錄相應的被監聽元素並對ImageResourceContent物件設定LazyImagesDelegate。在ImageResourceContent::UpdateImage()中實際建立PlaceholderImage時,呼叫LazyImagesDelegate::CreateIntersectionObserver()方法,監聽該元素與視口的交叉狀態。

為了保證背景圖片也能實現延遲載入,在CSSImageValue::CacheImage()和CSSImageSeValue::CacheImage()方法中需要增加一個Element*引數(即需要透過IntersectionObserver監聽的元素),此引數傳遞自StyleResolverState::LoadPendingResources()方法。比如CSSImageValue::CacheImage()及StyleFetchImage修改如下:

Web基礎體驗改進四:lazy load

Web基礎體驗改進四:lazy load

當圖片進入可視區域後,透過LazyImagesDelegateImpl::LoadImageIfAlmostVisible()重新載入圖片資源,實際是透過呼叫ImageResource::ReloadIfLoFiOrPlaceholderImage()方法,其中的第二個引數為kReloadAlways。

Web基礎體驗改進四:lazy load

2. Frame的延遲載入

實現方案中,也和標籤一樣透過屬性值判斷。一個iframe需要滿足以下條件才可以進行延遲載入,

是一個第三方的iframe;

尺寸大於4X4;

沒有標記為“display:none”或“visibility:hidden”;

x或y座標沒有位於當前螢幕外(x和y座標不能為負值);

在HTMLFrameOwnerElement::LoadOrRedirectSubframe()中在執行FrameLoader::Load()執行實際的載入之前,判斷是否對此frame延遲載入,如果不延遲則直接執行FrameLoader::Load(),否則建立一個IntersectionObserver物件監聽此HTMLFrameOwnerElement。

Web基礎體驗改進四:lazy load

Web基礎體驗改進四:lazy load

當此element進入視口區域後呼叫LoadIfHiddenOrNearViewport()執行實際載入。

Web基礎體驗改進四:lazy load

3. root margin

我們可以在被監聽元素距離當前視口一定距離時便開始載入,以進一步改善使用者的使用體驗。在IntersectionObserver的建構函式中有一個引數root_margin,其表示參照物的擴充套件範圍。比如將此值設定為800,那麼在當前視口區域以下的image元素在距離視口800畫素時便開始進行載入。該值應當來源於大量的試驗,與當前載入型別(Image或者Frame)及當前的網路連結狀況等情況有關。