過渡與動畫 - 緩動效果&基於貝塞爾曲線的調速函數


難題

給過渡和動畫加上緩動效果是一種常見的手法(比如具有回彈效果的過渡過程)是一種流行的表現手法,可以讓界面顯得更加生動和真實:在現實世界中,物體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-in-out

linear

從上面四個圖中,我們很直觀的看出,ease-outease-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);
}

codepen中查看效果

See the Pen css-animation-easing by okaychen (@okaychen) on CodePen.

我們可以借助cubic-bezier.com的圖形化工具,進行反復嘗試和優化,從而進一步改寫這個回彈動畫.

最后

經過以上這些知識的學習儲備和練習,相信我們已經可以做出很棒的彈跳動畫了.
我們在文章開始放了一個小球彈跳的gif圖效果,那么就讓我們真真正正的動手來寫一下吧!

codepen中查看效果

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%,我們把控制錨點向上移,

cubic-bezier(.25,.1,.3,1.5)

這個自定義調速函數在垂直坐標上已經超出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);
}

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);
}

codepen中查看效果

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;
}

參考資料


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM