分類: 網頁開發

使用 CSS Animation 製作網頁上的動畫(只要 CSS3,不用 JavaScript!)

這裡介紹如何使用 CSS3 的 animation 功能在網頁上製作動畫,而且不需要使用到 JavaScript。

CSS3 所提供的動畫(animation)功能可以讓網頁元素的 CSS 樣式(style)從一個設定轉換到另外一個,藉著這樣的方式產生動畫的效果。CSS 的再設定動畫時包含兩個部份,一個是用來設定 CSS 動畫的樣式,另外一個則是指定動畫開始、結束或中途路徑的關鍵影格(keyframes)設定。


使用 CSS 產生的動畫跟傳統上以 JavaScript 製作的動畫比較起來,有一些優點:
  1. 使用 CSS 會比 JavaScript 容易很多,對於簡單的動畫而言,不需要學習 JavaScript 即可馬上製作出來。
  2. 使用 CSS 的動畫比較節省資源,甚至在有一定負載量的系統中也可以很平順的執行,但是 JavaScript 的動畫就很容易因為效能問題而跑不起來(除非經過很精心的設計),而且一般瀏覽器的繪圖引擎(rendering engine)還可以透過 frame-skipping 這類的方式,提升整體的效能。
  3. CSS 把動畫播放的控制權交給瀏覽器控制,這樣可以讓瀏覽器進行更多的最佳化動作,例如當一個動畫放在背景的籤頁時,瀏覽器就可以減低這個動畫的更新頻率。

設定 CSS 動畫

要在網頁上製作 CSS 動畫,可以使用 animation 相關的 CSS 屬性,它可以讓你設定動畫的播放速度、時間與其他跟動畫播放有關的屬性,而動畫真正的外觀則是使用 @keyframes at-rule 來設定(請看隨後的說明)。

animation 相關屬性的部份,可以使用的 CSS 屬性有:

CSS 屬性 說明
animation-delay 設定網頁元素在被載入之後到開始播放動畫之間的等待時間。
animation-direction 設定網頁元素在動畫播放完之後,是否要以相反方向的方式播放,或是從頭開始以原來的方向重複播放。
animation-duration 設定整個動畫播放一次的時間長度。
animation-iteration-count 設定動畫播放的次數,若要不斷重複播放,則可設為 infinite
animation-name 設定 @keyframes at-rule 所使用的動畫名稱。
animation-play-state 這個屬性可用來暫停或繼續動畫播放。
animation-timing-function 透過加速曲線(acceleration curves),設定動畫播放的速度。
animation-fill-mode 設定動畫元素在播放前與播放後,如何套用 CSS 的樣式。

使用 @keyframes 設定動畫關鍵影格(keyframes)

在使用 animation 相關的 CSS 屬性定義完動畫的速度與時間之後,接著還要再設定動畫實際要顯現的樣子,這個部份則是使用 @keyframes at-rule 來設定,每一個關鍵影格描述了網頁元素在動畫中的某個時間點應該呈現的樣子。

由於實際的動畫播放時間在 CSS 樣式中已經定義好了,在關鍵影格的部份則是使用百分比來指定動畫在每個時間點呈現的方式,0% 代表動畫一開始播放的起始點,而 100% 則代表動畫的結尾,由於這兩個時間點很常使用,所以你也可以使用 fromto 來分別代表 0%100%。如果沒有指定動畫的開始點(from)或結束點(to),那麼瀏覽器就會自己計算網頁元素在動畫開始或結束時的各種屬性(大概是使用外差的方式吧)。

除了 0%100% 之外,你也可以再另外加上任何比例的關鍵影格,設定整個動畫的播放方式。

範例

接下來我們以實際的範例讓大家了解如何以 CSS 製作網頁動畫。

滑動的文字標題

第一個例子是讓一行文字標題從右方滑進來,下面這些是實作的程式碼。

h1 {
  animation-duration: 3s;
  animation-name: slidein;
}

@keyframes slidein {
  from {
    padding-left: 100%;
    width: 300%;
  }

  to {
    padding-left: 0%;
    width: 100%;
  }
}

這裡使用 animation-duration 這個屬性指定 <h1> 的文字標題執行歷時 3 秒的動畫,animation-name 指定這個動畫名稱為 slidein,而 @keyframes at-rule 則定義 slidein 這個動畫的關鍵影格。

這裡我們是為了讓大家容易了解 CSS 動畫的用法,所以才盡量讓程式碼保持簡潔,如果你希望讓一些不支援 CSS 動畫的瀏覽器可以顯示一些其他的 CSS 效果,也可以在這裡加入一些其他的 CSS 屬性。

動畫中的關鍵影格都是使用 @keyframes at-rule 來設定,在這個例子中只指定了兩個關鍵影格,一個是動畫的起始點(from),在這個時間點設定網頁元素的 padding-left100%,並且設定元素的寬度 width300%,這樣可以讓這個網頁元素在動畫一開始的時候完全隱藏在整個畫面的右邊。

第二個關鍵影格是指定動畫的結束點(to),在這個時間點上 padding-left 設定為 0%,而 width 設定為 100%,這樣就可以讓文字標題在動畫結束時滑到它該被放置的位置。

這裡的 CSS 屬性都沒有加上任何的 prefix,如果是 WebKit 為基礎的瀏覽器(Chrome 與 Safari 等)或其他比較舊版的瀏覽器都要加上 prefix 才能正常運作,例如上面的例子如果要讓大部份的瀏覽器都可以運作,就要加上 -webkit--moz- 這兩個 prefix:

h1 {
  -webkit-animation-duration: 3s;
  -moz-animation-duration: 3s;
  -webkit-animation-name: slidein;
  -moz-animation-name: slidein;
}

@-webkit-keyframes slidein {
  from {
    padding-left: 100%;
    width: 300%;
  }

  to {
    padding-left: 0%;
    width: 100%;
  }
}

@-moz-keyframes slidein {
  from {
    padding-left: 100%;
    width: 300%;
  }

  to {
    padding-left: 0%;
    width: 100%;
  }
}

雖然這樣程式碼顯得很冗長,但是為瀏覽器的相容性也只能這樣,在接下來的 CSS 程式碼中都有這樣的狀況,以下就不再贅述,請大家自己注意一下。

下面這個是實際的示範,請把滑鼠移到文字上,動畫就會開始播放。

滑動的文字標題

增加動畫關鍵影格

接著我們在上面的例子中再加入一個關鍵影格,讓這個文字標題在滑動時,字體大小會跟著慢慢變大再縮小成原來的樣子:

75% {
  font-size: 300%;
  padding-left: 25%;
  width: 150%;
}

這段程式碼是設定在整段動畫播放到 75% 的時間點上,文字標題的字型大小 font-size 應該變為 300%padding-left 應該變為 25%,寬度 width 則為 25%

下面這個是實際的示範,請把滑鼠移到文字上,動畫就會開始播放。

滑動的文字標題

重複播放動畫

若要讓動畫重複播放,只要加入 animation-iteration-count 這個屬性並指定重複的次數即可。這裡我們將重複次數指定為 infinite,讓動畫一直持續重複播放。

h1 {
  animation-duration: 3s;
  animation-name: slidein;
  animation-iteration-count: infinite;
}

下面這個是實際的示範,請把滑鼠移到文字上,動畫就會開始播放。

滑動的文字標題

讓文字標題來回滑動

如果只是讓動畫重複播放,當一段動畫播放結束後再重新播放時,就會發生文字突然從最左邊(動畫結尾)跳到最右邊(動畫開頭),這樣其實感覺不是很自然。

如果想讓整個文字標題動畫看起來可以比較平順的來回滑動,可以將 animation-direction 這個屬性設定為 alternate

h1 {
  animation-duration: 3s;
  animation-name: slidein;
  animation-iteration-count: infinite;
  animation-direction: alternate;
}

下面這個是實際的示範,請把滑鼠移到文字上,動畫就會開始播放。

滑動的文字標題

使用動畫事件(events)

動畫的事件(events)提供網頁設計者更多的操控方式與動畫的相關資訊,這類的事件都是以 AnimationEvent 物件來表示,它可以用來偵測動畫的開始、結束或重新播放的時間點,每個事件中都紀錄該事件發生的時間與產生該事件的動畫名稱。

接下來我們將修改上面的滑動文字標題範例,讓它輸出一些動畫播放的資訊,藉此了解整個動畫的運作過程。

加入動畫的事件傾聽者(event listeners)

這裡我們使用 JavaScript 來接收動畫所產生的事件,下面這個 setup() 函數內容就是設定動畫的事件傾聽者(event listeners),這個函數通常都是在網頁一開始載入時就被呼叫,作為初始化的一部分。

function setup() {
  var e = document.getElementById("watchme");
  e.addEventListener("animationstart", listener, false);
  e.addEventListener("animationend", listener, false);
  e.addEventListener("animationiteration", listener, false);

  var e = document.getElementById("watchme");
  e.className = "slidein";
}

這裡設定了三個事件的傾聽者,分別為 animationstart(動畫開始播放)、animationend(動畫結束) 與 animationiteration(動畫重新播放)。這段程式碼是非常標準的寫法,如果想更了解它是如何運作的,可以參考 element.addEventListener() 的說明文件。

在這個 setup() 函數的最後將這個網頁元素的 className 設定為 slidein,這個動作會讓動畫開始播放。

由於動畫一開始播放的時候就會產生 animationstart 事件了,在這個範例中如果讓動畫太早播放,JavaScript 的程式就會來不及初始化,所以這裡我們使用這樣的方式讓動畫開始的時間放在 JavaScript 程式的最後,確保動畫開始播放時,所有的準備工作都已經完成了。

由於每一個瀏覽器對於這些事件的命名都不太一樣,這裡我們是使用 W3C 的標準,以下是各種瀏覽器的事件命名方式:

W3C 標準 animationstart animationiteration animationend
Firefox animationstart animationiteration animationend
webkit webkitAnimationStart webkitAnimationIteration webkitAnimationEnd
Opera oanimationstart oanimationiteration oanimationend
IE10 MSAnimationStart MSAnimationIteration MSAnimationEnd

所以在指定事件傾聽者時,如果想要涵蓋各種不同的瀏覽器,就要把這些狀況都考慮進去,以下是一個簡單的小函數,可以一次加入所有瀏覽器需要的事件傾聽者:

var pfx = ["webkit", "moz", "MS", "o", ""];
function PrefixedEvent(element, type, callback) {
  for (var p = 0; p < pfx.length; p++) {
    if (!pfx[p]) type = type.toLowerCase();
    element.addEventListener(pfx[p]+type, callback, false);
  }
}

// animation listener events
PrefixedEvent(e, "AnimationStart", listener);
PrefixedEvent(e, "AnimationIteration", listener);
PrefixedEvent(e, "AnimationEnd", listener);

接收事件

上面的設定中,我們將所有的事件都遞送給 listener() 這個函數,而這個函數定義如下:

function listener(e) {
  var l = document.createElement("li");
  switch(e.type) {
    case "animationstart":
      l.innerHTML = "Started: elapsed time is " + e.elapsedTime;
      break;
    case "animationend":
      l.innerHTML = "Ended: elapsed time is " + e.elapsedTime;
      break;
    case "animationiteration":
      l.innerHTML = "New loop started at time " + e.elapsedTime;
      break;
  }
  document.getElementById("output").appendChild(l);
}

這段程式碼也很簡單,他做的事情就是檢查 event.type,判斷事件的種類,然後將輸出放到一個 idoutput<ul> 中。

這裡如果也要考慮不同瀏覽器的狀況的話,可以把判斷式改寫成這樣的形式:

if (e.type.toLowerCase().indexOf("animationend") >= 0) {
  // ...
}

這樣就可以處理各種瀏覽器所產生的事件。

HTML 網頁程式碼

這裡的 HTML 網頁程式碼主要包含會滑動文字標題與輸出用的 <ul>

<body onload="setup()">
  <h1 id="watchme">Watch me move</h1>
  <ul id="output">
  </ul>
</body>

下面這個是實際的示範,請把滑鼠移到文字上,動畫就會開始播放。

滑動的文字標題

    (function(){ var e = document.getElementById("moving-text5"); e.addEventListener("animationstart", listener, false); e.addEventListener("animationend", listener, false); e.addEventListener("animationiteration", listener, false); e.addEventListener("webkitAnimationStart", listener, false); e.addEventListener("webkitAnimationEnd", listener, false); e.addEventListener("webkitAnimationIteration", listener, false); e.addEventListener("MSAnimationStart", listener, false); e.addEventListener("MSAnimationEnd", listener, false); e.addEventListener("MSAnimationIteration", listener, false); e.className = "slidein5"; })();
    這裡我們放在網頁上的程式碼都儘量以比較簡單的方式撰寫,方便大家理解其中的重點,而在這個網頁中實際示範部分,因為要把一些 JavaScript 與 CSS 等程式碼內崁在 Blogger 部落格的文章中,所以有經過一些小修改,不過大致上的概念都是一樣的。如果你對於這樣內崁的方式有興趣,當然也可以直接查看這個網頁的原始碼,比較看看這些程式碼跟網頁上的差異。

    參考資料:Mozilla Developer Network貓箱unmatchedstylesitepoint

    G. T. Wang

    個人使用 Linux 經驗長達十餘年,樂於分享各種自由軟體技術與實作文章。

    Share
    Published by
    G. T. Wang
    標籤: CSS

    Recent Posts

    光陽 KYMCO GP 125 機車接電發動、更換電瓶記錄

    本篇記錄我的光陽 KYMCO ...

    2 年 ago

    [開箱] YubiKey 5C NFC 實體金鑰

    本篇是 YubiKey 5C ...

    2 年 ago

    [DIY] 自製竹火把

    本篇記錄我拿竹子加上過期的苦茶...

    3 年 ago