問題描述
最近在工作中遇到了一個(gè)很奇怪的問題盖喷,網(wǎng)站的頁面大部分按鈕和卡片都有一個(gè)向上移動(dòng)的過渡動(dòng)畫夯秃,當(dāng)鼠標(biāo)懸浮在這些元素上的時(shí)候愤惰,動(dòng)畫會(huì)觸發(fā)并在一定時(shí)間內(nèi)緩緩?fù)瓿砂椤⑼瑫r(shí)鼠標(biāo)樣式變成 pointer锨推,。
但是問題在于這個(gè)效果的實(shí)現(xiàn)并不理想公壤。鼠標(biāo)要是從左右兩邊和上邊移入按鈕或者卡片的話這個(gè)效果是看不出啥問題的换可,要是鼠標(biāo)是從下往上緩緩進(jìn)入,或者停留在上移的距離內(nèi)厦幅,這個(gè)特效會(huì)導(dǎo)致鼠標(biāo)和按鈕/卡片樣式不斷抖動(dòng)沾鳄,非常鬼畜。具體效果不便直接截圖給大家展示确憨,但是我在下面給出了解決方案后會(huì)給出一個(gè)模擬的效果 gif 和代碼(gif制作中)洞渔。
問題解決
雖然這個(gè)問題不在我的工作職責(zé)范圍內(nèi),但是作為一個(gè)“前端”工程師缚态,這個(gè)問題真的喚醒了我的強(qiáng)迫癥,不能忍暗塘觥玫芦!有沒有。
但是又因?yàn)楸救耸且粋€(gè)真實(shí)菜雞前端本辐,css方面急需惡補(bǔ)那種桥帆。医增。。剛遇到這個(gè)問題連怎么實(shí)現(xiàn)元素上移都不知道T.T老虫。所以一時(shí)間對(duì)這個(gè)問題實(shí)在想不出啥解決方法叶骨。然而在幾天之后,我在休息的時(shí)候解決了這個(gè)問題祈匙。
其實(shí)解決方法很簡單忽刽,其實(shí)就是原本代碼實(shí)現(xiàn)移動(dòng)效果使用的是css的top屬性結(jié)合position: relative;要解決這個(gè)問題只需要把實(shí)現(xiàn)方式更換成transform并使用相應(yīng)的transform函數(shù)即可夺欲,無需結(jié)合relative布局跪帝。
下面給出情景模擬的代碼,大家可以拷貝到一個(gè) html 文件里看看是啥效果些阅。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
</head>
<style>
body {
text-align: center;
}
#head {
height: 100px;
width: 100%;
}
#middle {
margin: 0 auto;
}
#top-button {
top: 0;
position: relative;
transition: .5s;
-moz-transition: .5s;
-webkit-transition: .5s;
-o-transition: .5s;
-ms-transition: .5s;
cursor: pointer;
}
#top-button:hover {
top: -8px;
}
#transform-button {
margin-left: 100px;
transition: .5s;
-moz-transition: .5s;
-webkit-transition: .5s;
-o-transition: .5s;
-ms-transition: .5s;
cursor: pointer;
}
#transform-button:hover {
transform: translateY(-20%);
}
</style>
<body>
<div id="head"></div>
<div id="middle">
<button id="top-button">top 實(shí)現(xiàn)</button>
<button id="transform-button">transform 實(shí)現(xiàn)</button>
</div>
</body>
</html>
問題深究
為啥使用不同的 css 屬性會(huì)導(dǎo)致如此巨大的效果差異呢伞剑?
我突然意識(shí)到 css 其實(shí)是在網(wǎng)頁中其實(shí)屬于較高層次的抽象,出現(xiàn)問題后難以糾錯(cuò)主要是在于使用者對(duì) css 的了解太過于淺薄市埋,往往知其然不知其所以然黎泣,僅僅停留在使用的層次。因此出現(xiàn)問題和解決問題的過程宛如魔法一般缤谎。
所以這次我們就借著遇到的這個(gè)問題抒倚,來探討一下css渲染web畫面的一些底層原理:
瀏覽器如何繪制 dom 元素?
注意我們討論的是繪制的步驟弓千,在繪制之前衡便,還會(huì)經(jīng)過css計(jì)算、布局等步驟洋访。
- 獲取 DOM 并將其分割為多個(gè)層(layer)
- 將每個(gè)層獨(dú)立地繪制進(jìn)位圖(bitmap)中
- 將層作為紋理(texture)上傳至 GPU镣陕, GPU 會(huì)復(fù)合(composite)多個(gè)層來生成最終的屏幕圖像,然后將其顯示在屏幕上姻政。
具體的細(xì)節(jié)有可能因?yàn)?webkit 的不同而產(chǎn)生區(qū)別呆抑。
top 和 transform 的根本區(qū)別?
top 是一個(gè)布局屬性汁展,而 transform 是一個(gè)觸發(fā)合成層的屬性鹊碍,它并不會(huì)在布局階段被使用。
所以現(xiàn)在明白了食绿,為啥使用top實(shí)現(xiàn)的動(dòng)畫要配合relative實(shí)現(xiàn)侈咕?因?yàn)樗且粋€(gè)布局屬性,一旦它處于文檔流中器紧,還能夠?qū)ζ渌卦斐捎绊懸@樣會(huì)造成整個(gè)頁面回流(reflow)和重繪。
所以使用top的動(dòng)畫功能的時(shí)候需要配合 relative铲汪、absolute 這些屬性熊尉。
但是無論如何罐柳,使用top做動(dòng)畫特效實(shí)際上都會(huì)觸發(fā)布局計(jì)算和重繪過程,生成新的幀畫面后狰住,交給 GPU 去合成圖像张吉,這樣實(shí)際上造成了額外的性能開銷和不必要的動(dòng)作;而transform只是會(huì)額外地生成一個(gè)合成層催植,動(dòng)畫元素會(huì)在一個(gè)獨(dú)立的層次中動(dòng)畫肮蛹,并直接交由 GPU 處理,沒有經(jīng)過重繪來生成新的幀查邢。
還有什么屬性會(huì)觸發(fā)合成層蔗崎?
- 3D 或透視變換 CSS 屬性
- 使用加速視頻解碼的 <video> 元素
- 擁有 3D (WebGL) 上下文或加速的 2D 上下文的 <canvas> 元素
- 復(fù)合插件(如 Flash)
- 進(jìn)行 opacity/transform 動(dòng)畫的元素
- 擁有加速 CSS filters 的元素
- 元素有一個(gè)包含復(fù)合層的后代節(jié)點(diǎn)(換句話說,就是一個(gè)元素?fù)碛幸粋€(gè)子元素扰藕,該子元素在自己的層里)
- 元素有一個(gè) z-index 較低且包含一個(gè)復(fù)合層的兄弟元素(換句話說就是該元素在復(fù)合層上面渲染)
總結(jié)一下
- 對(duì)布局屬性進(jìn)行動(dòng)畫缓苛,瀏覽器需要為每一幀進(jìn)行重繪并上傳到 GPU 處理。
- 對(duì)合成屬性進(jìn)行動(dòng)畫邓深,瀏覽器會(huì)為元素創(chuàng)建一個(gè)獨(dú)立的復(fù)合層未桥,該層不會(huì)被重繪,直接交由 GPU 計(jì)算圖像芥备。