精妙的配合!文字輪播與圖片輪播?CSS 不在話下

語言: CN / TW / HK

今天,分享一個實際業務中能夠用得上的動畫技巧。

巧用逐幀動畫,配合補間動畫實現一個無限迴圈的輪播效果,像是這樣:

看到上述示意圖,有同學不禁會發問,這不是個非常簡單的位移動畫麼?

我們來簡單分析分析,從表面上看,確實好像只有元素的 transform: translate() 在位移, 但是注意 ,這裡有兩個難點:

  1. 這是個無限輪播的效果,我們的動畫需要支援任意多個元素的無限輪播切換

  2. 因為是輪播,所以,執行到最後一個的時候,需要動畫切到第一個元素

到這裡,你可以暫停思考一下,如果有 20 個元素,需要進行類似的無限輪播播報,使用 CSS 實現,你會怎麼去做呢?

逐幀動畫控制整體切換

首先,我需要利用到逐幀動畫效果,也被稱為 步驟緩動函式 ,利用的是 animation-timing-function 中,的 steps,語法如下:

{
/* Keyword values */
animation-timing-function: step-start;
animation-timing-function: step-end;
/* Function values */
animation-timing-function: steps(6, start)
animation-timing-function: steps(4, end);
}

如果你對 steps 的語法還不是特別瞭解,強烈建議你先看看我的這篇文章 -- 深入淺出 CSS 動畫 [1] ,它對理解本文起著至關重要的作用。

好的,還是文章以開頭的例子,假設我們存在這樣 HTML 結構:

<div class="g-container">
<ul>
<li>Lorem ipsum 1111111</li>
<li>Lorem ipsum 2222222</li>
<li>Lorem ipsum 3333333</li>
<li>Lorem ipsum 4444444</li>
<li>Lorem ipsum 5555555</li>
<li>Lorem ipsum 6666666</li>
</ul>
</div>

首先,我們實現這樣一個簡單的佈局:

在這裡,要實現輪播效果,並且是任意個數,我們可以藉助 animation-timing-function: steps()

:root {
// 輪播的個數
--s: 6;
// 單個 li 容器的高度
--h: 36;
// 單次動畫的時長
--speed: 1.5s;
}
.g-container {
width: 300px;
height: calc(var(--h) * 1px);
}
ul {
display: flex;
flex-direction: column;
animation: move calc(var(--speed) * var(--s)) steps(var(--s)) infinite;
}
ul li {
width: 100%;
}
@keyframes move {
0% {
transform: translate(0, 0);
}
100% {
transform: translate(0, calc(var(--s) * var(--h) * -1px));
}
}

別看到上述有幾個 CSS 變數就慌了,其實很好理解:

  1. calc(var(--speed) * var(--s)) :單次動畫的耗時 * 輪播的個數,也就是總動畫時長
  2. steps(var(--s)) 就是逐幀動畫的幀數,這裡也就是 steps(6) ,很好理解
  3. calc(var(--s) * var(--h) * -1px)) 單個 li 容器的高度 * 輪播的個數,其實就是 ul 的總體高度,用於設定逐幀動畫的終點值

上述的效果,實際如下:

如果給容器新增上 overflow: hidden ,就是這樣的效果:

這樣,我們就得到了整體的結構,至少,整個效果是迴圈的。

但是由於只是逐幀動畫,所以只能看到切換,但是每一幀之間,沒有過渡動畫效果。所以,接下來,我們還得引入補間動畫。

利用補間動畫實現兩組資料間的切換

我們需要利用補間動畫,實現動態的切換效果。

這一步,其實也非常簡單,我們要做的,就是將一組資料,利用 transform ,從狀態 A 位移到 狀態 B。

單獨拿出一個來演示的話,大致的程式碼如下:

<div class="g-container">
<ul style="--s: 6">
<li>Lorem ipsum 1111111</li>
<li>Lorem ipsum 2222222</li>
<li>Lorem ipsum 3333333</li>
<li>Lorem ipsum 4444444</li>
<li>Lorem ipsum 5555555</li>
<li>Lorem ipsum 6666666</li>
</ul>
</div>
:root {
--h: 36;
--speed: 1.2s;
}
ul li {
height: 36px;
animation: liMove calc(var(--speed)) infinite;
}
@keyframes liMove {
0% {
transform: translate(0, 0);
}
80%,
100% {
transform: translate(0, -36px);
}
}

非常簡單的一個動畫:

bgg1

基於上述效果,我們如果把一開始提到的 逐幀動畫 和這裡這個 補間動畫 結合一下,ul 的整體移動,和 li 的 單個移動疊在在一起:

:root {
// 輪播的個數
--s: 6;
// 單個 li 容器的高度
--h: 36;
// 單次動畫的時長
--speed: 1.5s;
}
.g-container {
width: 300px;
height: calc(var(--h) * 1px);
}
ul {
display: flex;
flex-direction: column;
animation: move calc(var(--speed) * var(--s)) steps(var(--s)) infinite;
}
ul li {
width: 100%;
animation: liMove calc(var(--speed)) infinite;
}
@keyframes move {
0% {
transform: translate(0, 0);
}
100% {
transform: translate(0, calc(var(--s) * var(--h) * -1px));
}
}
@keyframes liMove {
0% {
transform: translate(0, 0);
}
80%,
100% {
transform: translate(0, calc(var(--h) * -1px));
}
}

就能得到這樣一個效果:

Wow,神奇的化學反應產生了!基於 逐幀動畫補間動畫 的結合,我們幾乎實現了一個輪播效果。

當然,有一點瑕疵,可以看到,最後一組資料,是從第六組資料 transform 移動向了一組空資料:

末尾填充頭部第一組資料

實際開發過輪播的同學肯定知道,這裡,其實也很好處理,我們只需要在末尾,補一組頭部的第一個資料即可:

改造下我們的 HTML:

<div class="g-container">
<ul>
<li>Lorem ipsum 1111111</li>
<li>Lorem ipsum 2222222</li>
<li>Lorem ipsum 3333333</li>
<li>Lorem ipsum 4444444</li>
<li>Lorem ipsum 5555555</li>
<li>Lorem ipsum 6666666</li>
<!--末尾補一個首條資料-->
<li>Lorem ipsum 1111111</li>
</ul>
</div>

這樣,我們再看看效果:

Beautiful!如果你還有所疑惑,我們給容器加上 overflow: hidden ,實際效果如下,通過額外新增的最後一組資料,我們的整個動畫剛好完美的銜接上,一個完美的輪播效果:

完整的程式碼,你可以戳這裡: CodePen Demo -- Vertical Infinity Loop [2]

橫向無限輪播

當然,實現了豎直方向的輪播,橫向的效果也是一樣的。

並且,我們可以通過在 HTML 結構中,通過 style 內填寫 CSS 變數值,傳入實際的 li 個數,以達到根據不同 li 個數適配不同動畫:

<div class="g-container">
<ul style="--s: 6">
<li>Lorem ipsum 1111111</li>
<li>Lorem ipsum 2222222</li>
<li>Lorem ipsum 3333333</li>
<li>Lorem ipsum 4444444</li>
<li>Lorem ipsum 5555555</li>
<li>Lorem ipsum 6666666</li>
<!--末尾補一個首尾資料-->
<li>Lorem ipsum 1111111</li>
</ul>
</div>

整個動畫的 CSS 程式碼基本是一致的,我們只需要改變兩個動畫的 transform 值,從豎直位移,改成水平位移即可:

:root {
--w: 300;
--speed: 1.5s;
}
.g-container {
width: calc(--w * 1px);
overflow: hidden;
}
ul {
display: flex;
flex-wrap: nowrap;
animation: move calc(var(--speed) * var(--s)) steps(var(--s)) infinite;
}
ul li {
flex-shrink: 0;
width: 100%;
height: 100%;
animation: liMove calc(var(--speed)) infinite;
}
@keyframes move {
0% {
transform: translate(0, 0);
}
100% {
transform: translate(calc(var(--s) * var(--w) * -1px), 0);
}
}
@keyframes liMove {
0% {
transform: translate(0, 0);
}
80%,
100% {
transform: translate(calc(var(--w) * -1px), 0);
}
}

這樣,我們就輕鬆的轉化為了橫向的效果:

完整的程式碼,你可以戳這裡: CodePen Demo -- Horizontal Infinity Loop [3]

輪播圖?不在話下

OK,上面的只是文字版的輪播,那如果是圖片呢?

沒問題,方法都是一樣的。基於上述的程式碼,我們可以輕鬆地將它修改一下後得到圖片版的輪播效果。

程式碼都是一樣的,就不再列出來,直接看看效果:

完整的程式碼,你可以戳這裡: CodePen Demo -- Horizontal Image Infinity Loop [4]

掌握了這個技巧之後,你可以將它運用在非常多隻需要簡化版的輪播效果之上。

再簡單總結一下,非常有意思的技巧:

  1. 利用 逐幀動畫 ,實現整體的輪播的迴圈效果

  2. 利用 補間動畫 ,實現具體的 * 狀態A狀態B 的動畫效果

  3. 逐幀動畫配合 補間動畫 構成整體輪播的效果

  4. 通過向 HTML 結構末尾補充一組頭部資料,實現整體動畫的銜接

  5. 通過 HTML 元素的 style 標籤,利用 CSS 變數,填入實際的參與迴圈的 DOM 個數,可以實現 JavaScript 與 CSS 的打通

最後

OK,本文到此結束,希望本文對你有所幫助 :)

想 Get 到最有意思的 CSS 資訊,千萬不要錯過我的公眾號 -- iCSS前端趣聞

更多精彩 CSS 技術文章彙總在我的 Github -- iCSS [5] ,持續更新,歡迎點個 star 訂閱收藏。

如果還有什麼疑問或者建議,可以多多交流,原創文章,文筆有限,才疏學淺,文中若有不正之處,萬望告知。

參考資料

[1]

深入淺出 CSS 動畫: https://github.com/chokcoco/iCSS/issues/141

[2]

CodePen Demo -- Vertical Infinity Loop: https://codepen.io/Chokcoco/pen/RwQVByx

[3]

CodePen Demo -- Horizontal Infinity Loop: https://codepen.io/Chokcoco/pen/JjpNBXY

[4]

CodePen Demo -- Horizontal Image Infinity Loop: https://codepen.io/Chokcoco/pen/GRQvqgq

[5]

Github -- iCSS: https://github.com/chokcoco/iCSS

iCSS,不止於 CSS,如果你也對各種新奇有趣的前端(CSS)知識感興趣,歡迎關注 。同時如果你有任何想法疑問,或者也想入群參與大前端技術討論,圍觀答疑解惑,共同成長進步,可以關注公眾號 加我微信,拉你入群

因為微信公眾號修改規則,如果不標星或點在看,你可能會收不到我公眾號文章的推送,請大家將本 公眾號星標 ,看完文章後記得 點下贊 或者 在看 ,謝謝各位!