成人免费xxxxx在线视频软件_久久精品久久久_亚洲国产精品久久久_天天色天天色_亚洲人成一区_欧美一级欧美三级在线观看

高性能滾動scroll及頁面渲染優化

開發 前端
本文主要想談談頁面優化之滾動優化。主要內容包括了為何需要優化滾動事件,滾動與頁面渲染的關系,節流與防抖,pointer-events:none 優化滾動。

[[189530]]

最近在研究頁面渲染及web動畫的性能問題,以及拜讀《CSS SECRET》(CSS揭秘)這本大作。

本文主要想談談頁面優化之滾動優化。

主要內容包括了為何需要優化滾動事件,滾動與頁面渲染的關系,節流與防抖,pointer-events:none 優化滾動。因為本文涉及了很多很多基礎,可以對照上面的知識點,選擇性跳到相應地方閱讀。

滾動優化的由來

滾動優化其實也不僅僅指滾動(scroll 事件),還包括了例如 resize 這類會頻繁觸發的事件。簡單的看看:

  1. var i = 0; 
  2.  
  3. window.addEventListener('scroll',function(){ 
  4.  
  5.     console.log(i++); 
  6.  
  7. },false);  

輸出如下:   

 

在綁定 scroll 、resize 這類事件時,當它發生時,它被觸發的頻次非常高,間隔很近。如果事件中涉及到大量的位置計算、DOM 操作、元素重繪等工作且這些工作無法在下一個 scroll 事件觸發前完成,就會造成瀏覽器掉幀。加之用戶鼠標滾動往往是連續的,就會持續觸發 scroll 事件導致掉幀擴大、瀏覽器 CPU 使用率增加、用戶體驗受到影響。

在滾動事件中綁定回調應用場景也非常多,在圖片的懶加載、下滑自動加載數據、側邊浮動導航欄等中有著廣泛的應用。

當用戶瀏覽網頁時,擁有平滑滾動經常是被忽視但卻是用戶體驗中至關重要的部分。當滾動表現正常時,用戶就會感覺應用十分流暢,令人愉悅,反之,笨重不自然卡頓的滾動,則會給用戶帶來極大不舒爽的感覺。

滾動與頁面渲染的關系

為什么滾動事件需要去優化?因為它影響了性能。那它影響了什么性能呢?額……這個就要從頁面性能問題由什么決定說起。

我覺得搞技術一定要追本溯源,不要看到別人一篇文章說滾動事件會導致卡頓并說了一堆解決方案優化技巧就如獲至寶奉為圭臬,我們需要的不是拿來主義而是批判主義,多去源頭看看。

從問題出發,一步一步尋找到最后,就很容易找到問題的癥結所在,只有這樣得出的解決方法才容易記住。

說教了一堆廢話,不喜歡的直接忽略哈,回到正題,要找到優化的入口就要知道問題出在哪里,對于頁面優化而言,那么我們就要知道頁面的渲染原理:

瀏覽器渲染原理我在我上一篇文章里也要詳細的講到,不過更多的是從動畫渲染的角度去講的:《【Web動畫】CSS3 3D 行星運轉 && 瀏覽器渲染原理》 。

想了想,還是再簡單的描述下,我發現每次 review 這些知識點都有新的收獲,這次換一張圖,以 chrome 為例子,一個 Web 頁面的展示,簡單來說可以認為經歷了以下下幾個步驟:

 

  • JavaScript:一般來說,我們會使用 JavaScript 來實現一些視覺變化的效果。比如做一個動畫或者往頁面里添加一些 DOM 元素等。
  • Style:計算樣式,這個過程是根據 CSS 選擇器,對每個 DOM 元素匹配對應的 CSS 樣式。這一步結束之后,就確定了每個 DOM 元素上該應用什么 CSS 樣式規則。
  • Layout:布局,上一步確定了每個 DOM 元素的樣式規則,這一步就是具體計算每個 DOM 元素最終在屏幕上顯示的大小和位置。web 頁面中元素的布局是相對的,因此一個元素的布局發生變化,會聯動地引發其他元素的布局發生變化。比如, 元素的寬度的變化會影響其子元素的寬度,其子元素寬度的變化也會繼續對其孫子元素產生影響。因此對于瀏覽器來說,布局過程是經常發生的。
  • Paint:繪制,本質上就是填充像素的過程。包括繪制文字、顏色、圖像、邊框和陰影等,也就是一個 DOM 元素所有的可視效果。一般來說,這個繪制過程是在多個層上完成的。
  • Composite:渲染層合并,由上一步可知,對頁面中 DOM 元素的繪制是在多個層上進行的。在每個層上完成繪制過程之后,瀏覽器會將所有層按照合理的順序合并成一個圖層,然后顯示在屏幕上。對于有位置重疊的元素的頁面,這個過程尤其重要,因為一旦圖層的合并順序出錯,將會導致元素顯示異常。

這里又涉及了層(GraphicsLayer)的概念,GraphicsLayer 層是作為紋理(texture)上傳給 GPU 的,現在經常能看到說 GPU 硬件加速,就和所謂的層的概念密切相關。但是和本文的滾動優化相關性不大,有興趣深入了解的可以自行 google 更多。

簡單來說,網頁生成的時候,至少會渲染(Layout+Paint)一次。用戶訪問的過程中,還會不斷重新的重排(reflow)和重繪(repaint)。

其中,用戶 scroll 和 resize 行為(即是滑動頁面和改變窗口大小)會導致頁面不斷的重新渲染。

當你滾動頁面時,瀏覽器可能會需要繪制這些層(有時也被稱為合成層)里的一些像素。通過元素分組,當某個層的內容改變時,我們只需要更新該層的結構,并僅僅重繪和柵格化渲染層結構里變化的那一部分,而無需完全重繪。顯然,如果當你滾動時,像視差網站(戳我看看)這樣有東西在移動時,有可能在多層導致大面積的內容調整,這會導致大量的繪制工作。

防抖(Debouncing)和節流(Throttling)

scroll 事件本身會觸發頁面的重新渲染,同時 scroll 事件的 handler 又會被高頻度的觸發, 因此事件的 handler 內部不應該有復雜操作,例如 DOM 操作就不應該放在事件處理中。

針對此類高頻度觸發事件問題(例如頁面 scroll ,屏幕 resize,監聽用戶輸入等),下面介紹兩種常用的解決方法,防抖和節流。

防抖(Debouncing)

防抖技術即是可以把多個順序地調用合并成一次,也就是在一定時間內,規定事件被觸發的次數。

通俗一點來說,看看下面這個簡化的例子:

  1. // 簡單的防抖動函數 
  2.  
  3. function debounce(func, wait, immediate) { 
  4.  
  5.     // 定時器變量 
  6.  
  7.     var timeout; 
  8.  
  9.     return function() { 
  10.  
  11.         // 每次觸發 scroll handler 時先清除定時器 
  12.  
  13.         clearTimeout(timeout); 
  14.  
  15.         // 指定 xx ms 后觸發真正想進行的操作 handler 
  16.  
  17.         timeout = setTimeout(func, wait); 
  18.  
  19.     }; 
  20.  
  21. }; 
  22.  
  23.   
  24.  
  25. // 實際想綁定在 scroll 事件上的 handler 
  26.  
  27. function realFunc(){ 
  28.  
  29.     console.log("Success"); 
  30.  
  31.  
  32.   
  33.  
  34. // 采用了防抖動 
  35.  
  36. window.addEventListener('scroll',debounce(realFunc,500)); 
  37.  
  38. // 沒采用防抖動 
  39.  
  40. window.addEventListener('scroll',realFunc);  

上面簡單的防抖的例子可以拿到瀏覽器下試一下,大概功能就是如果 500ms 內沒有連續觸發兩次 scroll 事件,那么才會觸發我們真正想在 scroll 事件中觸發的函數。

上面的示例可以更好的封裝一下:

  1. // 防抖動函數 
  2.  
  3. function debounce(func, wait, immediate) { 
  4.  
  5.     var timeout; 
  6.  
  7.     return function() { 
  8.  
  9.         var context = this, args = arguments; 
  10.  
  11.         var later = function() { 
  12.  
  13.             timeout = null
  14.  
  15.             if (!immediate) func.apply(context, args); 
  16.  
  17.         }; 
  18.  
  19.         var callNow = immediate & !timeout; 
  20.  
  21.         clearTimeout(timeout); 
  22.  
  23.         timeout = setTimeout(later, wait); 
  24.  
  25.         if (callNow) func.apply(context, args); 
  26.  
  27.     }; 
  28.  
  29. }; 
  30.  
  31.   
  32.  
  33. var myEfficientFn = debounce(function() { 
  34.  
  35.     // 滾動中的真正的操作 
  36.  
  37. }, 250); 
  38.  
  39.   
  40.  
  41. // 綁定監聽 
  42.  
  43. window.addEventListener('resize', myEfficientFn);  

節流(Throttling)

防抖函數確實不錯,但是也存在問題,譬如圖片的懶加載,我希望在下滑過程中圖片不斷的被加載出來,而不是只有當我停止下滑時候,圖片才被加載出來。又或者下滑時候的數據的 ajax 請求加載也是同理。

這個時候,我們希望即使頁面在不斷被滾動,但是滾動 handler 也可以以一定的頻率被觸發(譬如 250ms 觸發一次),這類場景,就要用到另一種技巧,稱為節流函數(throttling)。

節流函數,只允許一個函數在 X 毫秒內執行一次。

與防抖相比,節流函數最主要的不同在于它保證在 X 毫秒內至少執行一次我們希望觸發的事件 handler。

與防抖相比,節流函數多了一個 mustRun 屬性,代表 mustRun 毫秒內,必然會觸發一次 handler ,同樣是利用定時器,看看簡單的示例:

  1. // 簡單的節流函數 
  2.  
  3. function throttle(func, wait, mustRun) { 
  4.  
  5.     var timeout, 
  6.  
  7.         startTime = new Date(); 
  8.  
  9.  
  10.     return function() { 
  11.  
  12.         var context = this, 
  13.  
  14.             args = arguments, 
  15.  
  16.             curTime = new Date(); 
  17.  
  18.  
  19.         clearTimeout(timeout); 
  20.  
  21.         // 如果達到了規定的觸發時間間隔,觸發 handler 
  22.  
  23.         if(curTime - startTime >= mustRun){ 
  24.  
  25.             func.apply(context,args); 
  26.  
  27.             startTime = curTime; 
  28.  
  29.         // 沒達到觸發間隔,重新設定定時器 
  30.  
  31.         }else
  32.  
  33.             timeout = setTimeout(func, wait); 
  34.  
  35.         } 
  36.  
  37.     }; 
  38.  
  39. }; 
  40.  
  41. // 實際想綁定在 scroll 事件上的 handler 
  42.  
  43. function realFunc(){ 
  44.  
  45.     console.log("Success"); 
  46.  
  47.  
  48. // 采用了節流函數 
  49.  
  50. window.addEventListener('scroll',throttle(realFunc,500,1000));  

上面簡單的節流函數的例子可以拿到瀏覽器下試一下,大概功能就是如果在一段時間內 scroll 觸發的間隔一直短于 500ms ,那么能保證事件我們希望調用的 handler 至少在 1000ms 內會觸發一次。

使用 rAF(requestAnimationFrame)觸發滾動事件

上面介紹的抖動與節流實現的方式都是借助了定時器 setTimeout ,但是如果頁面只需要兼容高版本瀏覽器或應用在移動端,又或者頁面需要追求高精度的效果,那么可以使用瀏覽器的原生方法 rAF(requestAnimationFrame)。

requestAnimationFrame

window.requestAnimationFrame() 這個方法是用來在頁面重繪之前,通知瀏覽器調用一個指定的函數。這個方法接受一個函數為參,該函數會在重繪前調用。

rAF 常用于 web 動畫的制作,用于準確控制頁面的幀刷新渲染,讓動畫效果更加流暢,當然它的作用不僅僅局限于動畫制作,我們可以利用它的特性將它視為一個定時器。(當然它不是定時器)

通常來說,rAF 被調用的頻率是每秒 60 次,也就是 1000/60 ,觸發頻率大概是 16.7ms 。(當執行復雜操作時,當它發現無法維持 60fps 的頻率時,它會把頻率降低到 30fps 來保持幀數的穩定。)

簡單而言,使用 requestAnimationFrame 來觸發滾動事件,相當于上面的:

  1. throttle(func, xx, 1000/60) //xx 代表 xx ms內不會重復觸發事件 handler 

簡單的示例如下:

  1. var ticking = false; // rAF 觸發鎖 
  2.  
  3.  
  4. function onScroll(){ 
  5.  
  6.   if(!ticking) { 
  7.  
  8.     requestAnimationFrame(realFunc); 
  9.  
  10.     ticking = true
  11.  
  12.   } 
  13.  
  14.  
  15.  
  16. function realFunc(){ 
  17.  
  18.     // do something... 
  19.  
  20.     console.log("Success"); 
  21.  
  22.     ticking = false
  23.  
  24.  
  25. // 滾動事件監聽 
  26.  
  27. window.addEventListener('scroll', onScroll, false);  

上面簡單的使用 rAF 的例子可以拿到瀏覽器下試一下,大概功能就是在滾動的過程中,保持以 16.7ms 的頻率觸發事件 handler。

使用 requestAnimationFrame 優缺點并存,首先我們不得不考慮它的兼容問題,其次因為它只能實現以 16.7ms 的頻率來觸發,代表它的可調節性十分差。但是相比 throttle(func, xx, 16.7) ,用于更復雜的場景時,rAF 可能效果更佳,性能更好。

總結一下

  • 防抖動:防抖技術即是可以把多個順序地調用合并成一次,也就是在一定時間內,規定事件被觸發的次數。
  • 節流函數:只允許一個函數在 X 毫秒內執行一次,只有當上一次函數執行后過了你規定的時間間隔,才能進行下一次該函數的調用。
  • rAF:16.7ms 觸發一次 handler,降低了可控性,但是提升了性能和精確度。

簡化 scroll 內的操作

上面介紹的方法都是如何去優化 scroll 事件的觸發,避免 scroll 事件過度消耗資源的。

但是從本質上而言,我們應該盡量去精簡 scroll 事件的 handler ,將一些變量的初始化、不依賴于滾動位置變化的計算等都應當在 scroll 事件外提前就緒。

建議如下:

避免在scroll 事件中修改樣式屬性 / 將樣式操作從 scroll 事件中剝離

 

輸入事件處理函數,比如 scroll / touch 事件的處理,都會在 requestAnimationFrame 之前被調用執行。

因此,如果你在 scroll 事件的處理函數中做了修改樣式屬性的操作,那么這些操作會被瀏覽器暫存起來。然后在調用 requestAnimationFrame 的時候,如果你在一開始做了讀取樣式屬性的操作,那么這將會導致觸發瀏覽器的強制同步布局。

滑動過程中嘗試使用 pointer-events: none 禁止鼠標事件

大部分人可能都不認識這個屬性,嗯,那么它是干什么用的呢?

pointer-events 是一個 CSS 屬性,可以有多個不同的值,屬性的一部分值僅僅與 SVG 有關聯,這里我們只關注 pointer-events: none 的情況,大概的意思就是禁止鼠標行為,應用了該屬性后,譬如鼠標點擊,hover 等功能都將失效,即是元素不會成為鼠標事件的 target。

可以就近 F12 打開開發者工具面板,給 標簽添加上 pointer-events: none 樣式,然后在頁面上感受下效果,發現所有鼠標事件都被禁止了。

那么它有什么用呢?

pointer-events: none 可用來提高滾動時的幀頻。的確,當滾動時,鼠標懸停在某些元素上,則觸發其上的 hover 效果,然而這些影響通常不被用戶注意,并多半導致滾動出現問題。對 body 元素應用 pointer-events: none ,禁用了包括 hover 在內的鼠標事件,從而提高滾動性能。

  1. .disable-hover { 
  2.  
  3. pointer-events: none; 
  4.  
  5.  

大概的做法就是在頁面滾動的時候, 給 添加上 .disable-hover 樣式,那么在滾動停止之前, 所有鼠標事件都將被禁止。當滾動結束之后,再移除該屬性。

可以查看這個 demo (https://dl.dropboxusercontent.com/u/2272348/codez/expensivescroll/demo.html)頁面。

上面說 pointer-events: none 可用來提高滾動時的幀頻 的這段話摘自 pointer-events-MDN ,還專門有文章講解過這個技術:

使用pointer-events:none實現60fps滾動 。

這就完了嗎?沒有,張鑫旭有一篇專門的文章,用來探討 pointer-events: none 是否真的能夠加速滾動性能,并提出了自己的質疑:

pointer-events:none提高頁面滾動時候的繪制性能?

結論見仁見智,使用 pointer-events: none 的場合要依據業務本身來定奪,拒絕拿來主義,多去源頭看看,動手實踐一番再做定奪。

其他參考文獻(都是好文章,值得一讀):

  • 實例解析防抖動(Debouncing)和節流閥(Throttling)
  • 無線性能優化:Composite
  • Javascript高性能動畫與頁面渲染
  • Google Developers–渲染性能
  • Web高性能動畫 

 【編輯推薦】

責任編輯:龐桂玉 來源: 前端大全
相關推薦

2019-03-01 11:03:22

Lustre高性能計算

2020-05-27 09:41:10

前端性能邊緣計算

2021-08-13 09:06:52

Go高性能優化

2023-03-22 18:31:10

Android頁面優化

2023-11-18 19:46:07

GPU架構

2022-12-12 09:01:13

2023-04-10 11:18:38

前端性能優化

2015-09-16 13:54:30

Android性能優化渲染

2016-12-11 10:08:31

高性能組網能力

2023-08-29 15:10:04

持續性能優化開發

2009-01-05 10:00:11

JSP優化Servlet性能優化

2014-03-19 14:34:06

JQuery高性能

2018-03-30 18:17:10

MySQLLinux

2009-09-08 09:45:23

App Engine性

2022-06-06 22:36:55

渲染性能CSS

2019-03-14 15:38:19

ReactJavascript前端

2023-11-01 11:59:13

2009-06-29 18:22:43

TomcatJSP頁面

2019-05-21 09:40:47

Elasticsear高性能 API

2018-01-19 14:39:53

瀏覽器頁面優化
點贊
收藏

51CTO技術棧公眾號

主站蜘蛛池模板: 国产一区二区av | 毛片久久久 | 国产一区二区在线免费观看 | 最新国产在线 | 国产成人亚洲精品 | 国产高清一区二区 | 一级在线免费观看 | 精品一区二区电影 | 久久久久亚洲 | 国产在线观看不卡一区二区三区 | 久久久久久99 | 久久国产成人午夜av影院武则天 | 精品亚洲一区二区 | 久久久久se | 成人a网 | 一区二区三区免费网站 | 国产xxxx在线 | 天天玩天天操天天干 | 一区二区三区视频播放 | 国产黄色大片在线观看 | 91在线视频网址 | 老牛影视av一区二区在线观看 | 久久精品 | 国产日韩欧美 | 午夜成人在线视频 | 日韩欧美福利视频 | 色婷婷综合久久久久中文一区二区 | 国产精品毛片一区二区在线看 | 最新中文在线视频 | 久久久妇女国产精品影视 | 亚洲欧美精品国产一级在线 | 一区二区国产精品 | 成人黄色电影免费 | 亚洲精品久久久蜜桃 | 激情国产视频 | 超碰在线播 | 成人av播放 | 久久精品无码一区二区三区 | 国产精品女人久久久 | 日韩精品在线一区 | 亚洲美女在线一区 |