CSS動畫

jopen 9年前發布 | 60K 次閱讀 CSS 前端技術

本文是為幫助您入門和熟悉CSS動畫而編寫的,使用它們來為您帶來基于Web的接口以及為藝術帶來生命。雖然 W3C的CSS動畫規范 仍在修訂中,但是如今它已經有大量的內容可以供我們使用了。

對我而言,CSS動畫最令人激動的事情之一是,我們可以非常輕松地使用我們已經熟悉的工具來把它們添加進我們的項目中。如果您已經精通HTML和CSS,您就不需要學習新的語言或插件來為您的項目添加動態效果了。HTML和CSS已經足夠,這是一個非常大的加分!無論你只是添加一點點引人注目的設計細節,還是添加非常多的動畫,都沒有問題。

CSS的transitions屬性、JavaScript和SVG都可以為網頁添加動態效果,而且都值得我們去做試驗,但是JavaScript和SVG的內容不會在這本書中進行講解。我們重點講CSS動畫規范。

我整理本文的目的是,讓您知道CSS動畫的可能性,并為您進行試驗以及創建動畫提供一個堅實的基礎。本文會給你足夠的CSS動畫入門的內容,足以讓你變得更有創造力!

瀏覽器前綴一覽

如果沒有對瀏覽器前綴有足夠的了解,您就沒辦法把CSS動畫學好,所以我們花一點時間來看看我在本文是如何處理這個問題的。

在我寫這篇文章時,Firefox、Opera和IE都可以無前綴支持CSS動畫了~耶!但是其它的瀏覽器,包括這些瀏覽器稍舊的版本,仍需要添加瀏覽器前綴來對CSS動畫提供支持。因此,我強烈建議在你的所有項目中,只要是動畫屬性都添加前綴。雖然,我們只能說這是需要的。

當然,你在進行試驗或者只是在本地進行測試,只添加你使用的瀏覽器的前綴即可。到生產版本的時候再添加其它前綴即可。

為了方便閱讀,我會在本文的代碼片段中使用無前綴版本來寫CSS動畫屬性。有些代碼段中會包含前綴,還有一些示例也可以在 CodePen 上找到,你可以編輯并測試運行它們。

好了,我們現在就開始學習動畫吧!

CSS動畫基礎

CSS動畫看起來非常復雜,但是其核心動畫的基本內容是非常簡單的。動畫名、關鍵幀,還有一些是確定移動的方向的內容。

我們從創建CSS動畫的基本要素開始。任何CSS動畫中都有兩個主要的部分:

  • 定義動畫
  • 將其賦給指定的HTML元素(或元素)。

你可以按照不同的順序來寫,但是我建議先定義動畫,然后再應用它——這樣更符合我的處理過程。

@keyframes 規則

要定義CSS動畫,我們需要先使用@keyframes規則來聲明關鍵幀。你還需要給動畫命名,便于后面引用。

例如,如果你創建了一個在屏幕上移動的小車的動畫,你可能需要把動畫命名為像drive這樣的,然后你的@keyframes規則應該是這樣寫的:

@keyframes drive {

}

什么是關鍵幀?

簡單來說,你的關鍵幀就是一個描述在整個動畫過程中,會發生變化的屬性列表(也就是,哪些屬性會改變,如何改變以及什么時候改變)。

列表上的每一次運行,都會被認為是動畫的一次迭代。任何你想要看到的動畫屬性的改變都需要被列在你的關鍵幀中。對于Animatable屬性的列表,Mozilla Developer Network有我迄今為止看到的 最全面的內容

在傳統動畫中,關鍵幀是動畫中的關鍵點。通常,這些關鍵內容會先由資深漫畫家繪制出來,然后初級的漫畫家在每一幀之間繪制過渡的動畫,讓所有的關鍵幀都能平穩回放。CSS關鍵幀的工作方式也是類似的:我們使用關鍵幀在動畫中指定在各個點的動畫屬性值,瀏覽器會自動填充各個部分的過渡。如果你有使用過After Effects 或 Flash 這樣的軟件,你應該已經對于關鍵幀的概念非常熟悉了。

定義關鍵幀

動畫是由關鍵幀組成的!在@keyframes聲明中,我們有兩種方法來對它進行定義:關鍵字from和to;或百分比。

非常簡單的動畫可能只是把一個對象從一個地方移動到另一個地方。在這種情況下,關鍵字from和to非常適合來定義關鍵幀。

正如它們的名字,通過寫關鍵幀來定義動畫從哪里開始,然后到哪里結束。如果我們把它應用在我們上邊提到的簡單的小車動畫,我們可以把我們的小車從它當前的位置(坐標為0)移動到一個往右400px的位置,來讓它在屏幕上移動。

@keyframes drive {
    from {
        transform: translateX(0);
    }
    to {
        transform: translateX(400px);
    }
}

在很多情況下,你會想要在不止兩個狀態之間定義動畫,這樣的話使用百分比會比較合適。

用百分比定義關鍵幀,從0%關鍵幀開始,以100%作為結束。0%到100%之間的任何數字都可以定義關鍵幀,所以使用百分比有非常大的靈活性。當然,如果你喜歡的話,你也可以將百分比和from、to混合使用。

如果我們在小車動畫中使用百分比來定義關鍵幀,它是這樣寫的:

@keyframes drive {
    0% {
        transform: translateX(0);
    }
    100% {
        transform: translateX(400px);
    }
}

正如你看到的,from相當于0%,而to則相當于100%。

如果你的關鍵幀列表中不包括0%或者100%,元素上現有的動畫樣式將會直接被用在0%和100%的的位置。此外,你不必按照嚴格的升序排列來列出百分比。一個0%的關鍵幀仍然會被認為是動畫的第一個關鍵幀,即使它不是按照順序排列的。這有很大的靈活性可以給關鍵幀分組,以便以后再查看。

將動畫賦給HTML元素

一旦創建了關鍵幀聲明塊,就需要準備把動畫賦給一個HTML元素或其它元素。我們還需要為HTML元素定義一個簡短的屬性列表,比如img元素,為它應用我們剛才創建的動畫。

第一個屬性是animation-name,用于告訴我們的圖像,我們為它應用了哪組關鍵幀:

animation-name: drive;

第二個屬性是animation-duration。我們的關鍵幀定義了整個動畫的內容,但是我們并沒有聲明我們想要讓它持續多長。可以把它設置為2s:

animation-duration: 2s;

animation-duration的默認值是0,這也就是為什么在我們看到動畫出現之前,我們想要將它設置成其它值。它可以取秒(s)或微秒(ms)為單位。

只有設置了這兩個屬性以及我們剛才定義的關鍵幀,我們才可以看到動畫。

我們完整的CSS是這樣寫的:

.car {
    animation-name: drive;
    animation-duration:1s;
}
@keyframes drive {
    from {
        transform: translate(0);
    }
    to {
        transform: translate(400px);
    }
}

完成了!我們剛才只是完成了創建一個CSS動畫需要的最基礎的東西:一組定義好的關鍵幀;一個動畫名稱用于綁定HTML元素;以及動畫的長度聲明。

還有一件事……

有兩個附加屬性是我在所有的動畫中都會顯式定義的。一次性地完成動畫而不再去修改(或是很長一段時間都不再去修改)的情況是非常罕見的。所以,我發現為我自己創建的每個動畫都定義animation-timing-function和animation-iteration-count屬性,這非常方便。

animation-timing-function屬性

animation-timing-function屬性的默認值是ease。但是,我建議你再顯式設置一次這個值,因為它對于動畫有非常大的影響(我們會在后面詳細說一下它)。對于我們簡單的小車示例,我把"timing function"值設置為ease-in:

animation-time-function: ease-in;

animation-iteration-count屬性

animation-iteration-count屬性也是很方便的一個屬性,即使你使用的是默認值。這個屬性決定了動畫會重復播放多少次,它的默認值是1。

animation-iteration-count: 1;

作了這些補充之后,我們的最終CSS是這樣的:

.car {
    animation-name: drive;
    animation-duration: 2s;
    animation-timing-function: ease-in;
    animation-iteration-count: 1;
}
@keyframes drive {
    from {
        transform: translate(0);
    }
    to {
        transform: translate(400px);
    }
}

查看最終效果。作為我們的第一個示例動畫,還是不錯的。

探究動畫屬性

我們已經了解了CSS動畫最基礎的內容。它涵蓋了非常多的內容,但是你很快很發現動畫有不同的層,當你在完善動畫的同時還想節省時間的時候,你就需要有更多幫助你控制動畫的東西了。

幸運的是,有很多的屬性可以讓我們對CSS動畫有更深層次的控制,也可以有更多的潤色,讓動畫更豐富。

本節將著眼于animation-delay、animation-fill-mode和animation-direction這些屬性的使用。我們將使用一個稍微復雜一點的動畫,滾動的球,作為我們的下一個示例的基礎。我已經創建了一個從左到右移動的球的動畫,并通過幾個關鍵幀來演示這些屬性是如何派上用場的。這是我們的動畫效果。

我們最初的CSS樣式:

.ball {
    animation-name: ballmove;
    animation-duration: 2s;
    animation-timing-function: ease-in-out;
    animation-iteration-count: 1;
}
@keyframes ballmove {
    0% {
        transform: translateX(100px) rotate(0);
    }
    20% {
        transform: translateX(-10px) rotate(-0.5turn);
    }
    100% {
        transform: translateX(450px) rotate(2turn);
    }
}

animation-delay屬性

在我們最初的示例中,動畫是在我們加載頁面完成后就立即運行的。那如果我們不想要動畫怎么快就開始播放呢?這就是animation-delay上場的時候啦。animation-duration和animation-delay都接受以秒(s)和毫秒(ms)為單位的值,現在為動畫設置2s的animation-delay。

animation-delay: 2s;

帶有延遲的動畫:

現在我們已經在球動畫開始之前有了一個看起來不錯的暫停。你可能注意到了,在動畫結束的時候,我們的球會回到原來的位置。這不是結束一個動畫最理想的方式。當你的對象在屏幕上移動,你可能希望它能停在結束的位置,而不是回到原來的位置。這就是animation-fill-mode可以完成的東西。

animation-fill-mode屬性

animation-fill-mode屬性可以接受四個值:none、backwards、forwards和both。如果你沒有聲明這個屬性的話,默認值為none。在這個示例中,我們讓關鍵幀一路把球移動到容器的右側。但是在動畫結束的時候,小球又回到了它的初始位置。這是因為animation-fill-mode:none的作用。當動畫結束的時候,它會返回自己的初始位置。

我在CodePen上創建了一個示例,你可以為其添加或者改變animation-fill-mode屬性的值,來看看結果是否有變化。

animation-fill-mode: forwards

但是,如果我們顯式地設置animation-fill-mode為forwards,在動畫結束之后,我們的小球會保持它最后一幀的樣式;在這個示例中,100%的關鍵幀會把它放到右側。我們給.ball這個類添加一個屬性:

animation-fill-mode: forwards;

現在,我們的小球就會保持在我們動畫結束的位置,這可能比較符合常理。效果如下。你可以想象成animation-fill-mode: forwards就是在動畫播放過程中用于擴展最后一個關鍵幀的樣式的。

animation-fill-mode: backwards

當使用延遲動畫時,將動畫的fill-mode設置為backwards非常方便。在我們的示例中,動畫有一個2s的延遲,然后它先向左再向右移動。如果沒有設置animation-fill-mode屬性,在動畫經過延遲之后,開始播放,小球會突然跳到0%關鍵幀定義的位置。這雖然不會像動畫結束的時候它就突然回到起點這樣,但是看起來還是不太好的。

如果我們加上一個animation-fill-mode屬性并設置為backwards,小球就會在我們的animation-delay時變成我們0%關鍵幀定義的樣式。你可以想象成它就是把0%關鍵幀位置的樣式擴展到延遲的位置。

animation-fill-mode: backwards;

預覽我們的示例來看看結果:

補充一下,如果沒有0%(或from)關鍵幀,你的動畫也可以在animation-delay的過程中先到達相應的位置。瀏覽器會使用已經應用到你的目標元素上的樣式,作為你的動畫的開始關鍵幀的樣式,以替換缺省的初始關鍵幀,因此會在延遲過程中將你的目標元素先放到對應的位置。不過這并不總是可行的,取決于你項目的設置,有可能是其它的情況,不過有得選擇總是好的。

animation-fill-mode: both

還有一個可選的值是both,正如它的字面意思,它是forwards和backwards的結合。動畫可以在開始前就已經是第一個關鍵幀的樣式,然后,在動畫完成后,保持最后一個關鍵幀的樣式。

回到我們的示例中。這種情況下,我們會使用both,這樣我們的小球就會在它開始前就帶有我們第一個關鍵幀定義的樣式,在它結束之后會保持最后一個關鍵幀的樣式。

animation-fill-mode: both;

我們最終的CSS是這樣寫的:

.ball {
    animation-name: ballmove;
    animation-duration: 2s;
    animation-timing-function: ease-in-out;
    animation-iteration-count: 1;
    animation-delay: 1s;
    animation-fill-mode: both;
}
@keyframes ballmove {
    0% {
        transform: translateX(100px) rotate(0);
    }
    20% {
        transform: translateX(-10px) rotate(-0.5turn);
    }
    100% {
        transform: translateX(450px) rotate(2turn);
    }
}

預覽示例,查看結果:

animation-direction屬性

還有另一個動畫屬性我想在這探討一下,animation-direction。目前為止,我們的動畫只能forward播放,而且運行效果也非常不錯。但是我們還有其它的選擇——有一個非常有用的屬性!animation-direction,它的值可以是normal(正常),reverse(反轉),alternate(交替)和alternate-reverse(交替反轉)。它們聽起來有點拗口,但是當你看到它們的使用情況時你就會覺得真是so good。我創建了一個示例,你可以添加或更改animation-direction屬性的值來看看它們不同的效果。

它的默認值是normal,這個值是通過你列出的關鍵幀聲明直接播放的。這兒有一個截圖。

CSS動畫

reverse設置表示你的動畫是按照你的關鍵幀序列反向播放的,就像回繞播放一樣。把direction設置為reverse,我們的小球就會從右往左跑了。

CSS動畫

效果如下:

如果你的動畫的iteration-count屬性的值大于1,你可以使用alternate值。第一次按照正常的順序播放,第二次就會反向播放,然后正向,然后反向……方向交替,從正向開始,直到iteration-count跑完。

CSS動畫

效果如下:

最后,alternate-reverse是和alternate一樣的意思,除了它是從反方向開始的。通過設置alternate-reverse屬性,我們的小球和上一個示例一樣交替迭代方向,只不過它是從一個reverse的方向開始的,而不是正常的方向。

CSS動畫

如果你的觀察力比較敏銳,你可能會注意到我們的animation-timing-function屬性會隨著animation-direction的反向一起反向,這是CSS動畫一個很不錯的內置效果。

通過這些簡單的例子,我相信你可以發現這些屬性對于CSS動畫創建非常有趣的效果的用處之大。

簡寫

你可以使用簡寫來指定你的動畫屬性。Thanks god!一個animation定義的動畫簡寫屬性可能是這樣的:

animation: myAnimation 1s ease-in-out 2s 4;

也就是:animation: <animation-name> <animation-duration> <animation-timing-function> <animation-delay> <animation-iteration-count>。

你可能注意到了在不同的示例中,簡寫屬性的順序不一樣,雖然他們運行起來都沒問題。在這個特殊的簡寫中,相似術語的順序(比如持續和延遲的值)會比較重要。 W3C注意事項

該順序在定義每個動畫的時候都是非常重要的:第一個值解析為動畫持續的時間,第二個值解析為動畫延遲的時間。

W3C目前定義的簡寫順序是這樣的:

<single-animation> = <single-animation-name> || <time> || <single-animation-timing-function> || <time> || <single-animationiteration-count> || <single-animation-direction> || <single-animation-fill-mode> || <single-animation-play-state>

要使用簡寫在一個元素中定義多個動畫,你需要使用逗號來分隔每個動畫的屬性值。比如在一個元素中定義兩個動畫需要這樣寫:

animation: myAnimation 1s ease-in-out 2s 4, myOtherAnimation 4s ease-out 2s;

理解easing

easing是什么?這一節都是關于easing的內容?沒錯!Easing是我覺得我們網頁設計師目前還探討得不夠的內容之一。

“時間是動畫的一部分,它為每一次移動賦予了意義。移動可以是通過簡單地在兩個不同的位置繪制相同的內容,然后在兩者之間插入數個其它的圖片來完成。但是這樣屏幕上的看到的就只是單純的移動,而不是動畫。” ——Harold Whitaker and John Halas, Timing for Animation

而Easing有足夠的能力去影響動畫的交流。某一個對象是重要的,但是它到達相應位置的方式可能更重要得多。事實上, Timing for Animation 這本書詳細地寫了這方面的內容。雖然我們不太可能做到把動畫繪制成像迪斯尼那樣的,但是了解如果控制我們動畫的移動還是很重要的。

這個移動傳達的是對象什么樣的情緒、分量和其它關鍵性格以及溝通的因素。這些移動或變化的過渡為我們提供了一個很好的激流的機會,盡管它們可能只會停留不到一秒的時間。

根據定義,Easing是速率被分配到整個動畫過程中的方式。在CSS中,我們的easing是用animation-timing-function屬性處理的。我們有三種定義timing的方式:關鍵字;自定義三次貝塞爾曲線;steps。steps在這里是奇數的,因為它們有自己獨特的概念,所以它實際上并沒有做任何的easing。我們將在最后的一部分內容中簡要地探討一下steps。

easing關鍵字

首先,我們來仔細地看一下預定義的關鍵字選項,來對幕后發生的事情有更進一步的了解。我們預定義的easing關鍵字是:ease(默認);linear;ease-in;ease-out和ease-in-out。

如果我們要使用linearEasing創建一個在兩個關鍵幀之間一幀一幀線性移動的小球,它的運動效果如下:

CSS動畫

對象以保持相同的速度在兩個關鍵幀之間移動。速度從整個動畫的開始到結束都是不變的。這通常會被視為非常機械的不自然的移動,因為在現實生活中,沒有東西會像它這樣以恒定的速度移動。

如果我們用ease-in創建相同類型的插圖:

CSS動畫

該移動在一開始的時候比較慢,然后在接近終點的時候慢慢加快速度。在一般情況下,這種easing類型創造了一種蓄勢待發的加速感。對象在移動過程中的速度加快可以暗示其重量,還可以加上其他的外力來同它配合。

使用ease-out給了我們相反的感受。動畫在一開始的時候速度比較快,然后隨著慢慢接近終點,速度越來越慢:

CSS動畫

結合ease-in和ease-out的概念,我們得到了ease-in-out,這個值會讓對象在中點的時候速度上升到最快,在開始和結束的時候速度較慢。從ease值得到的Easing移動是ease-in-out的變體;ease在結束的時候有一個更劇烈的減速,但是你可以看到它們其實看起來是很相似的。個人而言,我更喜歡ease-in-out,因為在大多數情況下運動比較平衡。

貝塞爾曲線

值得慶幸的是,easing的值我們有不止五個關鍵字可以選擇。在我們希望能夠有更多的easing選擇的時候,三次貝塞爾曲線來拯救我們了!上面的幾個關鍵字也可以被定義為三次貝塞爾曲線。這些關鍵字有點像常見貝塞爾曲線的快捷方式。當你需要的控制比上邊五個關鍵字提供的更多的時候,你可以為你的timing函數創建三次貝塞爾曲線,這樣easing可選擇的值就幾乎是無限的!

創建曲線時,我們根據時間來計算動畫的進展,然后得到這樣的一條代表了動畫過程中的速率的曲線。

CSS動畫

lineareasing關鍵字對應的貝塞爾曲線

我們不需要去糾結它們背后的所有計算,因為我們的目的不在于此,盡管你對 貝塞爾曲線的基礎 充滿好奇心,想要研究它的每個數學方面的細節。理解曲線的關鍵是:曲線越陡峭代表速度越快,曲線越平坦代表速度越慢。下邊這條曲線是ease-in-out關鍵字對應的貝塞爾曲線。中間是最陡峭,所以移動得最快,最后是平緩的,所以速度變慢了。

CSS動畫

ease-in-out關鍵字對應的貝塞爾曲線

對曲線形狀的小調整都會影響導致我們動畫的細微差異。每條三次貝塞爾曲線都是通過四個在0和1這個范圍之間的值定義的,這四個值用于表達曲線該如何繪制。

cubic-bezier(0.165, 0.840, 0.440, 1.000)

如果是像上邊這樣寫,那它們對我們大多數人是沒有意義的,因為根本不明白它們代表的意思。想要讓你打破在數學課上使用舊圖形計算器的習慣是需要相當一段時間的。幸運的是,我們可以使用一些工具來讓這些數字的意義可視化,也更直觀,方便我們理解。

創建三次貝塞爾曲線的工具

我最喜歡的三次貝塞爾曲線生成工具是, Matthew Lein 的 Ceaser ,提供了各種不同的預設,并允許你拖動點來創建你自己的貝塞爾曲線,還可以預覽你創建的easing。當你對生成的東西滿意的時候,你就可以復制它動態生成的代碼,并把它放在你的CSS中使用。Ceaser還提供了和 Penner easing方程 (常用于Flash中,現已被移植到JavaScript、CSS等地方使用)等同的CSS。

CSS動畫

在Ceaser中創建貝塞爾曲線

Ceaser不是唯一可以取得easing信息的地方, Easings.net 展示了一些可選的三次貝塞爾曲線的交互版本,以及由此產生的移動都在同一個地方。Lea Verou的 cubic-bezier.com 也可以讓你創建、比較和分享三次貝塞爾函數。動畫是一個可視化的東西,所以有這些可視化編輯器和工具來幫助你取得你想要的移動效果是非常好的。這比靠猜數字來想象更有效得多。

現在我們已經對CSS中的easing有了相對深入的了解,你可以通過對你的動畫進行微調,做出合適的easing選擇,讓你得到需要的運動和信息。

如果這里討論的easing,timing和動畫原則激起了你的興趣,我強烈推薦你閱讀一下 迪斯尼動畫的十二個基礎原則 ,還有 Timing for AnimationThe Animator’s Survival Kit 這兩本書。傳統動畫工藝有非常豐富的歷史,值得我們去學習。

Timing函數并不是萬能的

經過前面關于timing函數可以做的東西的這么多的介紹,有一個更重要的點是,CSS要如何使用我們定義的timing函數。對于關鍵幀動畫,timing函數是在關鍵幀之間應用的。在很多情況下,你只能給每個動畫指定一個timing函數:

.someClass { animation-timing-function: cubic-bezier(0.550, 0.055, 0.675, 0.190); }

在這種情況下,你的三次貝塞爾函數會在動畫中的每個關鍵幀之間應用。這將決定所有屬性之間的移動樣式,都會按照你在keyframes中定義的函數運動。

但這并不總是理想的,尤其是那些復雜一些的動畫。當你要制作的是比較復雜的運動時,在所有的關鍵幀之間應用相同的easing,幾乎是不可能的。一開始的時候它看起來可能會很奇怪,但是我們可以改變timing函數,將它應用于@keyframes聲明塊中的中期動畫。

@keyframes myAnimation {
    0% { 
        opacity: 0.5; 
    }
    50% {
        opacity: 0.3;
        animation-timing-function: ease-in-out;
    }
    100% { 
        opacity: 1; 
    }
}

在上面的代碼中,一個ease-in-out的時間函數會被應用在50%到100%的關鍵幀之間,但是之前設置的時間函數將會被默認用于0%到50%關鍵幀之間。

常見的動畫任務

選出最常見的動畫基本是不可能的,所以我選了幾個示例,包括了一些我認為CSS動畫可以使用的比較有用而且有趣的內容。在這里我們將詳細講解每一個示例,把你CSS動畫方面的知識應用到實際中。我特意選了那些HTML相當簡單的例子,這樣我們就可以專注于CSS以及那些驅動動畫的特定屬性。

無限循環的背景動畫

CSS制作這種類型的動畫是非常棒的,它可以很簡單地建立一個無限循環。為了展示它是如何運行的,我們將創建幾朵在天空中飄動的動畫云朵。在CodePen上有這個 示例 ,你可以跟著代碼學習,接著往下看。

看一下初始的CSS,對于上邊的云朵,我們使用了共享的類樣式,為它們設置了背景圖片、寬度和高度。我們單獨為每朵云設置了不同的位置值以及z-index的值,來把它們錯開:

.cloud {
    width: 248px;
    height: 131px;
    position: absolute;
    background: transparent url(../images/cloud.png) 0 0 no-repeat;
}
.cloud01 {
    top: 100px;
    left: 300px;
    z-index: 100;
}
.cloud02 {
    top: 240px;
    z-index: 200;
}

現在我們就來讓這些云動起來吧!首先,我們先在keyframes中定義動畫,把它命名為drift。讓這些云動起來,飄過天空。因為我們不希望云在漂移的過程中有任何停頓,所以它們看起來好像完全適合用from、to關鍵幀。所以,我們的keyframes應該這樣寫:

@keyframes drift {
    from {
        transform: translateX(-255px);
    }
    to {
        transform: translateX(1350px);
    }
}

這里的關鍵點是,我選擇了一個離左邊較遠的from值(讓云從我們看不見的地方開始飄動),一個離右邊較遠的to值(讓云飄到我們看不見的地方結束)。為了更好的性能,我使用了translate,而不是在keyframes中不同定位(盡管在這樣一個小例子中,這點異微不足道)。另外,因為我不可能對所有特定的布局都使用translate,所以可以肯定地說,我的動畫不會造成任何布局上的問題或者是無意的覆蓋布局樣式。這個例子比較簡單,所以我們不需要考慮這個問題,但是將動畫樣式和布局樣式分開是一個好習慣。

下一步,我們將為這些云指定同一個動畫,只是設置的屬性略有不同,但是使用的都是相同的keyframes。把動畫的定義以及引用分開,可以很方便地重用動畫。對于第一朵云,我們給它指定這個drift動畫,持續時間是25s,因為云朵都是慢慢飄的~接著把animation-timing-function的值設置為linear,讓它在移動的過程中都保持相同的速度。然后定義我們的animation-iteration-count屬性,這可以讓我們的云朵在天空中一次又一次地飄啊飄啊飄啊~~無限循環。

.cloud01 { animation: drift 25s linear infinite; }

對于第二朵云,我們使用了相同的動畫,但是設置相對復雜一點。我們設置了持續時間為35s,這樣它可以比我們的第一朵云飄得更慢更穩~另外,我們還把這個動畫延遲了10s,把animation-fill-mode設置為backwards。這樣我們的云朵就會在那10s的延遲內先到達我們第一個關鍵幀(from關鍵幀)的位置。

.cloud02 {animation: drift 35s linear 10s infinite backwards;}

如果我們預覽 我們最后的代碼 ,兩朵云的動畫是不一樣的,盡管我們使用了相同的keyframes。

能夠像這樣在多個元素上重用動畫,是CSS一個非常有用的特性。只要讓它們的屬性稍有不同,就可以讓相同的一組關鍵幀變得非常萬能。

使用steps讓雪碧圖動起來

前面提到過steps,現在就來講一下。Steps和我們的另一個animation-timing-function的其它選項表現得非常不一樣,它們有它們自己的怪癖和復雜性。它們通常用于結合雪碧圖來創建幻燈片,或一幀一幀的動畫。如果你想跟著代碼一起來看,可以查看下面示例。

構建網站時經常使用雪碧圖,來作為一張大圖內包括了很多的小圖標或是你在網站上使用的其它的圖片。對于動畫,雪碧圖以類似的方式工作。我們把動畫的每一幀都收進了一張圖片內,把這張圖片作為某個div的背景,然后通過移動背景圖片來創建動畫。steps用于定義在動畫過程中你的背景圖片有多少次停頓。

Steps把動畫的持續時間根據steps的數量分成了相同的若干部分。這每一個steps就像你動畫的幀,不同的是你的動畫將被分成一系列停頓,而不是持續的運動。

對于這個示例,我們將使用一個由我的朋友——動畫師 Scott Benson 繪制的角色步行循環。他在After Effects中把這個短短的步行循環作成系列png格式的圖片導出,然后我把Photoshop中把它們集中到了一張雪碧圖上。(我發現自動雪碧圖生成器對于像這樣的雪碧圖并不好用,所以我使用了Photoshop來制作。)

CSS動畫

在CSS中,我們先給div指定width和height的值,讓它和我們動畫的尺寸相匹配,然后設置背景圖像為我們創建的雪碧圖。

.sprite {
    width: 245px;
    height: 400px;
    display: block;
    background: transparent url(../images/walker.png) 0 0 no-repeat;
    margin: 3em auto;
}

和其它的timing函數一樣,steps使用一組關鍵幀來定義動畫。所以,我們會創建一個keyframes定義,把它命名為walker,然后定義兩個關鍵幀。

@keyframes walker {
    0% {
        background-position: 0 0;
    }
    100% {
        background-position: 0 -4000px;
    }
}

我們的雪碧圖的總高度是4000px,我們在這里使用了一個負值,讓圖片上移。在我們的雪碧圖上移的時候,我們的圖像中下面的關鍵幀就會被顯示出來。通過上面的關鍵幀,我們把圖像從0 0的位置跳到0 -4000px的位置。

我們可以通過刪除0%關鍵幀來簡化這個動畫。如果我們沒有特別定義一個起始關鍵幀(在這里,指0%關鍵幀),之前應用到我們元素的樣式將會被作為起點使用。當我們在.sprite類中指定背景圖像時,我們已經把背景圖像的位置為0 0,所以我們不需要在關鍵幀中重復。我們稍微簡化了的關鍵幀如下,隱含了第一個關鍵幀:

@keyframes walker {
    100% {
        background-position: 0 -4000px;
    }
}

隨著我們的動畫定義完成,我們把它指定給我們的.sprite類來獲得動畫效果。我們將walker動畫指定給類名為.sprite的div,給它1秒的持續時間。(如果你想要動畫播放得快一些或者慢一些,可以調整duration的值。)我們把animation-timing-function設置為steps(10),表示把動畫的持續時間分成10份。10也是雪碧圖中幀的數量,這樣在動畫播放的時候,每一幀都可以看到。

最后同樣重要的是,把animation-iteration-count設置為infinite。這個雪碧圖將會一直循環下去,這樣我們可以看到我們的角色一直一直都在步行。我們給.sprite類添加的屬性如下:

.sprite { animation: walker 1s steps(10) infinite; }

你可以在這里查看 最后的動畫效果

使用animation-play-state來啟動或停止動畫

默認情況下,動畫在它們被分配好的時候就立即開始播放,但是我們可以控制它開始播放的時間。根據我們給它們指定的位置的不同,或者選擇播放暫停的時間,可以得到非常有趣的結果。可以把hover或類似性質的事件作為觸發器,分配動畫或改變動畫的屬性。當然,如果你把JavaScript和CSS動畫結合起來使用,你還可以得到更復雜的交互,可以得到的效果也就更多。不過hover狀態是可以獨立完成CSS各種效果的。比如說,在hover時創建一個比只有過渡更多的懸停動畫效果。

結合前面的那個例子,如果你方便在瀏覽器中查看的話,你可以看看下面的示例:

這個示例是一個帶有文本的標簽,在我們鼠標懸停時會旋轉,可以多留意一些很棒的新東西。我們先來定義能讓我們的標簽動起來的動畫:

@keyframes spin {
    100% {
        transform: rotate(1turn);
    }
}

我們把這個動畫賦給我們的標簽,這是一個div元素,設置類名為.sticker。另外,我們在第二行中把animation-play-state的值設置為paused:

.sticker {
    animation: spin 10s linear infinite;
    animation-play-state: paused;
}

animation-play-state的值有兩個,running或paused。默認值是running,除非你另外設置了。

如果你現在預覽文件,你不會看到任何效果。我們給標簽設置了旋轉,但是又讓它暫停了。為了看到我們努力的成果,我們需要把animation-play-state在某個狀態的時候設置為running,在這個例子中,hover就OK了:

.sticker:hover { animation-play-state: running; }

這樣,在我們的鼠標懸停的時候,標簽就會旋轉了;在我們鼠標離開的時候,它就停止旋轉;然后我們的鼠標再次懸停在上面的時候,它就從上次離開的地方開始接著旋轉。我們不是只在兩種不同的狀態之間直接切換,在每次hover的時候,我們展示的是一大段變化中的一部分。切換animation-play-state的值,比非傳統的hover效果多了一些更多有趣的選項。在一些線性動畫中,它是一個很方便的讓動畫停止的方法,然后在你一切準備好的時候開始播放。

我們完成的CSS代碼如下:

body {
    padding:4em; 
    background: #fcfcfc;
}
.wrap {
    width:200px; 
    margin:auto; 
    position:relative;
}
.msg {
    color: whitesmoke;
    text-align: center;
    font-family: serif;
    font-size: 3.5em;
    width: 200px;
    position: absolute;
    margin: 55px 0 0 2px;
    pointer-events: none;
}
.sticker {
    width: 200px;
    height: 200px;
    position: absolute;
    background: url(../images/sticker.png) top center no-repeat;
    animation: spin 10s linear infinite;
    animation-play-state: paused;
}
.sticker:hover {
    animation-play-state: running;
}
@keyframes spin { 
    100% {transform: rotate(1turn); } 
}

查看最后完整的示例

如果把JavaScript和CSS動畫結合起來使用,就可以做出更強大的交互效果,而且我也希望你能這樣做。你肯定會有興趣去了解在CSS動畫開始、進行和結束的時候,JavaScript中都有哪些動畫事件可以使用。它們超出了我這本指南的內容,但是 Mozilla Developer Connection中有非常精彩的介紹Craig Buckler也會引導你去了解各個不同的瀏覽器命名的差異 (因為,當然,瀏覽器制造商并不同意把它們統一命名)。

多個動畫,一個對象

目前為止,我們討論的都是為單個元素應用單個動畫,但是我們可以為對象添加不止一個動畫,只要我們需要。最常見的方法是動畫一前一后,這樣動畫就可以一個輪著一個播放。巧妙地設置animation-delay屬性的值可以讓我們用純CSS來完成這個效果。

如果是讓兩個動畫同時作用在相同的元素上,可能比較有技術性。但是CSS不能把兩個或者更多keyframes集合組合起來,所以這樣設置的結果通常并不是我們想要的。

為了展示如何把多個動畫組合起來,我們讓一個小獎章從左邊滾入,然后在到達指定的位置前進行縮放。示例如下:

首先我們用keyframes創建兩個動畫。如果這不是在一本書的上下文中,在到達最后的動畫之前會有很多次的調整和預覽。但是我們必須跳過這些發現樂趣的步驟,直接看這兩個關鍵幀動畫:

@keyframes roll-in {
    0% {
        transform: translateX(-200px) rotate(0deg);
    }
    100% {
        transform: translateX(0) rotate(360deg);
    }
}
@keyframes scale-up {
    0% {
        transform: scale(1);
        animation-timing-function: ease-in;
    }
    25% {
        transform: scale(1.15);
        animation-timing-function: ease-out;
    }
    60% {
        transform: scale(0.9);
        animation-timing-function: ease-in;
    }
    100% {
        transform: scale(1);
        animation-timing-function: ease-out;
    }
}

使用第一個keyframes聲明來讓我們的獎章(一個div元素,指定其類名為.mol)從左邊滾入。它只有兩個幀,從左側移動到右側,并加入一些旋轉。很適合使用from和to來定義關鍵幀,但是我喜歡統一使用一種方法。

第二個動畫是讓獎章的大小來回彈動,讓它在結束的位置有彈性地結束動畫。我們甚至可以改變timing函數,來對它的彈動效果進行微調。

我們需要把這兩個動畫設置為是一個接一個播放的,把第二個動畫的延遲時間和第一個動畫的持續時間的值設置相同即可:

.mol {
    animation-name: roll-in, scale-up;
    animation-duration: 1s, 0.75s;
    animation-delay: 0s, 1s;
    animation-timing-function: ease-in, linear;
    animation-iteration-count: 1;
    animation-fill-mode: forwards;
}

把動畫屬性用逗號分隔開,在羅列動畫名字的時候,應該按照相同的順序。在這種情況下,1s、0s和ease-in都是第一個值,與roll-in動畫相關,因為它被命名為第一個,然后第二個值是scale-up。在任何情況下,如果我們只指定了一個值,如animation-iteration-count,它就會被用于兩個動畫。

把第二個動畫的延遲設置為1s,和第一個動畫的持續時間保持一致,這樣就可以在第一個動畫結束的時候馬上執行。我們可以持續添加更多的動畫,然后相應地調整延遲和持續時間,但是在這個例子中,我們只寫了兩個,這兩個回轉動畫最后的CSS如下:

.mol {
    width: 174px;
    height: 174px;
    background: transparent url('../images/mol_badge.png') top center no-repeat;
    position: absolute;
    left: 400px;
    animation-name: roll-in, scale-up;
    animation-duration: 1s, 0.75s;
    animation-delay: 0s, 1s;
    animation-timing-function: ease-in, linear;
    animation-iteration-count: 1;
    animation-fill-mode: forwards;
}
@keyframes roll-in {
    0% {
        transform: translateX(-200px) rotate(0deg);
    }
    100% {
        transform: translateX(0px) rotate(360deg);
    }
}
@keyframes scale-up {
    0% {
        transform: scale(1);
        animation-timing-function: ease-in;
    }
    25% {
        transform: scale(1.15);
        animation-timing-function: ease-out;
    }
    60% {
        transform: scale(0.9);
        animation-timing-function: ease-in;
    }
    100% { 
        transform: scale(1); 
    }
}

性能及瀏覽器支持情況

同往常一樣,當我們在使用一些大家認為是很新的東西時,需要確保廣泛的跨瀏覽器的良好體驗。確定瀏覽器支持之后,針對缺乏支持的情況做好良好的計劃,這對你創建動畫來說是一個良好的開始。

CSS動畫 vs JavaScript動畫:誰更厲害?

在測試的時候,如 這篇發表在歐朋開發者blog上的文章 ,都表明了CSS動畫確實渲染得更快,而且比等效的JavaScript占用更少的內存。這是由于渲染CSS動畫更多的是由瀏覽器內部完成的,這提高了很多效率。

CSS動畫還受益于硬件加速性能的提高,尤其是Paul Irish的 這篇文章中 展示的使用transform的demo。

這些demo,以及那些類似的,都展示了CSS在很多情況下,相比JavaScript有更多提高性能的潛力。你的情況可能因你的目標瀏覽器而有不同,這正是完成動畫任務的關鍵。但總體而言,CSS動畫是一個有力的競爭者。我想我們可以期待一下這些潛在的性能優勢能隨著瀏覽器的發展而有所提高。

目前的瀏覽器支持情況

caniuse.com 網站提供了關于瀏覽器支持最詳細的信息。它提供了一個方便查看的圖表,顯示了哪些瀏覽器版本支持動畫,以及哪些前綴是必須添加的。當你對某個CSS屬性的當前或過去的瀏覽器支持感興趣的話,可以隨時查看這個資源。

如果你要創建一個基于安卓受眾的東西,你需要關注CSS動畫規范在安卓瀏覽器3.0及以下版本中的支持情況。部分支持比不支持更麻煩,因為我們并不清楚到底哪部分是支持的哪部分是不支持的。Daniel Eden對于比較舊的安卓瀏覽器 提供了一些有用的建議

不管怎樣,只要你想要測試某特定的設備或瀏覽器版本對項目的支持情況的話,最好先進行全面的測試并確保在那個環境中是可以運行的,以確保它能支持,而且有良好的性能。

但是當動畫在不同的瀏覽器中渲染時,仍然有一些比較小的古怪差異,沒有辦法得到完全的支持。我希望這種bug會隨著動畫慢慢成熟的支持支持慢慢地較少。但是目前,你可能還是偶爾會碰到這種情況,因為CSS動畫還是一塊比較新的內容。

現在可以使用CSS動畫了嗎?

CSS動畫有很廣泛的支持,現在我們還是希望在現代瀏覽器的圈子中,每個人都能看到我們做的web。可惜的是CSS動畫還沒有完全得到目前使用中的所有瀏覽器的支持,絕對不行。在生產工作中使用CSS動畫需要考慮缺乏瀏覽器支持的情況,因為除非你的受眾非常小眾,不過你也只能偶爾碰到一兩個。所以制作CSS動畫大部分的工作量在于,你需要根據你使用CSS動畫的具體情況,為它準備好fallback。

用于細節設計的動畫

只為非必要的效果和細節設計使用CSS動畫的時候,一個什么都不做的方法往往會導致一個可接受的fallback。瀏覽器會忽略它們無法解析的CSS,所以如果你提前計劃,確保你的網站在不能加載動畫的時候,看起來不會非常糟糕,那就ok了。

一定要確保添加的動畫只是作為額外的非必要的效果或細節,而不是任何影響到布局或重要任務的關鍵。我發現使用transforms變換和其它不太可能用于的屬性,可以幫忙將布局和動畫樣式分離,所以關注一下那些應用了動畫的元素,在沒有動畫的情況下展示如何。在你醞釀那些你可能并不真正需要的庫和fallback之前,測試一下那些do-nothing的方法究竟是干嘛的。

在文章的前面,有一個云朵無限循環的動畫示例。如果你有在不支持動畫屬性的瀏覽器中查看過該示例,你會看到兩朵停在空中的云。我把它們移出了屏幕,因為作為動畫的一部分,它們應該是可以在屏幕上飄進飄出的。相比看到空曠的啥都沒有的天空,看到兩朵靜態的云更好一些,在這種情況下,這也是完全可以接受的結果。

基本動畫

在處理涉及重要效果或包含重要內容的動畫時,沒有做任何處理和測試是絕對不行的。在這種情況下,你有兩個選擇:實現fallback;使用比CSS有更廣泛的瀏覽器支持的東西來創建動畫。

為了保持你的理智以及和維持同事之間的友誼,注意避免重復工作(例如,用CSS和JavaScript編寫了同樣的動畫),除非你必須這樣做或者是為了得到什么顯著的效益。對同一個東西創建兩個版本的代碼并不是一個好注意,尤其是那些你需要經常維護的項目。在這種項目中,如果你遇到的是需要重要的舊瀏覽器支持、包含重要內容的動畫,使用JavaScript來解決是最好的選擇。

比如說,在CSS中編寫一個幻燈片過渡的動畫,JavaScript的fallback可以適用于更多的情況,尤其是那些不怎么需要改變的內容。但是,對于某個網站上的一個經常更新的功能模塊,為每一個特別的動畫都寫一個CSS和JavaScript版本的動畫,這絕對是在浪費大家的時間。

試驗性和娛樂型的項目有時候可能從一些fallback中獲益,可以有更多的受眾。降低滿意度是一個挑戰,像 Modernizr 這樣的工具可以為你提供瀏覽器支持的情況,以及你需要做的調整。它可能會為你提供一個JavaScript版本的代碼,或是比較舊的版本,或其它可以在你的項目中運行的版本。

要重視fallback,不要告訴用戶說他們的瀏覽器不是我們希望他們使用的,或任何其它能幫助你吸引用戶的方法。如果你創建的動畫確實是無法以一個合理的方式在用戶的瀏覽器或設備中運行,就盡力為他們提供那些最重要的內容,不要把它們隱藏了就ok。

如果你發現你不能在你的生產作業中使用CSS動畫,不要擔心。有很多像 CodePenJS Bin 這樣的網站,為你提供一個實踐的平臺和社區。

結束語

我希望你喜歡這段學習CSS動畫的旅程。這只是介紹了CSS動畫可以完成什么,以及把運動做成漂亮的設計細節在網站上展示,這僅僅是個開始。把這些實例和資源作為你實踐的開端,試著在自己的項目中使用CSS動畫吧~~O(∩_∩)O

本文根據@Val Head的《 CSS Animations 》電子書所整理,如果感興趣,可以購買此電子書: http://www.fivesimplesteps.com/products/css-animations

 本文由用戶 jopen 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!