巧用 CSS 變數,實現動畫函式複用,製作高階感拉滿的網格動畫

chokcoco發表於2023-03-07

本文將介紹一種基於 CSS 變數技巧,透過合理使用 CSS 變數,實現 CSS 動畫 @keyframes 的複用。

CSS 變數

CSS 變數大家應該都比較熟悉了,已經不能算是新知識了,快速過一遍。

CSS 變數(CSS Variable),在之前也叫做 CSS 自定義屬性,其使用方式如下:

// 宣告一個變數:
:root{
  --bgColor: #000;
}

這裡我們藉助了上面 #12、結構性偽類 中的 :root{ } 偽類,在全域性 :root{ } 偽類中定義了一個 CSS 變數,取名為 --bgColor

定義完了之後則是使用,假設我要設定一個 div 的背景色為黑色:

.main{
  background:var(--bgColor);
}

這裡,我們在需要使用之前定義變數的地方,透過 var(定義的變數名) 來呼叫。

在 @keyframes 中使用 CSS 變數

OK,迴歸我們的正題。巧用 CSS 變數,實現動畫函式複用

假設,我們現在有多個元素,需要實現一個位移動畫,從位置 A 位移到 位置 B,位置 A 相同,但是位置 B 不一樣,像是這樣:

正常而言,由於終點不一樣,我們可能需要實現 3 個不一樣的 @keyframes,像是這樣:

<ul>
    <li></li>
    <li></li>
    <li></li>
</ul>
li:nth-child(1) {
    animation: move1 2s linear;
}
li:nth-child(2) {
    animation: move2 2s linear;
}
li:nth-child(3) {
    animation: move3 2s linear;
}

@keyframes move1 {
    60%,
    100% {
        transform: translate(150px);
    }
}
@keyframes move2 {
    60%,
    100% {
        transform: translate(120px);
    }
}
@keyframes move3 {
    60%,
    100% {
        transform: translate(200px);
    }
}

這個程式碼有問題嗎?沒有。

但是,我們可以利用 CSS 變數,讓它變得更為簡潔,我們改造一下 @keyframes 程式碼,將固定的位移值,變成一個變數:

@keyframes move {
    60%,
    100% {
        transform: translate(var(--dis));
    }
}

由於 CSS 變數是存在作用域的,我們可以透過 CSS 變數的方式,給每一個 li 定義一個不同的 --dis 變數,像是這樣:

li:nth-child(1) {
    --dis: 150px;
}
li:nth-child(2) {
    --dis: 120px;
}
li:nth-child(3) {
    --dis: 200px;
}

這樣,雖然動畫的結束點不一樣,但是我們利用 CSS 變數,複用了同一個 @keyframes 函式:

透過內聯 style 屬性傳入自定義變數

除了透過在 <style> 內傳入不同的自定義變數,我們還可以透過內聯 style 屬性傳入自定義變數。

我們再改造一下我們的 @keyframes:

@keyframes move {
    60%,
    100% {
        transform: translate(var(--end));
        background: var(--color);
    }
}

這一次,我們不需要透過 :nth-child() 去修改每一個 li 的 CSS,而是透過 HTML 元素的內聯 style 屬性,像是這樣:

<ul>
    <li style="--end: 150px; --color: red;"></li>
    <li style="--end: 200px; --color: blue;"></li>
    <li style="--end: 120px; --color: green;"></li>
</ul>

是的,每個 li 元素的 @keyframes 可以讀取到每個 li 的 style 裡面定義的不一樣的 CSS 變數。

這樣,我們就可以得到如下效果:

完整的程式碼,可以戳這裡:CodePen Demo -- 巧用 CSS 變數,實現動畫函式複用

實戰演練

下面我們實戰演練一下,上一點難度。

在很久之前,我們實現過這樣一個動畫效果:

這個動畫效果的實現方式在於:

  1. 父級元素實現一個 rotateZ(360deg) 的勻速動畫
  2. 子級元素實現一個反向的 rotateZ(-360deg) 的勻速動畫
  3. 給父級元素新增一個 rotateX(40deg) 的動畫

由於父容器和子容器同時相反向旋轉,所以子元素看上去其實和沒有旋轉是一樣的。但是由於又新增了一個 rotateX(40deg) 動畫,因此看上去就會有這樣一種 3D 效果。

在之前,我們的程式碼是這樣的:

<div class="reverseRotate">
    <div class="rotate">
    </div>
</div>
.rotate {
    animation: rotate 5s linear infinite; 
}
.reverseRotate {
    animation: reverseRotate 5s linear infinite; 
}
@keyframes rotate {
    0% {
        transform: rotateX(0deg) rotateZ(0deg);
    }
    50% {
        transform: rotateX(40deg) rotateZ(180deg);
    }
    100% {
        transform: rotateX(0deg) rotateZ(360deg);
    }
}
@keyframes reverseRotate {
    0% {
        transform: rotateZ(0deg);
    }
    100% {
        transform: rotateZ(-360deg);
    }
}

可以看到,我們這裡實現了兩個動畫效果:

  1. @keyframes rotate {} 父容器的旋轉動畫
  2. @keyframes reverseRotate {} 子容器的旋轉動畫

其實,這裡,運用今天的技巧,我們可以把兩個動畫合成為一個,利用 CSS 自定義變數進行控制。改造後更簡潔的 CSS 程式碼如下:

.rotate {
    --degZ: 360deg;
    --degZMiddle: 180deg;
    --degX: 30deg;
    animation: rotate 5s linear infinite; 
}

.reverseRotate {
    --degZ: -360deg;
    --degZMiddle: -180deg;
    --degX: 0;
    animation: rotate 5s linear infinite; 
}

@keyframes rotate {
    0% {
        transform: rotateX(0deg) rotateZ(0deg);
    }
    50% {
        transform: rotateX(var(--degX)) rotateZ(var(--degZMiddle));
    }
    100% {
        transform: rotateX(0deg) rotateZ(var(--degZ));
    }
}

是的,我們可以得到同樣的效果!

完整的程式碼,你可以戳這裡:CodePen DEMO -- Css動畫正反旋轉相消

圖片旋轉配合容器旋轉

下面,我們再來嘗試一個有意思的動畫效果,圖片旋轉配合容器旋轉。

在上述的基礎上,如果我們把子元素,改成圖片,整個效果就會有意思不少,我們稍微改變一點點程式碼:

<div class="reverseRotate">
    <img class="rotate" src="https://picsum.photos/1000/1000?random=5" alt="">
</div>
.rotate,
.reverseRotate {
    width: 60vh;
    height: 60vh;
}
.reverseRotate {
    position: absolute;
    top: 50%;
    left: 50%;
    transform: translate(-50%, -50%);
    border: 3px solid #999;
    overflow: hidden;
}

.rotate {
    position: absolute;
    top: 50%;
    left: 50%;
    transform: translate(-50%, -50%);
    width: 100%;
    height: 100%;
}

.rotate {
    --degZ: 360deg;
    animation: rotate 5s linear infinite; 
}

.reverseRotate {
    --degZ: -360deg;
    animation: rotate 5s linear infinite; 
}

@keyframes rotate {
    0% {
        transform: translate(-50%, -50%) rotateZ(0deg);
    }
    100% {
        transform: translate(-50%, -50%) rotateZ(var(--degZ));
    }
}

這裡,我們做了什麼事情呢?

  1. 去掉了 3D 效果
  2. 給外層容器加了邊框
  3. 內層圖片基於父容器絕對定位,水平垂直居中
  4. 內外兩層容器反向旋轉 360° 動畫

這樣,我們就能看到,雖然內外兩層容器同時在進行相反方向的旋轉 360° 動畫,但是內部的圖片其實是靜止不動的!

效果如下:

由於,內部圖片的大小為父容器的 100%,所以在旋轉過程中,父容器會有明顯的無法包裹住整個圖片的情況。

這個很好解決,我們只需要把圖片大小調整大一點:

// ... 其它程式碼不變
.rotate {
    width: 150%;
    height: 150%;
}
.rotate {
    --degZ: 360deg;
    animation: rotate 5s linear infinite; 
}

正常而言,對於正方形容器,內部圖片設定到 141% 即可滿足父容器旋轉過程,可以一直包裹住圖片的效果。那麼,我們就能得到這樣一種效果:

完整的程式碼,你可以戳這裡:CodePen Demo -- Css動畫正反旋轉相消

Gird 佈局配合正反旋轉動畫

當然,上述當只有一個容器的時候,整個動畫效果還不夠震撼。

如果我們可以把這個效果融合進整個佈局的動畫之中,整個效果又會完全不一樣。

Rotating gallery with CSS scroll-driven animations 這篇文章中,作者提供了一種非常巧妙的思路,將 Grid 佈局動畫與上述動畫效果巧妙的結合了起來。

首先,我們利用 Gird 佈局,實現這樣一個簡單的網格佈局結構:

<div class="container">
  <div class="A">
      <img src="https://picsum.photos/600/600?random=1" alt=""></div>
  <div class="B">
      <img src="https://picsum.photos/600/600?random=2" alt=""></div>
  <div class="C">
      <img src="https://picsum.photos/600/600?random=3" alt=""></div>
  <div class="D">
      <img src="https://picsum.photos/600/600?random=4" alt=""></div>
  <div class="E">
      <img src="https://picsum.photos/600/600?random=5" alt=""></div>
</div>
.container {
    width: 60vmin;
    height: 60vmin;
    display: grid;
    grid-template-columns: 1fr 1fr 1fr;
    grid-template-rows: 1fr 1fr 1fr;
    gap: 4px;
    grid-template-areas:
        "E B B"
        "E A C"
        "D D C";
}
.container > div {
    border: 3px solid #431312;
    border-radius: 5px;
}

.A {
    grid-area: A;
}
.B {
    grid-area: B;
}
.C {
    grid-area: C;
}
.D {
    grid-area: D;
}
.E {
    grid-area: E;
}

效果如下:

接下來,我們要做的,就是結合上面的知識點,容器滾動起來,圖片反向滾動起來,配合一些 tranfrom 變換。

有了上面的鋪墊,下面的新增的程式碼就非常好理解了:

.container > div img {
    --scale: 1;
    --rotation: -360deg;
    position: absolute;
    top: 50%;
    left: 50%;
    transform: translate(-50%, -50%);
    width: 260%;
    height: 260%;
    object-fit: cover;
    object-position: center;
}
.container,
.container > div img {
    animation: 10s scale-up both ease-in-out infinite alternate;
}
@keyframes scale-up {
	0% {
		transform: translate(-50%, -50%) scale(var(--scale)) rotate(0deg);
	}
	100% {
		transform: translate(-50%, -50%) scale(1) rotate(var(--rotation));	
	}
}

這樣,我們就得到了一個高階感拉滿的網格旋轉動畫:

注意,這裡我們依舊是透過 CSS 自定義變數,在不同元素間,複用了同一個動畫 @keyframes 函式。

完整的程式碼,你可以戳這裡:CodePen Demo -- Grid 圖片旋轉動畫 & 使用 CSS 變數複用動畫函式

最後

好了,本文到此結束,希望本文對你有所幫助 ?

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

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

相關文章