有一天,Harry Roberts有一段有關於他網站上的代碼在twitter求教,如果有可能,在某些方面得到改善。Harry Roberts做的是使用keyframes的carousel動畫,所以說使用一些數學計算是有可能得到相應改善。
“Why do we have to learn algebra, Miss? We’re never going to use it…” —Everyone in my maths class bit.ly/UaM2wf
有什麼好主意?
據我所見,Harry Roberts在他的首頁上使用了一個carousel動畫。可以使用CSS,為什麼要使用JavaScript呢,你說對吧。所以他使用CSS動畫來製作Carousel動畫。這聽起來似乎是一個很好的想法,但直到你需要計算keyframe時,你就不會這麼認為。
下面是有關於Harry Roberts對Carousel動畫做的描述。
滾動Carousel(寫入程式碼,有點讓人覺得噁心!)和使用一個小動畫(這個動畫運動是模糊/動畫)。整個Carousel動畫需要計算過渡和延遲時間(比如100%):
n * x + (n - 1) * y = 100
其中n是指投影片的數量,x是動畫在靜態時的百分比,y是動畫在運動時的百分比。
這個動畫有5張投影片,根據上面描述,可以得到:
5 * x + (5 - 1) * y = 100
如果知道x和n的值,我們就可以計算出y的值:
(100 - (n * x)) / (n - 1) = y
假設x選擇的值為17.5(也就是動畫中17.5%位置),我們知道n=5,這樣可以計算出y=3.125:
(100 - (5 * 17.5)) / (5 -1) = 3.125
幀從17.5%過渡到3.125%等等,直到100%。
如果我們把x設定為15,那麼可以計算出y=6.25:
(100 - (5 * 15)) / (5 -1) = 6.25
如果y運動是從"zero-or-below",這也意味著,我們選擇了x值太大。
注意,我們還有一個中間點,也就是在中間過渡幀,這個數可以這們得來:
(a * x) + ((a - 1) * y) + (y / 2)
問題是在n個幀哪一個幀才是中間幀。一般情況是3和4之間:
(3 * 17.5) + ((3 - 1) * 3.125)+ (3.125 / 2) = 60.3125
這的確很混亂。
最終結果是:
@keyframes carousel { 0% { transform: translate3d(0, 0, 0); filter: blur(0); } 17.5% { transform: translate3d(0, 0, 0); filter: blur(0); } 19.0625% { filter: blur(2px); } 20.625% { transform: translate3d(-20%, 0, 0); filter: blur(0); } 38.125% { transform: translate3d(-20%, 0, 0); filter: blur(0); } 39.6875% { filter: blur(2px); } 41.25% { transform: translate3d(-40%, 0, 0); filter: blur(0); } 58.75% { transform: translate3d(-40%, 0, 0); filter: blur(0); } 60.3125% { filter: blur(2px); } 61.875% { transform: translate3d(-60%, 0, 0); filter: blur(0); } 79.375% { transform: translate3d(-60%, 0, 0); filter: blur(0); } 80.9375% { filter: blur(2px); } 82.5% { transform: translate3d(-80%, 0, 0); filter: blur(0); } 100% { transform: translate3d(-80%, 0, 0); filter: blur(0); }}
清潔CSS動畫
之前我考慮過Sass,減少動畫代碼。從上面的代碼塊中,我們不難發現,有一些主要畫面格是相同的。這樣我們可以把代碼清理清理,讓整個動畫變得更簡單:
@keyframes carousel { 0%, 17.5% { transform: translate3d(0, 0, 0); filter: blur(0); } 19.0625% { filter: blur(2px); } 20.625%, 38.125% { transform: translate3d(-20%, 0, 0); filter: blur(0); } 39.6875% { filter: blur(2px); } 41.25%, 58.75% { transform: translate3d(-40%, 0, 0); filter: blur(0); } 60.3125% { filter: blur(2px); } 61.875%, 79.375% { transform: translate3d(-60%, 0, 0); filter: blur(0); } 80.9375% { filter: blur(2px); } 82.5%, 100% { transform: translate3d(-80%, 0, 0); filter: blur(0); }}
Sass怎麼玩
主要畫面格通常可以得到最佳化。因為@keyframes中迴圈的幀通常很容易產生重複的動畫。我們可以試試。
首先,為了保持一致性,我們照常使用Harry的變數名:n,x和y。別忘了它們的含義:
$n是動畫中的幀數
$x是動畫幀的百分比,邏輯上是小於或等於100% / $n
$y是動畫中每一幀的比例
$n: 5;
$x: 17.5%;
$y: (100% - $n * $x) / ($n - 1);
現在我們在@keyframes中做一個迴圈:
@keyframes carousel {
@for $i from 0 to $n { // 0, 1, 2, 3, 4
//Sass動畫
}
}
在迴圈中,我們使用Harry的公式計算每一對相同的主要畫面格(比如41.25%和58.75%):
$current-frame: ($i * $x) + ($i * $y);
$next-frame: (($i + 1) * $x) + ($i + $y);
特別聲明:小括弧完全可以略去,這裡只是用它們來讓代碼保持更乾淨。
現在,我們使用這些變數來產生主要畫面格,別忘了在裡面插入正確的CSS(有關於更多的Sass內容,可以點擊這裡):
#{$current-frame, $next-frame} {
transform: translateX($i * -100% / $frames);
filter: blur(0);
}
非常簡單,不是嗎?運行第一個迴圈之後,編譯出CSS代碼:
0%, 17.5% {
transform: translate3d(0%, 0, 0);
filter: blur(0);
}
剩下的就是Harry說的,在中間幀中添加blur()效果。我們可以使用他的公式計算出:
$halfway-frame: $i * ($x / 1%) + ($i - 1) * $y + ($y / 2);
#{$halfway-frame} {
filter: blur(2px);
}
這裡出錯了。
無效的CSS:我們預期的主要畫面格,比如10%,可計算出來的是-1.5625%。
正如你所看到,我們最終得到了一個負的主要畫面格。這在CSS規範中是禁止的,就算在Sass中也認為這是一個錯誤,所以我們需要確保這種事情不會發生。實際上,只有當$i為0才會發生。因此,有一個簡單的方法來阻止這樣的事情發生,根據規則輸出$i的值:
@if $i > 0 {
#{$halfway-frame} {
filter: blur(2px);
}
}
錯誤解決了,這是最後的代碼:
$n: 5;$x: 17.5%;$y: (100% - $n * $x) / ($n - 1);@keyframes carousel { @for $i from 0 to $n { $current-frame: ($i * $x) + ($i * $y); $next-frame: (($i + 1) * $x) + ($i + $y); #{$current-frame, $next-frame} { transform: translate3d($i * -100% / $frames, 0, 0); } $halfway-frame: $i * ($x / 1%) + ($i - 1) * $y + ($y / 2); @if $i > 0 { #{$halfway-frame} { filter: blur(2px); } } }}
將這一切放在一個mixin中
到目前為止,這一切都好了?Harry的動畫代碼工作的很好,所以他沒有再次計算。如果他想把投影片從5張變成4張,或者說希望動畫時間更快或更慢,這一切以得重新開始。
目前我們的變數會汙染全範圍。同樣的,如果在其他地方需要Carousel動畫,需要重新設定其他變數名,然後將動畫內容複寫到新的動畫中。這絕不是理想的做法。
這就是我們使用mixin的理由。為了讓事情更易理解,我們使用實際的變數名來替代這些字母:
$n換成$frames
$x換成$static
$y換成$animating
另外,為了確保它輸出不同的動,可以利用mixin多次調用不同的參數。所以我們需要再添加一個參數:動畫的名字。
@mixin carousel-animation($frames, $static, $name: 'carousel') { $animating: (100% - $frames * $static) / ($frames - 1); // Moar Sass}
由於mixin可以在不同的地方調用,為了阻止選取器包括它,可以使用@at-root確保動畫輸出是在根層級:
@mixin carousel-animation($frames, $static, $name: 'carousel') { $animating: (100% - $frames * $static) / ($frames - 1); @at-root { @keyframes #{$name} { // Animation logic here } }}
在實際中,可以這樣調用:
@include carousel-animation( $frames: 5, $static: 17.5%);
編譯出來的CSS:
@keyframes carousel { 0%, 17.5% { transform: translateX(0%); filter: blur(0); } 19.0625% { filter: blur(2px); } 20.625%, 38.125% { transform: translateX(-20%); filter: blur(0); } 39.6875% { filter: blur(2px); } 41.25%, 58.75% { transform: translateX(-40%); filter: blur(0); } 60.3125% { filter: blur(2px); } 61.875%, 79.375% { transform: translateX(-60%); filter: blur(0); } 80.9375% { filter: blur(2px); } 82.5%, 100% { transform: translateX(-80%); filter: blur(0); }}
任務完成了。如果我們想在聯絡頁面使用另一個動,可以這樣使用:
@include carousel-animation( $name: 'carousel-contact', $frames: 3, $static: 20%);
是不是十分完美。(^_^)。
總結
這想當的完美。而Harry當初的代碼容易閱讀,但真的不太容易維護。使用Sass的自動化計算和迴圈特性,可以變得更佳方便。當然,它也使代碼變得更為複雜,但這也使得它易於維護和更新。使用起來也非常的簡單。
Sass
// ----// Sass (v3.4.0.rc.1)// Compass (v1.0.0.alpha.20)// ----/** * Generate the carousel animation * based on the number of frames * and the pourcentage of a frame spent static * * @param {Number} $n - number of frames * @param {Number} $x - percentage of the animation spent static per frame * @param {String} $animation-name ('carousel') - animation name */ @mixin carousel-animation($frames, $static, $animation-name: 'carousel') { // Make `$static` a percentage in case it's unitless @if unitless($static) { $static: percentage($static); } // Compute the percentage of animation spent animating for each frame $animating: (100% - $frames * $static) / ($frames - 1); // Output the animation at root level // to make sure it doesn't crash if called in a selector @at-root { // Create an animation @keyframes #{$animation-name} { // Loop over the frames @for $i from 0 to $frames { // Compute keyframes $current-frame: $i * $static + $i * $animating; $next-frame: ($i + 1) * $static + $i * $animating; $halfway-frame: $i * $static / 1% + ($i - 1) * $animating + $animating / 2; // Output halfway styles for blur // Avoid a negative keyframes by making sure `$i` is at least `1` @if $i > 0 { #{$halfway-frame} { filter: blur(2px); } } // Output styles for each frame #{$current-frame, $next-frame} { transform: translateX($i * -100% / $frames); filter: blur(0); } } } }}// Generate animation@include carousel-animation(5, 17.5%);
CSS
/** * Generate the carousel animation * based on the number of frames * and the pourcentage of a frame spent static * * @param {Number} $n - number of frames * @param {Number} $x - percentage of the animation spent static per frame * @param {String} $animation-name ('carousel') - animation name */@keyframes carousel { 0%, 17.5% { transform: translateX(0%); filter: blur(0); } 19.0625% { filter: blur(2px); } 20.625%, 38.125% { transform: translateX(-20%); filter: blur(0); } 39.6875% { filter: blur(2px); } 41.25%, 58.75% { transform: translateX(-40%); filter: blur(0); } 60.3125% { filter: blur(2px); } 61.875%, 79.375% { transform: translateX(-60%); filter: blur(0); } 80.9375% { filter: blur(2px); } 82.5%, 100% { transform: translateX(-80%); filter: blur(0); }}