輪事件:滾輪
滾動(卷動)事件:滾輪、拖拽滾動條、鍵盤方向鍵
<script type="text/javascript">
//滾輪事件:滾輪
//卷動事件:滾輪、拖拽滾動條、鍵盤?鍵
//IE和Chrome
gunlun.onmousewheel = function(){
this.innerHTML += "IE和Chrome<br/>";
}
//Firefox
gunlun.addEventListener("DOMMouseScroll", function(){
this.innerHTML += "Firefox<br/>";
})
</script>
判斷滾輪方向
<script type="text/javascript">
//滾輪事件:滾輪
//卷動事件:滾輪、拖拽滾動條、鍵盤?鍵
//IE和Chrome
gunlun.onmousewheel = function(e){
var ev = e || window.event;
console.log(ev.wheelDelta);//判斷滾輪方向的
//上120
//下-120
this.innerHTML += "IE和Chrome<br/>";
}
//Firefox
gunlun.addEventListener("DOMMouseScroll",function(e){
var ev = e || window.event;
console.log(ev.detail);//滾輪方向
//上-3
//下3
this.innerHTML += "Firefox<br/>";
})
</script>
兼容性封裝
文主要想談談頁面優化之滾動優化。
主要內容包括了為何需要優化滾動事件,滾動與頁面渲染的關系,節流與防抖,pointer-events:none 優化滾動。因為本文涉及了很多很多基礎,可以對照上面的知識點,選擇性跳到相應地方閱讀。
滾動優化的由來
滾動優化其實也不僅僅指滾動(scroll 事件),還包括了例如 resize 這類會頻繁觸發的事件。簡單的看看:
var i = 0; window.addEventListener('scroll',function(){ console.log(i++); },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 頁面中元素的布局是相對的,因此一個元素的布局發生變化,會聯動地引發其他元素的布局發生變化。比如,<body> 元素的寬度的變化會影響其子元素的寬度,其子元素寬度的變化也會繼續對其孫子元素產生影響。因此對于瀏覽器來說,布局過程是經常發生的。
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,監聽用戶輸入等),之前我有JavaScript什么是防抖和節流?和JavaScript什么是防抖和節流(下部)?兩篇文章詳細的介紹了兩者的概念和自己實現的方法,這也是大多數教程推薦且常用的解決方法。
但是,采用它們也有一些問題,例如
1、如果列表頁有很多圖片,我們采用懶加載方式,如果配合防抖函數,當我們不停的滾動鼠標的時候(不暫停),就會發現圖片看不到,只有停下來過了delay ms之后才會被加載出來,這樣的體驗不是很友好
2、這時候如果我們采用節流函數就可以解決上面的問題,即使鼠標在滾動 handler 也可以以一定的頻率被觸發(譬如 250ms 觸發一次),在下滑過程中圖片不斷的被加載出來,而不是只有當我停止下滑時候,圖片才被加載出來。又或者下滑時候的數據的 ajax 請求加載也是同理。
使用 rAF(requestAnimationFrame)觸發滾動事件
上面介紹的抖動與節流實現的方式都是借助了定時器 setTimeout ,但是如果頁面只需要兼容高版本瀏覽器或應用在移動端,又或者頁面需要追求高精度的效果,那么可以使用瀏覽器的原生方法 rAF(requestAnimationFrame)。
requestAnimationFrame
window.requestAnimationFrame() 這個方法是用來在頁面重繪之前,通知瀏覽器調用一個指定的函數。這個方法接受一個函數為參,該函數會在重繪前調用。
rAF 常用于 web 動畫的制作,用于準確控制頁面的幀刷新渲染,讓動畫效果更加流暢,當然它的作用不僅僅局限于動畫制作,我們可以利用它的特性將它視為一個定時器。(當然它不是定時器)
通常來說,rAF 被調用的頻率是每秒 60 次,也就是 1000/60 ,觸發頻率大概是 16.7ms 。
rAF并不是一個定時器,我們通常遞歸調用rAFt以達到連續repaint的目的。rAF的本質是為我們提供了主動申請一幀的能力,以往的setInterval幀刷新都是瀏覽器根據你的操作來觸發。而使用rAF讓我們掌握了主動權。這就意味著使用rAF可以保證不丟幀,但是并不能保證幀率。
rAF 更多的是用于動畫制作替代 setInterval(func,1000/60) 更加精確的控制動畫及保證動畫的流暢性,確保動畫不會掉幀
(當執行復雜操作時,當它發現無法維持 60fps 的頻率時,它會把頻率降低到 30fps 來保持幀數的穩定。)
簡單而言,使用 requestAnimationFrame 來觸發滾動事件,相當于上面的:
throttle(func, xx, 1000/60) //xx 代表 xx ms內不會重復觸發事件 handler
簡單的示例如下:
上面簡單的使用 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 打開開發者工具面板,給 <body> 標簽添加上 pointer-events: none 樣式,然后在頁面上感受下效果,發現所有鼠標事件都被禁止了。
那么它有什么用呢?
pointer-events: none 可用來提高滾動時的幀頻。的確,當滾動時,鼠標懸停在某些元素上,則觸發其上的 hover 效果,然而這些影響通常不被用戶注意,并多半導致滾動出現問題。對 body 元素應用 pointer-events: none ,禁用了包括 hover 在內的鼠標事件,從而提高滾動性能。
.disable-hover { pointer-events: none; }
大概的做法就是在頁面滾動的時候, 給 <body> 添加上 .disable-hover 樣式,那么在滾動停止之前, 所有鼠標事件都將被禁止。當滾動結束之后,再移除該屬性。
上面說 pointer-events: none 可用來提高滾動時的幀頻 的這段話摘自 pointer-events-MDN
這就完了嗎?沒有,張鑫旭有一篇專門的文章,用來探討 pointer-events: none 是否真的能夠加速滾動性能,并提出了自己的質疑:
pointer-events:none提高頁面滾動時候的繪制性能?(如有興趣,可以自己去查看)
結論見仁見智,使用 pointer-events: none 的場合要依據業務本身來定奪,拒絕拿來主義,多去源頭看看,動手實踐一番再做定奪。
參考文章:
SS 是一種美麗且復雜的技術,我們每天在工作中都會用到。然而,包括我在內的許多開發者都忽略了它的一些重要方面。
這很明顯,因為在互聯網上很難找到關于 CSS 的新知識或高級內容。大多數內容創作者只寫一些熱門話題,比如新語言、框架和庫。
個人對 CSS 的了解僅限于讓它正常工作。而這特別令人遺憾,因為我們從未嘗試深入研究這個主題。認識到這一點后,做了一些研究,并列出了一些新發現的內容。
CSS 性能是一個非常重要的話題,因為它直接影響網站的效率。由于現代應用程序包含大量的 CSS 代碼,即使是微小的錯誤也可能導致顯著的性能下降。
首先,下面賂你展示一些令我驚訝的東西。只需看看下面的例子,試著找出可能的問題。
正如你所看到的,這個例子一點都不復雜。這段 CSS 代碼移除了標題中所有段落內鏈接的文本裝飾。這不是普通的 CSS 嗎?
但你知道使用這樣的選擇器會導致瀏覽器執行大量額外工作嗎?這個問題適用于我們代碼中所有類似的子選擇器。
這是因為瀏覽器在解析 CSS 時,從右向左讀取選擇器。有了這個新知識,我們將這個過程分解成幾個步驟:
通過使用更具體的選擇器,我們可以幫助瀏覽器避免所有這些額外的工作。我們可以將 .header__link 類應用于錨元素,并用它替換選擇器 #header p a。這樣,瀏覽器會更快地找到所需的元素。
所謂昂貴的 CSS 屬性,是指這些屬性在我們的應用程序中可能會耗費大量性能。但這并不意味著你完全不能使用它們。你只需要理解,如果元素使用了這些屬性并且經常渲染——它肯定會影響性能。
需要注意的一件棘手事情是,改變某些 CSS 屬性需要更新整個布局。幾何屬性如寬度、高度、頂部等可能導致整個樹的重新繪制。
還有一些屬性很難渲染。雖然這些屬性的清單很短,但你可以在這里了解更多關于它們的信息。
重繪(Repaint)和重排(Reflow)是在瀏覽器中渲染網頁過程中兩個重要的概念。當頁面加載或更新時,瀏覽器會通過一系列步驟將內容顯示在屏幕上,而重繪和重排在這個過程中起著重要作用。
為了更好地理解這個過程,我們可以將其分解為幾個精確的步驟:
當頁面首次加載時,如果不是空的,至少會執行一次重排和重繪。但它們也會在以下情況下繼續發生:
重排是計算網頁上元素布局的過程。它根據元素的內容和樣式來確定每個元素的大小和位置。
在現代應用程序中,特別是頁面上有成千上萬個元素和頻繁的布局更新時,它很快會變成一個資源消耗大的操作。即使是對文檔中單個元素的重排,也可能需要重排其父元素和隨后的任何其他元素。
由于它是一個用戶阻塞操作,了解如何改進重排以及各種文檔屬性對其持續時間的影響是非常有用的。典型的引發重排過程的情況通常有:
重繪是將像素繪制到屏幕上的簡單過程。在重排期間確定布局后,瀏覽器將每個元素繪制到屏幕上。
重繪通常比重排消耗的資源少,但它仍然會影響網頁的性能。
最典型的情況是當元素樣式的變化不影響其在屏幕上的大小或位置時。例如,當背景顏色改變時,瀏覽器只需用新樣式重新繪制(或重繪)節點。
以下是一些會觸發重繪的操作的例子:
JavaScript 和 CSS 是非常棒的技術,尤其是在一起使用時。每種技術都有其獨特的優勢,結合使用時可以創造出令人難以置信的效果。
然而,我可以自信地說,UI 控制中使用 CSS 越多,Web 應用程序就越容錯和可靠。
觀點很簡單:
現在讓我們考慮一些你可以輕松用 CSS 替代 JS 的情況。
以前,要實現平滑滾動,需要使用幾行 JavaScript 代碼。但現在,這個任務可以完全通過 CSS 的力量來解決。
現在你可以通過使用 CSS 屬性 scroll-behavior 來實現平滑滾動。
CSS 提供了兩個很棒的屬性:text-overflow 和 line-clamp。它們允許我們修剪文本,同時讓我們擺脫使用 JavaScript 代碼或其他復雜方法來執行此類任務。這兩個屬性雖然不是新的,但非常有用。
這個屬性控制文本在不適合一行的情況下如何顯示。這個屬性的一個完美例子是在卡片標題中使用 text-overflow: ellipsis,它會在被截斷的文本末尾顯示一個 Unicode 字符 …。
請記住,text-overflow: ellipsis 只有在與 white-space: nowrap 和 overflow: hidden 屬性配合使用時才有效。
line-clamp 屬性
line-clamp 屬性在需要處理多行文本時非常有用。上面示例中的卡片描述展示了它的效果。
這個屬性是 CSS Overflow Module Level 3 標準的一部分,雖然目前還處于草案階段,但已經有大約 95% 的瀏覽器支持這個屬性,但需要加上 -webkit- 前綴。
在使用它之前,重要的是要提到它不允許控制顯示字符的數量。但它仍然非常有用。
要使用 line-clamp 屬性,我們需要將其與 display: -webkit-box 和 -webkit-box-orient: vertical 結合使用。示例如下:
現代前端開發的世界正在迅速變化,我們不斷得到新的機會來幫助我們更快、更好地完成工作。嘗試使用 CSS 是非常有趣且有幫助的。如果在不使用 JS 的情況下可以實現相同的結果,請考慮使用 CSS。
*請認真填寫需求信息,我們會在24小時內與您取得聯系。