在 CSS3 出現之前,簡單的互動都需要使用 JS 才能完成,如今 CSS3 增加了 transform
,transition
, animation
三大互動屬性,為 CSS 的單調性增加了很多趣味,CSS 也可以實現比較複雜的動畫了。
變換對應的 CSS 屬性為 transform
,變換可分為 2D 變換和 3D 變換,可以由 transform-style
來指定。 transform-style
需要宣告在父節點中,即需要發生變化的節點的父節點。
flat:2D 變換(所有變換效果在平面上呈現)
preserve-3d:3D 變換(所有變換效果在空間上呈現)
不同的變換對應不同的變換函數,在不同的變換空間使用對應的變換函數即可,接下來簡單介紹一下這些變換函數。
1)translate 位移
translate(x,y):2D位移
translate3d(x,y,z):3D位移
translateX(x):X軸位移,等同於 translate(x,0)
或 translate3d(x,0,0)
translateY(y):Y軸位移,等同於 translate(0,y)
或 translate3d(0,y,0)
translateZ(z):Z軸位移,等同於 translate3d(0,0,z)
描述
Length
長度,可用任何長度單位,允許負值0
2)scale 縮放
scale(x,y):2D縮放
scale3d(x,y,z):3D縮放
scaleX(x):X軸縮放,等同於 scale(x,1)
或 scale3d(x,1,1)
scaleY(y):Y軸縮放,等同於 scale(1,y)
或 scale3d(1,y,1)
scaleZ(z):Z軸縮放,等同於 scale3d(1,1,z)
描述
Number
數值或 Percentage
百分比,允許負值1
或 100%
0<(x,y,z)<1
沿X軸縮小/沿Y軸縮小/沿Z軸變厚, (x,y,z)>1
沿X軸放大/沿Y軸放大/沿Z軸變薄1<(x,y,z)<0
翻轉沿X軸縮小/沿Y軸縮小/沿Z軸變厚, (x,y,z)<-1
翻轉沿X軸放大/沿Y軸放大/沿Z軸變薄3)skew 扭曲
skew(x,y):2D扭曲
skewX(x):X軸扭曲,等同於 skew(x,0)
skewY(y):Y軸扭曲,等同於 skew(0,y)
描述
Angle
角度或 Turn
周0
4)rotate 旋轉
rotate():2D旋轉
rotate3d(x,y,z,a):3D旋轉, [x,y,z]
是一個向量,數值都是 0~1
rotateX(a):X軸旋轉,等同於 rotate(1,0,0,a)
,正值時沿X軸向上逆時針旋轉,負值時沿X軸向下順時針旋轉
rotateY(a):3D Y軸旋轉,等同於 rotate(0,1,0,a)
,正值時沿Y軸向右逆時針旋轉,負值時沿Y軸向左順時針旋轉
rotateZ(a):3D Z軸旋轉,等同於 rotate(0,0,1,a)
,正值時沿Z軸順時針旋轉,負值時沿Z軸逆時針旋轉
描述
Angle
角度或 Turn
周5)視距效果 perspective
transform: perspective()
可以宣告視距效果,除此之外還有一個 perspective
屬性也可以用來宣告視距效果。
perspective
和 transform:perspective()
都能宣告視距,那為何要存在兩種宣告方式呢?
perspective
與 transform:perspective()
的作用相同
perspective
在 舞臺節點
(變換節點的父節點)上使用, transform:perspective()
在 當前變換節點
上使用,也可與其他變換函數一起使用
視距效果在 3D 動畫中記得要宣告,否則有些 3D 變換效果可能無法得到更好的展現。
值越小,使用者與空間Z軸距離越近,視覺效果越強
值越大,使用者與空間Z軸距離越遠,視覺效果越弱
1)❤️
通過純 CSS 也可以實現一個心形 ❤️。
使用單個 div 元素結合 ::before
和 ::after
兩個偽元素通過錯位疊加的方式就可以輕鬆實現,搞起來。
步驟:
宣告 <div>
的尺寸為一個 正方形
並以中心順時針旋轉 45deg
宣告兩個偽元素繼承 <div>
的尺寸並實行絕對定位
宣告兩個偽元素的圓角率為 100%
並平移到相應位置
2)0.5px 邊框
在行動端由於螢幕的解析度較高,因此 1px 邊框看起來會有點粗,那麼可以直接宣告 0.5px 的邊框嗎?可以,但是即便宣告成功了,有些瀏覽器還是會按照 1px 來渲染。為了實現 0.5px 的邊框,我們可以利用 CSS 中的 transform 來實現。
步驟:
宣告一個偽元素,令其寬高為 200%,border 寬度為 1px
通過 transform: scale(.5)
將該偽元素縮小為原來的 0.5 倍。
過渡的出現讓狀態間的切換更加絲滑,先簡單介紹一下與過渡相關的屬性,相關的動畫實踐稍候奉上。
1)transition-property:屬性
all
:全部屬性過渡( 預設
)
none
:無屬性過渡
String
:某個屬性過渡
2)transition-duration:時間
Time
:秒或毫秒(預設 0
)3)transition-timing-function:緩動函數
ease
:逐漸變慢,等同於 cubic-bezier(.25,.1,.25,1)
( 預設
)
linear
:勻速,等同於 cubic-bezier(0,0,1,1)
ease-in
:加速,等同於 cubic-bezier(.42,0,1,1)
ease-out
:減速,等同於 cubic-bezier(0,0,.58,1)
ease-in-out
:先加速後減速,等同於 cubic-bezier(.42,0,.58,1)
cubic-bezier
:貝塞爾曲線, (x1,y1,x2,y2)
四個值指定於曲線上的點 P1
和 P2
,所有值需在 [0,1]
區域內
4)transition-delay:時延
Time
:秒或毫秒(預設 0
)由於 duration
和 delay
的取值都是時間,所以可能會發生混淆。
duration
和 delay
作用於所有節點,包括自身的 ::before
和 ::after
transition
中出現兩個時間值時,第一個解析為 duration
,第二個解析為 delay
transition
中出現一個時間值時,解析為 duration
變換屬性中一個比較重要的屬性是 transition-timing-function
,它決定了過渡時間內速度是如何變化的。它的值實際上是一個貝塞爾曲線,推薦一個設定貝塞爾曲線的網站,可以根據需要設計出符合需求的貝塞爾曲線。
1)IOS 設定頁面中的開關按鈕
下面這個開關在 IOS 手機的設定面板中太常見了,開關的動畫效果利用 transition
這個屬性也可以很輕鬆地實現,而且這裡利用上面講到的設定貝塞爾曲線的網站偵錯出了一種貝塞爾曲線使得開關按鈕被開啟的過程有一個剎車的效果。
CSS 可以通過設定多個點精確地控制一個或一組動畫,用來實現複雜的動畫效果。動畫由多個點組成,每個點擁有獨立的狀態,這些狀態通過瀏覽器處理成過渡效果,點與點之間的過渡效果串聯起來就是一個完整的動畫。
1)animation-name:名稱
none
:無動畫( 預設
)
String
:動畫名稱
2)animation-duration:時間
Time
:秒或毫秒(預設 0
)3)animation-timing-function:緩動函數
ease
:逐漸變慢,等同於 cubic-bezier(.25,.1,.25,1)
( 預設
)
linear
:勻速,等同於 cubic-bezier(0,0,1,1)
ease-in
:加速,等同於 cubic-bezier(.42,0,1,1)
ease-out
:減速,等同於 cubic-bezier(0,0,.58,1)
ease-in-out
:先加速後減速,等同於 cubic-bezier(.42,0,.58,1)
cubic-bezier
:貝塞爾曲線, (x1,y1,x2,y2)
四個值指定於曲線上的點 P1
和 P2
,所有值需在 [0,1]
區域內
steps([,[start|end]]?)
:把動畫平均劃分成 n等分
,直到平均走完該動畫
step-start
:等同於 steps(1,start)
,把動畫分成一步,動畫執行時以左側端點 0%
為開始
step-end
:等同於 steps(1,end)
,把動畫分成一步,動畫執行時以右側端點 100%
為開始
4)animation-delay:時延
Time
:秒或毫秒(預設 0
)5)animation-iteration-count:播放次數
Number
:數值(預設 1
)
infinite
:無限次
6)animation-direction:輪流反向播放(播放次數為一次則該屬性無效果)
normal
:正常播放( 預設
)
alternate
:輪流反向播放,奇數次數正常播放,偶數次數反向播放
7)animation-play-state:播放狀態
running
:正在播放( 預設
)
paused
:暫停播放
8)animation-fill-mode:播放前後其效果是否可見
none
:不改變預設行為( 預設
)
backwards
:在時延所指定時間內或在動畫開始前應用開始屬性( 在第一個關鍵幀中定義
)
forwards
:在動畫結束後保持最後一個屬性( 在最後一個關鍵幀中定義
)
both
:向前和向後填充模式都被應用
CSS 中動畫分為兩種,關鍵幀動畫和逐幀動畫。關鍵幀動畫是將人為定義好的每一幀的狀態串聯成一個動畫,需要通過 animation
和 @keyframes
宣告。逐幀動畫的宣告較為簡單,使用一張逐幀長圖然後配合 animation-timing-function: steps()
來完成。
逐幀動畫的宣告步驟一般如下:
準備一張 逐幀長圖
,該影象包含動畫效果的每一幀且每幀寬高必須一致
在 steps()
裡宣告逐幀長圖及其展示方式
在指定節點中宣告 animation
呼叫動畫
關鍵幀動畫的宣告步驟一般如下:
在 @keyframes
裡宣告動畫名稱和動畫每個關鍵幀的狀態
在指定節點中宣告 animation
呼叫動畫
關鍵幀動畫宣告範例:
@keyframes animation-name {
from {}
to {}
}
/* 或 */
@keyframes animation-name {
p1 {}
p2 {}
p3 {}
}
關鍵幀的取值必須是 from
、 to
或 Percentage
。 from
可用 0%
代替, to
可用 100%
代替,若開始或結束的關鍵幀無對應的狀態,可不用宣告 from
或 to
。 0%
的 %
不能省略,否則關鍵幀解析會失敗。通過 animation-fill-mode
屬性設定動畫結束後的樣式。
1)自動打字器
很多線上編輯器網站都有一些自動打字的效果,例如CodePen。很多同學都以為是JS實現的效果,其實檢視 Chrome Devtools
發現是純CSS實現的。觀察多幾次自動打字器,可發現其存在以下特點。
字型都是等寬字型,等寬字型可保證每次打字時遊標的移動距離都是一致的
打字器的寬度由最初的 0px
逐漸增加內容後變成最終固定字數的寬度,寬度以等寬字型的個數為準
遊標隨著每打一個字就閃爍一次,打字速度均勻,打字完畢再次重複打字
整個打字過程存在兩個動畫,一個是打字器自增寬度,一個是遊標閃爍
整個打字過程一閃一閃地完成,根據其斷斷續續的特點可判斷該動畫為逐幀動畫
這裡用 CSS 簡單實現了一個自動打字器,線上原始碼和演示。
先簡單介紹一下這個動畫的一些背景:
使用者點選中間的抽獎按鈕後會展示抽獎動畫,然後展示將要前進的步數,小人會在跑道上跑動到相應的位置,到達此次終點後,小人展示停止動畫並且顯示中獎結果。
使用者還可以通過長按中獎按鈕觸發顯示五連抽和十連抽,當點選五連抽時,首先會展示抽獎動畫,然後展示將要前進的步數,小人會在跑道上瞬間移動到相應的位置,然後展示停止的動畫,緊接著展示中獎動畫。十連抽的過程和五連抽相同。
整體流程
大體思路是將各個功能封裝為類,並且控制每個類實現的功能較小,這樣可以更大程度地實現類的複用。像下面介紹的 Animate CSS
這個動畫庫也是封裝了很多類,想要什麼效果直接參照即可,這裡也是同理。並在此基礎上定義了一些函數對跑動等功能進行了封裝,整體流程如下圖所示。
結合上圖對太空艙動畫中的一些點做一些簡單的說明。
跑動的實現
通過關鍵幀動畫,將各個方向相鄰兩格之間的跑動封裝為一個類。每一幀代表跑動的姿態,並且每一幀通過 translate 向前進的方向移動,最後一幀剛好到達下一格。
以向南方向的跑動為例。
@translateSouthX
和 @translateSouthY
代表向南每兩格 X 和 Y 方向的距離, .run-south-animation
是一個 mixin
,代表向南跑動的動畫。
@import "./animation.less";
@translateSouthX: -25px;
@translateSouthY: 13.5px;
// 向南方向跑動
.run-south {
background: url("...");
background-size: 1680px 255px;
.run-south-animation(@translateSouthX, @translateSouthY);
}
.run-south-animation
這個 mixin
是向南奔跑動畫的實現,注意 animation-timing-function
的值要設定為 steps(1)
,因為這裡每兩幀之間不需要補間動畫。 forwards
表示最後的狀態保持為動畫結束時的狀態。 animation-iteration-count
的值為 1 表示動畫只執行一次不需要重複,因為這裡是為了封裝一個移動一格的動畫,每次使用這個封裝好的類時只應該跑動一格。
關鍵幀 runSouth
中,經過多次測試最終選擇了使用 28 幀,因為在此幀數下,動畫較為流暢,圖片的大小也可以接受。在關鍵幀動畫中,幀數的選取至關重要,要根據具體的需求反覆測試,太多或太少都不合適。
@animationDuration: .7s
// 向南奔跑動畫
.run-south-animation(@translateX, @translateY) {
animation: runSouth @animationDuration forwards;
animation-iteration-count: 1;
animation-timing-function: steps(1);
@keyframes runSouth {
0% {
transform: translate(0, 0);
background-position: 0px -2*(@singleAstronautWidth+@verticalInterval);
}
...
50.4% {
transform: translate(@translateX / 28 * 14, @translateY / 28 * 14);
background-position: -840px -2*(@singleAstronautWidth+@verticalInterval);
}
...
100% {
transform: translate(@translateX / 28 * 28, @translateY / 28 * 28);
background-position: -1680px -2*(@singleAstronautWidth+@verticalInterval);
}
}
}
如何再次觸發跑動動畫
當第一次將跑動的類(假設向南)賦予展示小人的元素時小人可以正常跑動,但是如果緊接著再將這個類賦予這個元素時,即使元件中的 state 改變了,也不會觸發小人繼續向南跑動了。
為了驗證這一點,寫了一個小 demo,如下程式碼所示,在 3 s 後改變 state 中的 cls 和 doc,由於 cls 沒有發生變化,即使 doc 發生了變化,也不會觸發 first
類中的動畫重新執行,線上原始碼和演示。
class App extends React.Component {
state = {
cls: 'first',
doc: 'hello hello'
}
componentDidMount() {
setTimeout(() => {
this.setState({
cls: 'first',
doc: 'world world'
})
}, 3000)
}
render() {
return <div>
<h1 className={this.state.cls}>
{this.state.doc}
</h1>
</div>;
}
}
ReactDOM.render(<App />, document.getElementById("root"));
body {
height: 100vh;
margin: 0;
display: grid;
place-items: center;
max-width: 250px;
margin: 0 auto;
}
.first {
animation: myfirst 2s linear both;
animation-iteration-count: 1;
}
@keyframes myfirst
{
from {background: red;}
to {background: yellow;}
}
當 state 中的 doc 發生變化時,h1 元素只會更新變化的屬性,並不會將整個元素重新渲染。由 react 中的 diff 演演算法可知,要想讓 h1 元素重新渲染,可以給它賦予一個 key 值,然後在 3s 後改變這個 key 值,此時兩次 key 值不同就會觸發 h1 元素整個重新渲染,然後就可以讓動畫再次執行。
因此在 state 中新增一個 key 值,並將其賦予 h1 元素的 key 屬性,在 3s 後改變這個 key 值就可以使動畫重新執行。具體的程式碼可以參考:線上原始碼和演示。
移動航天小人到指定位置的實現(moveToSpecialPosByCurPos)
獲取航天小人將要到的指定位置
根據將要到的指定位置獲取下一步的前進方向
改變展示小人元素的位置(使用 transform 屬性)
監聽動畫結束事件,來對下一步的行為做出判斷
CSS 動畫事件
animationstart 某個 CSS 動畫開始時觸發。
animationend 某個 CSS 動畫完成時觸發。
animationiteration 某個 CSS 動畫完成後重新開始時觸發。
前面我們封裝的各個方向的類,每呼叫一次,小人會向前跑一步,跑完一步後會觸發 animationend
事件,在 animationend
事件的回撥函數中處理下面應該如何走。
判斷是否是連抽,如果是連抽走連抽邏輯
如果不是連抽,將剩餘的步數減 1,當前的步數加 1
判斷當前的位置是否大於賽道的總步數,如果大於賽道總步數,將當前位置設定為起點
如果小人當前剩餘的步數為0,那麼改變ELotteryStatus的狀態觸發中獎動畫的展示,並且觸發小人停止時的發光動畫,在1s後隱藏小人的發光動畫。
如果小人當前的剩餘步數不為0,根據當前的位置獲取下一步的前進方向,然後將相應前進方向跑動動畫的類賦予給展示小人的元素
改變展示小人元素的 key 值觸發再次執行動畫,小人繼續向前跑動。
連抽時瞬間移動的實現
當前位置加上抽獎的步數得到最終的位置
最終的位置是否超過賽道長度,如果超過了賽道長度,利用最終的位置減去賽道的總長度
呼叫 moveToSpecialPosByCurPos 函數移動到指定位置
觸發小人停止時的發光動畫,並且在 1s 後隱藏小人的發光動畫,改變 ELotteryStatus 狀態展示中動畫
從後臺拉取最新的位置,校正小人的位置
在瞭解 CSS 動畫的原理之後,實際做動畫需求的時候,一種比較推薦的做法是站在巨人的肩膀上。業界已經有很多優秀的開源動畫庫了,可以看一下這些開源的動畫庫是否可以用得上。
這裡主要介紹兩個比較常用的 CSS 動畫庫 Animista
和 Animate CSS
。
Animista
提供了很多動畫型別,並且還提供了很多引數供你設定,設定完成之後就可以獲得該動畫的 CSS 程式碼了。
Animate CSS
則是提供了很多動畫類,通過給元素設定不同的類就可以實現不同的動畫效果。
Animista 是一個線上動畫生成器,同時也是一個動畫庫,基本使用步驟如下:
1)選擇動畫,調節引數
首先選擇你想要的動畫型別,然後根據你的需求對該動畫的引數進行調節。
2)生成CSS程式碼
設定完滿足自己需求的動畫後,我們可以直接從網站上獲取程式碼,甚者還可以進行壓縮。
Animate CSS 是最著名的動畫庫之一,接下來介紹一下 Animate CSS
的一些基本用法,詳細的用法可以去官網查詢。
基本用法
首先在需要新增動畫的元素上新增類 animated
,然後是動畫的名字。
<div class="animated slideInLeft"></div>
Animate CSS ****還提供了一些類來控制動畫的一些屬性,例如動畫的延遲和速度等。
delay
通過新增 delay
類來延遲動畫的播放。
<div class="animated slideInLeft animate__delay-2s"><div>
speed
通過新增這些類(slow|slower|fast|faster)之一來控制動畫速度。
<div class="animated slideInLeft slow|slower|fast|faster"><div>
這裡用 Animate CSS 庫寫了一個小的 demo ,線上原始碼和演示。
迴流又叫重排,指幾何屬性需要改變的渲染,例如當元素的尺寸,佈局等發生變化時一般會引發迴流。重繪,指外觀屬性需要改變的渲染,例如當元素的背景色發生變化時一般會引發重繪。
一個 CSS 動畫往往會涉及尺寸,位置,顏色等屬性的變化,如果處理不當就會引發不斷地迴流和重繪,導致頁面卡頓,尤其在效能有限的行動端這種問題尤為嚴重。
在上面提到,幾何屬性改變時一般會引發迴流,外觀屬性改變時一般會引發重繪,那麼在 CSS 中哪些屬性是幾何屬性,哪些屬性是外觀屬性呢?這裡簡單地總結了一下。
幾何屬性:包括佈局、尺寸等可用數學幾何衡量的屬性。
佈局: display
、 float
、 position
、 list
、 table
、 flex
、 columns
、 grid
尺寸: margin
、 padding
、 border
、 width
、 height
外觀屬性:包括介面、文字等可用狀態向量描述的屬性
介面: appearance
、 outline
、 background
、 mask
、 box-shadow
、 box-reflect
、 filter
、 opacity
、 clip
文字: text
、 font
、 word
我們知道迴流一定會引發重繪,重繪不一定會引發迴流。迴流成本比重繪成本高得多,一個節點的迴流很有可能導致子節點、兄弟節點或祖先節點的迴流。頻繁觸發迴流會使得頁面不斷渲染,從而引發嚴重的效能問題,因此我們一定要儘可能地避免迴流,減少重繪。
接下來介紹一些 CSS 動畫效能優化的方法。
從以下四個方面對比一下 display:none
和 visibility:hidden
,方便書寫, display:none
簡稱 DN
, visibility:hidden
簡稱 VH
。
佔位表現
DN不佔據空間
VH佔據空間
觸發影響
DN觸發迴流重繪
VH觸發重繪
過渡影響
DN影響過渡不影響動畫
VH不影響過渡不影響動畫
株連效果
DN後自身及其子節點全都不可見
VH後自身及其子節點全都不可見但可宣告子節點 visibility:visible
單獨顯示
兩者的 佔位表現
、 觸發影響
和 株連效果
就能說明 VH
代替 DN
的好處,如果兩者都能實現需求的情況下推薦使用 visibility:hidden
。
top
是幾何屬性,操作 top
會改變節點位置從而引發迴流,使用 transform:translate3d(x,0,0)
代替 top
,只會引發圖層重繪,還會間接啟動GPU加速,因此更加推薦使用 transform 來代替 top。
當然 Table 佈局現在已經很少用了,不過在這裡還是提一下,來指出它所帶來的問題,避免引入類似的問題。牽一髮而動全身用在 Table 佈局身上再合適不過了,可能很小的一個改動就會造成整個 <table>
迴流,大家如果感興趣可以用 Chrome Devtools
的 Performance
偵錯看看。對於類似 table 佈局的結構,建議用 <ul>
、 <li>
和 <span>
等標籤進行取代。
瀏覽器的CSS解析器在解析css檔案時,對CSS規則是從右到左匹配查詢的,樣式層級過多會影響迴流重繪效率,建議保持CSS規則在 3層
左右。
像下面這段程式碼就存在很大的問題,每次迴圈操作 DOM 都會發生迴流,應該在迴圈外使用變數儲存一些不會變化的DOM 對映值。
for (let i = 0; i < 10000; i++) {
const top = document.getElementById("css").style.top;
console.log(top);
}
建議修改為下面這樣:
const top = document.getElementById("css").style.top;
for (let i = 0; i < 10000; i++) {
console.log(top);
}
在瀏覽器中設定頻繁迴流或重繪的節點為一張新圖層,因為新圖層能夠阻止節點的渲染行為影響別的節點,這張圖層裡如何變化都無法影響到其他圖層。
設定新圖層通常有以下兩種方式:
將節點設定為 <video>
或 <iframe>
為節點新增 will-change
為節點宣告 transform:translate3d()
或 transform:translateZ()
,這兩個宣告都會開啟GPU硬體加速模式,從而讓瀏覽器在渲染動畫時從CPU轉向GPU,實現硬體加速。
transform:translate3d()
和 transform:translateZ()
其實是為了渲染3D樣式,但宣告為 0
後並無真正使用3D效果,但瀏覽器卻因此開啟了GPU硬體加速模式。在 Webkit核心
下使用 transform:translate3d()
加速效果會更明顯。
CSS 效能優化的方法還有很多很多,這裡就不一一列舉了,感興趣的同學可以自行學習一哈。
本文主要講了 CSS 動畫方面的知識並給出了一些簡單的實踐以及一些效能上的優化方法,希望通過本文可以幫助你對 CSS 動畫有一個簡單的認識並能上手一些簡單的動畫。
雖然在 CSS3 中引入三大互動屬性後,讓 CSS 也能實現一些比較複雜的動畫了,但是不得不承認 CSS 可以實現的動畫效果還是非常有限。
不過好在業界還有很多可以實現動畫的方案,例如 canvas,three.js,zrender,d3 等等,提到動畫一般還會聯想到資料視覺化這個領域,資料視覺化的應用在我們日常工作和生活中隨處可見,例如前端監控平臺上的各種繪圖,某些網站上各種商品的 3D 展示等等,如果對資料視覺化的方向比較感興趣也可以研究學習一哈,很有趣的一個方向。
-------------------------------------------
個性簽名:夢想不只是夢與想
如果您覺得這篇文章哪個地方不恰當甚至有錯誤的話,麻煩告訴一下博主哦,感激不盡。
如果您覺得這篇文章對你有一點小小的幫助的話,希望能在右下角點個「推薦」哦。