做者:Nicolas(滬江前端開發工程師)
本文原創翻譯,轉載請註明做者及出處。css
各位,趕忙綁住本身並牢牢抓牢了,由於當你掌握了特別有趣但又複雜的CSS時序函數以後,你將會真正體驗到豎起頭髮般的興奮感覺。前端
好吧,本文的主題可能還沒能讓你熱血沸騰。言歸正傳,時序函數對CSS動畫而言就像是一顆隱藏的寶石,你想獲得多少驚喜取決於你如何使用它。web
首先,讓咱們定義下場景,並確保這些與時序函數相關的場景都是咱們熟悉的。如上所述,當你在CSS動畫領域中工做時(其中包括CSS過渡和基於關鍵幀的動畫),該功能將變得可用。那麼,它到底是什麼,它是作什麼的呢?數組
它是一個不太顯眼的基於動畫的CSS屬性之一,而它的大多數的相鄰屬性都是至關自明的。然而,它的特別之處是它使你可以控制和改變更畫的加速度 - 也就是說,它定義了動畫在指定的持續時間內加速和減速的方式。瀏覽器
雖然它不影響動畫的實際持續時間,但它可能會影響到用戶如何感知動畫的快或者慢。若是你還不知道它的實際意義,那麼請在這裏聽我娓娓道來,由於時序函數屬性比定義的更加有趣。網絡
注意:事實上沒有確切的「時序函數」的屬性命名,當我提到這個「屬性」時,既是指transition-timing-function和animation-timing-function屬性。架構
在繼續以前,讓咱們熟悉一下語法以及它適合在CSS中定義整個動畫過程的位置。爲了淺顯易懂,讓咱們使用CSS過渡作爲例子。咱們將從完整的過渡屬性數組開始:函數
div { transition-property: background; transition-duration: 1s; transition-delay: .5s; transition-timing-function: linear; } /* This could, of course, be shortened to: */ div { transition: background 1s .5s linear; }
對定義過渡的簡寫是至關寬鬆的,對於順序的惟一要求是延遲參數必須在持續時間值以後進行聲明(但沒必要當即跟在後面)。此外,對該功能來講transition-duration的值是惟一的必填項; 並且因爲其餘參數的默認值在大多數的時候都會適當填充,所以過渡不多須要超過如下的代碼片斷:工具
div { transition: 1s; } /* This is the same as saying: */ div { transition: all 1s 0s ease; }
但這有點無趣。雖然默認值一般足以知足頁面的標準懸停事件等,但若是你正在作一些更重要的東西,那麼時序函數是微調動畫的一個重要技巧!字體
無論怎麼樣,你如今已經有了對時序函數是幹什麼的基本瞭解。接下來讓咱們來看看它是如何作到的。
揭開面紗
過去不少人可能不會在乎時序函數屬性的可用的關鍵詞,它們有五個:ease(默認)ease-in,ease-out,ease-in-out和linear。然而,這些關鍵字僅僅是用於定義貝塞爾曲線的簡寫。
納尼?
你可能不瞭解這個術語,可是我敢打賭,若是你使用過圖形編輯軟件,而後你還建立過一條曲線,你實際上看到的是一條貝塞爾曲線!沒錯,當您使用筆或路徑工具來建立一個漂亮的平滑曲線,那麼你正在繪製一條貝塞爾曲線!總之,貝塞爾曲線是時序函數背後的黑魔法 ; 它基本描述了在圖形上的加速模式。
這是關鍵字ease的貝塞爾曲線
若是你像我同樣第一次看到這樣的貝塞爾曲線,那麼你可能想知道曲線是怎麼從圖表上的四個點繪製而成的!我可能沒法經過言語來告訴你,但幸運的是,我有一個特別精彩的GIF來幫助我解決這個問題,很是感謝維基百科。
一條正在繪製中的三次方貝塞爾曲線
由於該曲線由四個點造成,咱們將其稱爲「三次方」貝塞爾曲線,而不是「二次方」曲線(三個點)或「四次方」曲線(五個點)。
那麼如今,這纔是真正使人感到興奮的地方了,由於我揭示了你實際上能夠經過cubic-bezier()函數簡單的替換時序函數屬性值的關鍵字來訪問這個曲線。我很是理解你可能須要一些時間來控制你的興奮情緒...
你可使用cubic-bezier()函數操縱你想要的貝塞爾曲線,從而爲你的動畫建立出徹底自定義的加速模式!因此,讓咱們看看這個函數是如何工做的,以及它是如何使你可以建立出屬於本身的貝塞爾曲線的。
首先,咱們知道曲線由四個點造成,稱爲點0,點1,點2和點3。另一個須要注意的重要事情是,第一個點和最後一個點(0和3)已經在圖表上定義了,點0老是位於0,0(左下)和點3老是位於1,1(右上)。
這就使得你用cubic-bezier()函數只能在圖表上繪製點1和點2了。此函數傳入四個參數,前兩個是點1的x和y座標,後兩個是點2的x和y座標。
transition-timing-function: cubic-bezier(x, y, x, y);
爲了溫馨的理解語法,以及它是如何建立曲線和動畫的物理效果的,我將給你帶來5個和時序函數關鍵字相等的cubic-bezier()值以及動畫的最終效果。
讓咱們先從ease-in-out關鍵字開始,由於這條曲線背後的邏輯以及它如何將其轉換爲動畫的多是最容易理解的。
/* The cubic-bezier() equivalent of the ease-in-out keyword */ transition-timing-function: cubic-bezier(.42, 0, .58, 1);
一個徹底對稱的貝塞爾曲線,意味着動畫從慢速開始到全速,而後再減速。
你能夠看到,點1位於沿着x軸的0.42處和在y軸上的0處,而點2位於x軸上的0.58和y軸上的1。這就是一條徹底對稱的貝塞爾曲線,意味着動畫將低速移動至全速,而後以與起始處徹底相同的速率移出。所以,這個關鍵字的名稱就是這樣來的。
若是你看一下下面的演示,你將會看到ease-in-out值的物理效果,以及它和其餘關鍵字的值的不一樣之處。CodePen地址。
關鍵字ease是CSS時序函數屬性的默認值,它實際上和前一個很是接近,雖然它以更快的速度移入,而以更平穩的速度移出。
/* The ease keyword and its cubic-bezier() equivalent */ transition-timing-function: cubic-bezier(.25, .1, .25, 1);
關鍵字ease的曲線以更快的速度移入,以更平穩的速度移出。
你能夠看到在動畫中的這個時序函數直接轉換成的物理效果,在原點處更加陡峭,到結束點又被拉長了。在查看了這些示例後,記得參考以前的演示來對比下效果。
勿庸置疑,關鍵字ease-in和ease-out是正好相反的。前者是低速開始,然後者是低速結束,其餘時間內都保持全速。咱們以前看到的ease-in-out關鍵字如同邏輯所暗示的那樣,就是這2條貝塞爾曲線的完美組合。
/* The ease-in keyword and its cubic-bezier() equivalent */ transition-timing-function: cubic-bezier(.42, 0, 1, 1); /* The ease-out keyword and its cubic-bezier() equivalent */ transition-timing-function: cubic-bezier(0, 0, .58, 1);
ease-in貝塞爾曲線(左)| ease-out貝塞爾曲線(右)
最後一個關鍵字就徹底不是曲線了。正如其名稱所示,linear時序函數值在整個動畫過程當中都保持相同的速度,這意味着所獲得的貝塞爾曲線(或者根本不是)將只是一條直線。在曲線圖上表現爲沒有變化的加速模式。
/* The linear keyword and its cubic-bezier() equivalent */ transition-timing-function: cubic-bezier(0, 0, 1, 1);
linear時序函數值在整個動畫期間保持相同速度。
假如你回到以前的演示,你可能會注意到,儘管全部的例子都有保持不變的持續時間值,但有一部分的動畫看起來會慢於其餘動畫。這是爲何呢?就拿ease-in-out做爲一個例子,咱們知道,它在開始和結束時都比較慢,這意味着它所涉及動畫的中間部分須要以更快的速度執行。這就有效確保了咱們感知的實際動畫會更快更短,而線性動畫看上去更長。
你也許會以爲,這篇文章寫的有點拖沓(看我都寫了些什麼呀?),因此,如今是時候來寫點乾貨了,看看如何使用cubic-bezier()函數來建立自定義的時序函數。
如今咱們已經看到了關鍵字是如何等同於相對應的貝塞爾曲線的,而且看到了它們在動畫上的效果,那麼再來看看如何操做曲線來建立自定義加速模式。
如今你應該可以利用cubic-bezier()函數在曲線圖上繪製點1和點2 ,並至關清楚這將會如何影響動畫。然而,考慮到你一般看不到的曲線圖上繪製點,顯然這可能會很是無趣。
幸虧還存在Lea Verou這樣的牛人,他們彷佛從不須要休息,直到讓CSS的開發能夠變得更加簡單!Lea開發的Cubic Bézier工具,能夠用來建立完整的自定義的貝塞爾曲線,並將執行的動做與預約義的關鍵字進行比較。這意味着,你可使用這個平臺工具創建貝塞爾曲線直到獲得你想要的效果,而不是在cubic-bezier()函數中無聊地編輯數字。你能夠訪問Cubic Bezier而後把玩一下曲線,直到能夠實現你想要的效果。這就方便多了不是嗎。
LEA Verou開發的極其有用的Cubic Bézier(查看大圖)
簡寫的關鍵字爲你提供了在開始使用時序函數時有了很好的選擇,可是它們之間的差別一般較小。只有當你開始建立自定義貝塞爾曲線時,你纔會意識到時序函數在動畫上會給你帶來驚人的效果。
請看下面的示例,看一下各個動畫在相同持續時間內的極端差別。CodePen地址。
讓咱們仔細看一下第一個例子,試着去搞明白爲何它會產生這樣一個徹底不一樣的效果。
/* cubic-bezier() values for first example from preceding demo page */ transition-timing-function: cubic-bezier(.1, .9, .9, .1);
自定義貝塞爾曲線的示例
這個時序函數和默認關鍵字之間的主要區別是這個陡峭的貝塞爾曲線靠近了「進度」標尺(y軸)。這意味着動畫會忽然前進,在中間(即曲線接近水平時)有一段較長的幾乎暫停的低速。這種模式與咱們習慣於使用時序函數關鍵字的方式造成了鮮明對比,它採用相反的方向,緩動時段出如今動畫的開始和結束,並非在中間。
如今終於能夠將貝塞爾曲線收入囊中了,也已經對這個cubic-bezier()函數的功能屬性作了完全的探討,對嗎?也許你是這麼想的,但這個狡猾的傢伙還有更多的套路能夠玩!
沒錯:貝塞爾曲線還能夠更有趣!誰會想到,只有時間標尺(x軸)被限制在曲線圖上的0-1的範圍內,而進度標尺(y軸)能夠繼續延伸甚至超出0-1的範圍。
進度標尺恰如你所想象的那樣,底端(0)標記動畫的開始,頂端(1)標記動畫的結束。一般,三次的貝塞爾曲線老是以不一樣強度在這個進度標尺裏向北方向運行,直到到達動畫的終點。然而,在0—1範圍以外繪製點1和點2的可能性將使得曲線蜿蜒回退到進度標尺內,這實際上會致使在動畫中出現反向運動!和以前同樣,理解這一點的最好方法是經過視圖:
用標準0-1範圍以外的值的自定義貝塞爾曲線
你能夠看到點2在-0.5的位置,被繪製在標準0-1範圍以外,,曲線反向下拉。若是你看下面的演示,你會看到,這會在動畫的中段產生一個彈跳效果。CodePen地址。
反之,你能夠把反向運動放置於動畫的起始處,並故意超出原來終點。它就像是後退了幾步又回到了起點; 而後,在終點處,你的運動慣性使你超過了目的地,致使你走了幾步後,又保證你回到預期的目的地。請看下面的示例,以完全理解咱們在這裏說的什麼。另外,產生這種效果的貝塞爾曲線也能夠在下面看到。CodePen地址。
使用標準0-1範圍以外的值的自定義貝塞爾曲線
你如今應該對cubic-bezier()函數的值在標準的0-1範圍之外如何影響到動畫播放有一個較好的理解。固然咱們能夠成天看移動箱子的例子,但仍是讓咱們用一個有創意的時序函數逼真的演示一個示例來完成這一部分的內容。CodePen地址。
沒錯:這是一個漂浮氣球的動畫!你不老是想用CSS來作到這樣的動畫嗎?
這個動畫的要點是,當你點擊「添加氦氣」氣球飄到了「天花板」, 就像天然世界中的那樣,在碰到頂部以前會輕微回彈。使用cubic-bezier()函數的值在0-1範圍以外可讓咱們創造出彈跳,最終有助於產生一個逼真的效果。下面的代碼片斷展現了在cubic-bezier()函數中使用的座標,而最終的貝塞爾曲線的表如今下方能夠看到。
/* The cubic-bezier() values for the bouncing balloon */ transition-timing-function: cubic-bezier(.65, 1.95, .03, .32);
模擬彈跳的氣球的自定義貝塞爾曲線
這個例子很是好地解釋了曲線是如何轉換爲最終的動畫的,由於它表現的幾乎完美。首先,能夠看到的是,曲線從進度標尺開始一直到結束都是一條直線,表示氣球以恆定的速度從動畫開始移動到結束。而後,和睦球很是類似的是,曲線從標尺的頂端向下反彈,而後再緩緩地回到頂部。至關簡單!
一旦你掌握了曲線並用它來操縱你想要的藝術品,你就成功了。
要注意的最後一點是,當應用於CSS關鍵幀動畫時時序函數是如何表現的。這些概念與咱們迄今爲止使用的過渡示例中的概念徹底相同。但須要注意有一個重要的例外:當你應用一個時序函數設置關鍵幀時,它會在每個關鍵幀都會被執行,而不是應用於動畫的整個部分。
爲了證實這一點,假如咱們有四個關鍵幀,把一個箱子在一個矩形的四個角內移動,而後你應用了「彈跳」時序函數,也就是咱們在前面的氣球示例中使用的「反彈」的時序函數,那麼四個關鍵幀中每個動做都會經歷反彈,而不是整個動畫。讓咱們看看這個效果和代碼。CodePen地址。
@keyframes square { 25% { top:200px; left:0; } 50% { top:200px; left:400px; } 75% { top:0; left:400px; } } div { animation: square 8s infinite cubic-bezier(.65, 1.95, .03, .32); top: 0; left: 0; /* Other styles */ }
注意,若是100%的關鍵幀沒有定義,那麼元素將簡單的返回到它起點的樣式,這在這個案例中是原本想要的結果,因此就不必定義了。從演示中能夠很明顯的看到,時序函數應用於四個關鍵幀中的每個,由於它們每一個都表現爲從容器壁反彈。
若是你須要某些關鍵幀來呈現有別於其餘的時序函數,能夠直接使用一個單獨的時序函數值給到關鍵幀,好比下面的代碼片斷。
@keyframes square { 50% { top: 200px; left: 400px; animation-timing-function: ease-in-out; } }
若是你認爲這就是時序函數的所有了,那麼我告訴你,CSS時序函數要比預約義的緩動函數還要更多!
在這一節中,當咱們經過steps()時序函數探索「分步函數」的概念時,咱們能夠把咱們的曲線轉換成直線。
steps()函數是一個很小衆的工具,但在工具箱中仍然是有用的。它使您可以將動畫分紅多個步驟,而不是咱們習慣的常規補間動畫。例如,若是咱們想要將一個正方形在4秒內分四步向右移動400個像素,那麼正方形將每秒向右跳100個像素,而不是連續運動。讓咱們來看看在這個特殊案例中所須要的語法,既然咱們已經理解了錯綜複雜的cubic-bezier()函數,這個就應該很是簡單了。CodePen地址。
div { transition: 4s steps(4); } div:target { left: 400px; }
如你所見,將動畫分割成多個步驟是如此的簡單。但要記住,這個數字必須是一個正整數,因此不能是負數或者小數。然而,第二個可選參數爲咱們提供了更多的控制,可能的值有start和end,後者是默認的值。
transition-timing-function: steps(4, start); transition-timing-function: steps(4, end);
Start會在每一個步驟的起始位置運行動畫,而end是在每一個步驟的結束處運行動畫。使用以前的「移動箱子」示例,下圖對二者之間的差別作了很好的解釋。
start和end值在steps()函數中的差別。
能夠看到,start值,只要動畫被觸發,它就會當即開始,而end值,它開始於第一個步驟的結尾處(在這個案例中,將會在一秒鐘以後被觸發)。
爲了確保這個概述的足夠全面,steps()函數還能夠用兩個預約義的關鍵字:step-start和step-end代替。前者至關於steps(1, start),然後者是至關於steps(1, end)。
好吧,你可能沒有太多的需求來動畫一個移動的箱子,但steps()函數也有不少很酷的用途。例如,若是你有一套基礎的卡通的全部圖片精靈(sprites),那麼你可使用這種技術來逐幀播放,只須要使用幾個CSS屬性!讓咱們來看一個演示和製做它功能的代碼。CodePen地址。
div { width: 125px; height: 150px; background: url(images/sprite.jpg) left; transition: 2s steps(16); /* The number of steps = the number of frames in the cartoon */ } div:target { background-position: -2000px 0; }
首先,咱們有一個小的矩形框(125像素寬),它有一個背景圖像(2000像素寬),並排包含16幀。這個背景圖像最初與框的左邊緣對齊; 因此,咱們如今須要作的是將背景圖像一直向左移動,以便全部的16幀都經過小矩形窗口。正常動畫下,當背景圖像向左移動時,幀將僅僅在視圖中滑動; 然而,用steps()函數,背景圖像能夠移動16步到左側,確保每個16幀的圖像能夠像你但願的那樣進出視圖。就像這樣,你僅僅使用CSS過渡就能夠播放一個基本的卡通!
這個GIF演示了背景圖分步經過「窗口」的概念
我還發現另外一個使用steps()函數的創意來源於LEA Verou,她想出了一個特別巧妙的打字動畫。CodePen地址。
首先,你須要一些文本,但不幸的是,你還須要確切的知道你正在使用的字符數,由於你須要在CSS中使用這個數字。另外一個要求是字體必須是等寬的,以便全部字符的寬度徹底相同。
<p>smashingmag</p> .text { width: 6.6em; width: 11ch; /* Number of characters */ border-right: .1em solid; font: 5em monospace; }
咱們正在處理的文本有11個字符。在CSS單位ch的幫助下,咱們實際上可使用這個數字來定義該段的寬度,儘管咱們應該指定回退寬度針對那些並不支持這個單位的瀏覽器。而後,該段落在右側顯示一個黑色實心框,這將成爲光標。如今一切就緒; 咱們只須要簡單的使它動起來。
這須要兩個單獨的動畫:一個用於光標,一個用於打字。要實現前者,咱們所須要作的就是使黑色邊框閃爍,這沒有更簡單的了。
@keyframes cursor { 50% { border-color: transparent; } } .text { /* existing styles */ animation: cursor 1s step-end infinite; }
按照原先的設定,黑色邊框只會在黑色和透明之間切換,而後連續循環。這是steps()相當重要的地方,由於若是沒有它,光標也只是淡入淡出,而不會閃爍。
最後,打字動畫也很簡單。咱們須要作的是在11個步驟(字符數)中將其寬度從新設置爲所有寬度以前,將段落的寬度變爲零。
@keyframes typing { from { width: 0; } } .text { /* existing styles */ animation: typing 8s steps(11), cursor 1s step-end infinite; }
在這裏有一個關鍵幀,文本會在8秒內每次顯示一個字母,而黑色右邊框(光標)會連續閃爍。該技術很是簡單,但卻頗有效。
只要加上這個由LEA Verou創造的steps()函數的極佳用法,就將徹底改變效果,或許使文字看上去被刪除也不在話下。要作到這一點,只需改變下關鍵幀的關鍵字,以便它從to讀取而不是from,而後添加一個forwards的animation-fill-mode參數到一組動畫規則中。這將確保一旦文本「刪除」(即當動畫完成時),文本仍然保留被刪除狀態。看一下下面的演示。CodePen地址。
但本節中的兩個示例的缺點是,必須事先知道幀或字符的數量,以指定正確的步數,若是此數字變動了,那麼您將須要同時更改代碼。儘管如此,steps()函數在此已經展示了它的價值,並且是CSS時序函數的另外一個神奇功能。
咱們已經制定的,除非瀏覽器支持基於CSS的動畫,即CSS過渡和CSS動畫(基於關鍵幀的)模塊,不然不能使用CSS時序函數。幸運的是,如今瀏覽器的支持性愈來愈好了。
再次強調,對於關鍵幀動畫,只須要包含-webkit-前綴和沒有前綴的代碼。
顯然,基於CSS動畫的瀏覽器支持性是很是好的,但當涉及到時序函數時,支持性會變得略有不一樣。請看下錶更詳細地說明。
再次再次強調,雖然某些瀏覽器還須要更長一段時間才能支持時序函數的完整功能,但能夠看到目前的瀏覽器版本對此功能的支持較爲廣泛。(譯者注:目前主流瀏覽器都已經很好的支持)
讓咱們回顧一下,咱們學到了CSS時序函數的什麼?。
最後,這不是一篇關於CSS3技術的文章,雖然這些技術如今已經獲得全面的支持,但仍是須要作漸進加強。咱們老是要從下到上的處理; 也就是說,漸進加強能夠在不能很好支持這些功能的設備和瀏覽器上爲瀏覽器優化處理,確保你的做品的可接受性和可訪問性。
除此以外,祝你在用曲線和分步的時序函數調試動畫時玩的愉快!哈~
「Cubic Bézier」,Lea Verou
「Timing Functions」,Mozilla 開發者網絡
「Bézier Curves」,維基百科
iKcamp原創新書《移動Web前端高效開發實戰》已在亞馬遜、京東、噹噹開售。