難題
給過渡和動畫加上緩動效果是一種常見的手法(比如具有回彈效果的過渡過程)是一種流行的表現手法,可以讓界面顯得更加生動和真實:在現實世界中,物體A點到B點往往也是不完全勻速的
以純技術的角度來看,回彈效果是指當一個過渡達到最終值時,往回到一點,然后再次回到最終值,如此往復一次或者多次,並逐漸收斂,最終穩定在最終值。有相當的多JavaScript類庫可以創建動畫,且內置回彈效果等其他緩動效果。但是眼下,我們其實已經不需要借助腳本來實現過渡和動畫了。不過,在CSS中實現回彈效果的最佳方式是什么呢?
彈跳動畫
我們的第一感覺可能就是使用css動畫,並且設置如下關鍵幀:
@keyframes bounce{
60%,80%,to{transform:translateY(350px);}
70%{transform:translateY(250px);}
90%{transform:translateY(300px);}
}
相信我們都做過這樣的事,但是我們跑一遍這個動畫,會發現它顯示的及其不真實,主要原因在於,每當這個小球方向改變時,她得移動過程都是持續加速的,這看起來很不自然。原因其實就是因為它的調速函數在關鍵幀的銜接都是一樣的
所有的過渡和動畫之間都是跟一條曲線有關的,這條曲線指定了動畫過程在整段時間中是如何推進的
。
如果不指定調速函數,就是得到一個默認值。但是這個默認值
並不是我們想象中的勻速效果,而是:
注意,當時間進行到一半時,這個過渡已經推進到80%.
說到調速函數
,我們很自然聯系到了css內置的緩動曲線和貝塞爾曲線。
不論是在animation/transition
簡寫屬性中,還是在animation-timing-function/transition-timing-function
展開屬性中,你都可以把這個默認的調速函數顯示指定ease
關鍵字。除了ease外,還有四種內置的緩動曲線,你可以借助他們來改變動畫的推進方式
從上面四個圖中,我們很直觀的看出,ease-out
是ease-in
的反向版本。而這一對組合正是實現回彈效果所需要的:每當小球的運動方向相反時,我們希望調速函數也是相反的
。我們希望小球下落是加速的(ease-out)
,而彈起向上是減速的(ease-in)
:
@keyframes bounce{
60%,80%,to{
transform:translateY(400px);
animation-timing-function:ease-out;
}
70%{transform:translateY(300px);}
90%{transform:translateY(360px);}
}
.ball{
animation:bounce 3s ease-in;
}
雖然我們改動不大,但是已經發現回彈效果變得真實起來。不過顯然這五種內置的緩動曲線
是不夠用的,假如我們這個回彈效果是用來模擬自由落體的,那么我們需要一個更高的加速度
和ease的反向版本,又如何得到呢?
其實所有的這五種曲線都是通過(三次)貝塞爾曲線
來指定的,而CSS的調速函數都是只有一個片段的貝塞爾曲線
,每個函數也只有兩個控制錨點,CSS就提供了一個cubic-bezier()
函數,允許我們指定自定義調速函數。他接受四個參數,分別是兩個控制錨點的坐標值,
cubic-bezier(x1,y1,x2,y2)
,曲線的兩個端點固定在(0,0)和(1,1)之間,前者是整個過渡的起點(時間進度0%,動畫進度0%)而后者是整個過渡的終點(時間進度100%,動畫進度100%)。
舉例來說,ease
等同於cubic-bezier(.25,.1,.25,1)
,因此它的反向版本就是cubic-bezier(.1,.25,1,.25)
@keyframes bounce{
60%,80%,to{
transform:translateY(400px);
animation-timing-function:ease;
}
70%{
transform:translateY(300PX);
}
90%{
transform:translateY(160px);
}
}
.ball{
animation:bounce 3s cubic-bezier(.1,.25,1,.25);
}
See the Pen css-animation-easing by okaychen (@okaychen) on CodePen.
我們可以借助cubic-bezier.com的圖形化工具,進行反復嘗試和優化,從而進一步改寫這個回彈動畫.
最后
經過以上這些知識的學習儲備和練習,相信我們已經可以做出很棒的彈跳動畫了.
我們在文章開始放了一個小球彈跳的gif圖效果,那么就讓我們真真正正的動手來寫一下吧!
See the Pen css-animation-easing-practice by okaychen (@okaychen) on CodePen.
彈性過渡
假設我們有一個文本輸入框,每當它被聚焦時,都需要展示一個提示框
我們有如下結構:
<label>
Your username:<input id="username" />
<span class="callout">Only letters,numbers,usrescore(_) and hyphens (-) allowed!</span>
</label>
每當用戶聚焦這個文本輸入框時,都會有一個半秒鍾的過渡,可能我們會完成這樣的代碼
input:not(:focus) + .callout{
transform:scale(0);
}
.callout{
transition:.5s transform;
transition-origin:1.4em -.4em;
}
這個過渡沒有任何問題,但是我們希望它在結尾時能在誇張一點話,顯得更加自然生動,我們可能會把這個過渡改為一個動畫,然后用上面提到的緩動曲線
@keyframes elastic-grow{
from{transform:scale(0);}
70% {
transform:scale(1.1);
animation-timing-function:cubic-bezier(.1,.25,1,.25); /*反向的ease*/
}
}
input:not(:focus) + .callout{ transform:scale(0); }
input:focus + .callout{ animation:elastic-grow .5s; }
.callout{ transform-origin:1.4em -.4em; }
添加了這個動畫之后,確實發揮了作用。不過這里我們其實只是需要一個過渡而已,而我們本質上卻使用了一個動畫,顯得有些大材小用,有一種殺雞用牛刀的感覺,我們如何只用過渡完成這個效果呢?
這里我們就用到了上面說起的調速函數cubic-bezier()
,在這個例子中,我們希望調速函數先到達110%的程度(相當於scale(1.1)
),然后在過渡回100%,我們把控制錨點向上移,
這個自定義調速函數在垂直坐標上已經超出0~1的區間,最終又回到1,在70%的時間點到達了110%的變形程度的高峰,然后繼續用剩下30%的時間回到它的最終值
整個過渡的推進,非常接近前面的動畫方案,但他僅需要一行代碼就可以實現整個效果
input:not(:focus) + .callout{ transform:scale(0) }
.callout{
transform-origin:1.4em -.4em;
transition:.5s cubic-bezier(.25,.1,.3,1.5);
}
but,wait...當提示框收縮時,左下角出現的是什么?其實,當我們把焦點從輸入框切出去的時候,所觸發的過渡會以scale(1)
作為起始值,並以scale(0)
作為最終值,這個過渡仍然會在350ms后到達110%的變形程度。只不過在這里,110%的變形程度的解析結果並不是scale(1.1)
,而是scale(-0.1)
我們可以定義關閉狀態的css規則(假如我們指定普通的ease調速函數)把當前的調速函數覆蓋掉
input:not(:focus) + .callout{
transform:scale(0);
transition-timing-function:ease; /*覆蓋cubic-bezier*/
}
.callout{
transform-origin:1.4em -.4em;
transition:.5s cubic-bezier(.25,.1,.3,1.5);
}
再試一試,發現已經關閉提示框已經恢復到我們設置cubic-bezier()
之前的樣子了,
但是其實我們仔細觀察發現另一個問題:提示框的關閉動作明顯要遲鈍一些。
我們細細想來發現,在提示框展開過程中,當時間為50%(250ms)時,它就已經到達100%的尺寸效果了。但是在收縮過程中,從0%~100%的變化會花費我們為過渡所指定的素有時間(500ms),因此感覺會慢上一般
然后我們會想到同時覆蓋過渡的持續時間:可以用transition-duration
這一屬性,也可以用transition
這個簡寫屬性來覆蓋所有值,如果選擇后者的話就不需要指定ease了,因為他本來就是transition
的初始值:
input:not(:focus) + .callout{
transform:scale(0);
transition:.25s;
}
.callout{
transform-origin:1.4em -.4em;
transition:.5s cubic-bezier(.25,.1,.3,1.5);
}
See the Pen css-animation-task by okaychen (@okaychen) on CodePen.
最后
雖然彈性過渡在很多過渡中都可以收到不錯的效果,但是某些時候他產生的效果可能相當糟糕
。典型的反面案例
出現在對顏色屬性
的彈性過渡中。盡管顏色發生彈性過渡可能非常有趣,但這種效果在UI場景中通常是不合適的.
為了避免不小心對顏色設置了彈性過渡,可以嘗試把過渡的作用范圍限制在某幾種特定的屬性上
,transition不指定時,transition-property
就會得到它的初始值:all
,這意味着只要是過渡的屬性都會參與過渡。我們可以在transition
中設置transform
input:not(:focus){
transform:scale(0);
transition:.25s transform;
}
.callout{
transition-origin:1.4em -.4em;
transform:.5s cubic-bezier(.25,.1,.3,1.5) transform;
}
參考資料