高階 CSS 技巧在複雜動效中的應用

最近我在 CodePen 上看到了這樣一個有意思的動畫:

bbb1.gif

整個動畫效果是在一個標籤內,藉助了 SVG PATH 實現。其核心在於對漸變(Gradient)的究極利用。

完整的代碼你可以看看這裏 -- CodePen DEMO -- to the future 🍻 By Jane Ori]

源代碼還是非常非常複雜的,並且疊加了複雜的 SVG PATH 路徑。

我嘗試着將其稍微拆分成幾小塊,運用不同的 CSS 高階技巧從另外一個方面方向重新實現了一遍。因爲整個過程還是有非常多有意思的 CSS 技巧,本文就給大家分享一下。

實現上半部分背景加落日

首先,我們來實現上半部分的背景加落日效果:

bg1.png

大家可以先停頓思考下,這裏讓你來實現,會如何去做?需要多少個標籤?

好的,這裏,我們利用一個 DOM 標籤去完成這個圖形:

<div class="g-bg"><div>

背景色好做,使用一個徑向漸變或者線性漸變即可:

.g-bg {
    background: radial-gradient(circle at 50% 100%, var(--color1), var(--color2));
}

如此,先實現一個背景:

bg2.png

好,接下來,我們使用其中一個僞元素實現落日的效果。

.g-bg {
    position: absolute;
    background: radial-gradient(circle at 50% 100%, var(--color1), var(--color2));
    
    &::before {
        content: "";
        position: absolute;
        bottom: 20%;
        left: 10%;
        right: 10%;
        top: 10%;
        background: radial-gradient(circle at 50% 100%, var(--color3), var(--color4) 55%, transparent 55.1%, transparent);
    }
}

效果如下:

bg3.png

到這裏,我覺得算是出現了第一個技巧,也就是這一行代碼 background: radial-gradient(circle at 50% 100%, var(--color3), var(--color4) 55%, transparent 55.1%, transparent),它用於在一個矩形元素中,通過徑向漸變從實色到透明色,實現一個半圓。

技巧 1:可以利用徑向漸變,在一個矩形 DIV 元素中,通過徑向漸變從實色到透明色的變化,實現一個半圓

我們繼續,接下來,切割這個圓形,得到這樣一種效果:

注意,這裏需要裁剪切割的地方不是白色,而是透明的,需要透出後面的背景色。

毫無疑問,這裏需要使用 mask 來完成,我們給僞元素加上 mask:

.g-bg {
    position: absolute;
    background: radial-gradient(circle at 50% 100%, var(--color1), var(--color2));
    
    &::before {
        content: "";
        position: absolute;
        bottom: 20%;
        left: 10%;
        right: 10%;
        top: 10%;
        background: radial-gradient(circle at 50% 100%, var(--color3), var(--color4) 55%, transparent 55.1%, transparent);
        mask: linear-gradient(to top,
            #000 0, #000 10%,
            transparent 10%, transparent 13%,
            #000 13%, #000 20%,
            transparent 20%, transparent 22%,
            #000 22%, #000 35%,
            transparent 35%, transparent 36%,
            #000 36%, #000 100%);
    }
}

這樣,我們就實現了這個效果:

bg5.png

這裏,引出了第二個技巧:

技巧 2:利用 mask 可以對圖形進行裁剪,被裁剪區域將會變成透明。

好,接下來,我們需要在整個圖形上再疊加上豎形黑色條紋。這個其實也可以用 mask,如果整個圖形後面還有一層黑色背景。

當然,這裏我們也可以把另外一個僞元素利用起來,利用它,通過多重線性漸變(repeating-linear-gradient)實現這裏的豎形黑色條紋。

看看代碼:

.g-bg {
    position: absolute;
    background: radial-gradient(circle at 50% 100%, var(--color1), var(--color2));
    
    &::before {
        content: "";
        // code of sun
    }

    &::after {
        content: "";
        position: absolute;
        top: 0;
        bottom: 0;
        right: 0;
        left: 0;
        background: repeating-linear-gradient(90deg, transparent 0, transparent 3px, rgba(0,0,0,.5) 4px, rgba(0,0,0,.5) 5px);
    }
}

這裏,我們利用 repeating-linear-gradient 快速創建批量的豎形黑色條紋效果,得到這樣的效果:

bg6.png

這裏,得到技巧 3。

技巧 3:當你碰到大量重複有規律的線條,或者方塊圖形,你第一時間就應該想到在一個 DOM 中利用漸變而不是多個 DOM 去實現

好,至此,我們整個上半部分就實現了。

利用 -webkit-box-reflect 實現倒影

有了上面的基礎,接下來我們要得到完整的背景:

bg7.png

怎麼做呢?換個配色重新實現一遍嗎?當然不是,這裏我們利用 CSS 提供的倒影功能,可以快速完成這個操作。

.g-bg {
    position: absolute;
    background: radial-gradient(circle at 50% 100%, var(--color1), var(--color2));
    -webkit-box-reflect: below;

    &::before {
        content: "";
        // ...
    }
    &::after {
        content: "";
        // ...
    }
}

我們給 .g-bg 加一個 -webkit-box-reflect: below,意爲下方的倒影:

雖然是複製了一個一模一樣的 .g-bg 出來,但是和我們要的效果還相差很多啊,怎麼辦呢?

別急,-webkit-box-reflect: below 還提供,倒影偏移距離,倒影遮罩等屬性。

我們需要給下方的倒影,添加一個遮罩,修改一下 -webkit-box-reflect 的代碼:

.g-bg {
    position: absolute;
    background: radial-gradient(circle at 50% 100%, var(--color1), var(--color2));
    -webkit-box-reflect: below -50px linear-gradient(rgba(255, 255, 255, .2), transparent);

    &::before {
        content: "";
        // ...
    }
    &::after {
        content: "";
        // ...
    }
}

這樣,我們就得到了這樣一種效果:

這裏,整個圖形其實是半透明的,我們在背後疊加上一層我們想要的色彩漸變,可以利用 body 的僞元素:

body {
    &::before {
        position: absolute;
        content: "";
        top: 50%;
        left: 0;
        bottom: 0;
        right: 0;
        background: linear-gradient(var(--c5), var(--c6));
    }
}
.g-bg {
    position: absolute;
    background: radial-gradient(circle at 50% 100%, var(--color1), var(--color2));
    -webkit-box-reflect: below -50px linear-gradient(rgba(255, 255, 255, .2), transparent);

    &::before {
        content: "";
        // ...
    }
    &::after {
        content: "";
        // ...
    }
}

倒影通過半透明和背後的漸變背景疊加,這樣,我們就完美實現了我們想要的整體背景效果:

bg10.png

這裏,我們可以引出技巧 4。

技巧 4:當出現重複的對稱圖形時,-webkit-box-reflect 也許能派上用場。

利用 CSS 3D 動畫實現線條動畫

好,主體背景完成了,下面,我們來試着實現 3D 線條動畫:

利用 CSS 3D,我們是可以實現這樣一種效果的。我們一步一步來拆解。

首先,我們需要實現這樣一種網格效果:

還記得上面的技巧 3 嗎?當你碰到大量重複有規律的線條,或者方塊圖形,你第一時間就應該想到在一個 DOM 中利用漸變而不是多個 DOM 去實現。

這種效果,其實利用漸變一個標籤就組足夠了:

<div class="grid"></div>
.grid {
    background:
        repeating-linear-gradient(var(--c1), var(--c1) 1px, transparent 1px, transparent 20px),
        repeating-linear-gradient(90deg, var(--c1), var(--c1) 1px, transparent 1px, transparent 20px);
}

僅此而已,我們就能得到一個網格圖。

好的,接下來,需要利用 transform 讓他呈現一種 3D 視覺:

body {
    perspective: 300px;
}
.grid {
    position: absolute;
    width: 300vw;
    height: 600px;
    left: -100vw;
    top: 55vh;
    transform-style: preserve-3d;
    background:
        repeating-linear-gradient(var(--c1), var(--c1) 1px, transparent 1px, transparent 20px),
        repeating-linear-gradient(90deg, var(--c1), var(--c1) 1px, transparent 1px, transparent 20px);
    transform: translate3d(0, 0, 0) rotateX(90deg);
    transform-origin: 50% 0;
}

效果如下:

由於,整體繞 X 軸旋轉 90°,所以這裏的 top: 55vh 很重要。

由於旋轉圓心是 50% 0,如果是 top: 50vh, 相當於整個圖形會垂直於屏幕,如果 top 值小於 50vh,則整個網格是一種向上的翻轉效果:

接着,我們需要讓其運動起來。

我們嘗試添加一個 translateZ 的運動動畫:

.grid {
    // ...
    animation: move 10s infinite linear;
}
@keyframes move {
    0% {
        transform: translate3d(0, 0, -600px) rotateX(90deg);
    }
    100% {
        transform: translate3d(0, 0, 600px) rotateX(90deg);
    }
}

看看效果:

bbb4.gif

這裏有個很嚴重的問題,僅僅只是單個動畫,很難做到無限循環銜接。

因此,我們需要再加一組 Grid,動畫兩組動畫先後出發,來實現整個動畫的銜接。

<div class="grid"></div>
<div class="grid"></div>
.grid {
    // ...
    animation: move 10s infinite linear;
}
.grid:nth-child(2) {
    animation: move 10s infinite -5s linear;
}
@keyframes move {
    0% {
        transform: translate3d(0, 0, -600px) rotateX(90deg);
    }
    100% {
        transform: translate3d(0, 0, 600px) rotateX(90deg);
    }
}

我們通過這麼一種方式:

  1. 兩組一模一樣的動畫,整個位移長度是 1200px,整個動畫持續 10s,緩動爲線性動畫
  2. 第一組出發 5s 後(剛好行進了 600px),第二組再出發,如此 infinite 反覆
  3. 整個 3D 動畫,在近屏幕端看上去就是無限循環的一種效果
  4. 這裏運用的是 -5s,意思是提前 5s 出發,實際動畫效果也就不會有等待感

如下(這裏,爲了錄製 GIF,我整體是加快了動畫的速度):

可以看到,近屏幕端的動畫是連續不斷的,只是遠端會出現一定的閃爍。這裏,可以得到技巧 5。

技巧 5:利用 2 組動畫可以將一些有效在單組內的動畫無法實現的連續效果實現

這樣,疊加上上面的效果,我們就得到了這樣一種效果:

可以看到,很接近了。目前線條動畫遠處還有一些抖動。剛好,我們還差一個山峯的效果,可以把這塊瑕疵擋住。

使用 box-shadow 及 SVG 濾鏡實現山脈效果

OK,最後,我們在屏幕中間再疊加上一個山峯的效果就好。

這裏,原效果使用的是一長串導出的 SVG 路徑。如果我們沒有這種資源,只是想簡單模擬一下效果。這裏我給出一種可能可行的方案。

我首先利用一個圓角矩形進行旋轉,再配合容器的 overflow: hidden 得到一個小山峯:

<div class="g-mountain"></div>
.g-mountain {
    position: absolute;
    left: 0;
    right: 0;
    top: 15%;
    bottom: 42%;
    overflow: hidden;
    
    &::after {
        content: "";
        position: absolute;
        top: 78%;
        background: #011d3f;
        width: 15vw;
        height: 15vw;
        transform: rotate(-45deg);
    }
}

大概是這樣一種效果:

好,如果我們想重複得到多個這樣的圖形,該怎麼辦呢?多個 DOM 嗎?不是的,這裏我們可以利用 box-shadow 複製自身。

.g-mountain {
    // ...
    &::after {
        content: "";
        position: absolute;
        top: 78%;
        background: #011d3f;
        width: 15vw;
        height: 15vw;
        transform: rotate(-45deg);
        box-shadow: 
            -3vw -3vw, 5vw 5vw, 
            10vw 10vw 0 3vw, 15vw 20vw 0 4vw, 
            22vw 22vw 0 6vw, 25vw 30vw 0 12vw, 
            38vw 36vw 0 1vw, 41vw 39vw 0 3vw, 
            45vw 45vw 0 2vw, 52vw 52vw 0 4vh, 
            55vw 55vw 0 1.5vw, 61vw 61vw 0 0.5vw, 68vw 68vw 0 0;
    }
}

這樣,我們就用一個標籤,實現了一系列的“山”:

這裏,我們得到了技巧 6。

技巧 6:box-shadow 可以有效的複製自身,並且,可以利用第四個參數,擴散半徑,來等比例放大自身。

其實,到這裏,一個比較粗糙的還原就完成了。當然,有一點小問題是,山峯明顯不應該是一條條直線。能否營造出一種彎彎曲曲的外輪廓效果呢?

這個使用純 CSS 是比較難實現的,當然,好在這裏我們可以運用上之前給大家多次提及過的 SVG 濾鏡。

利用 feTurbulence 可以有效實現一些波形紋理效果。並且可以通過 CSS filter 快速引入。

<div class="g-mountain"></div>
<svg width="0">
  <filter id="filter">
    <feTurbulence id="turbulence" type="fractalNoise" baseFrequency=".03" numOctaves="20" />
    <feDisplacementMap in="SourceGraphic" scale="30" />
  </filter>
</svg>
.g-mountain {
    // ...
    filter: url('#filter');   

    &::after {
    }
}

這裏,原本,整齊劃一的直線,立馬變得雜亂無章了起來,看起來更像是山脈的輪廓:

這裏,我們得出了技巧 7。

技巧 7:SVG 濾鏡可以通過 CSS 濾鏡快速引入,SVG 濾鏡可以實現一些 CSS 完成不了的事情,譬如一些特殊的紋理,波紋,煙霧顆粒感等等效果。

好,至此,我們就大體上按照自己的理解,重新實現了一遍上述的動畫,再做一些簡單的修飾,最終的效果如下:

CodePen Demo -- Pure CSS to the future

最後

今天的內容有點多,技巧也很猛。文中所有技巧在我過往的文章中都有非常高頻的出現次數,對其中細節不瞭解的可以在 iCSS 中通過關鍵字查找,好好補一補。

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

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

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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章