【Web動畫】科技感十足的暗黑字符雨動畫


本文將使用純 CSS,帶大家一步一步實現一個這樣的科幻字符跳動背景動畫。類似於這樣的字符雨動畫:

Digital Char Rain Animation

或者是類似於這樣的:

CodePen HomeMatrix digital rain (animated version) By yuanchuan

運用在一些類似科技主題的背景之上,非常的添彩。

文字的豎排

首先第一步,就是需要實現文字的豎向排列:

這一步非常的簡單,可能方法也很多,這里我簡單羅列一下:

  1. 使用控制文本排列的屬性 writing-mode 進行控制,可以通過 writing-mode: vertical-lr 等將文字進行豎向排列,但是對於數字和英文,將會旋轉 90° 展示:
<p>1234567890ABC</p>
<p>中文或其他字符ォヶ</p>
p {
    writing-mode: vertical-lr; 
}

當然這種情況下,英文字符的展示不太滿足我們的需求。

  1. 控制容器的寬度,控制每行只能展示 1 個中文字符。

這個方法算是最簡單便捷的方法了,但是由於英文的特殊性,要讓連續的長字符串自然的換行,我們還需要配合 word-break: break-all

p {
    width: 12px;
    font-size: 10px;
    word-break: break-all;
}

效果如下,滿足需求:

使用 CSS 實現隨機字符串的選取

為了讓我們的效果更加自然。每一行的字符的選取最好是隨機的。

但是要讓 CSS 實現隨機生成每一行的字符可太難了。所以這里我們請出 CSS 預處理器 SASS/LESS 。

而且由於不太可能利用 CSS 給單個標簽內,譬如 <p> 標簽插入字符,所以我們把標簽內的字符展示,放在每個 <p> 元素的偽元素 ::beforecontent 當中。

我們可以提前設置好一組字符串,然后利用 SASS function 隨機生成每一次元素內的 content,偽代碼如下:

<div>
    <p></p>
    <p></p>
    <p></p>
</div>
$str: 'ぁぃぅぇぉかきくけこんさしすせそた◁▣▤▥▦▧♂♀♥☻►◄▧▨♦ちつってとゐなにぬねのはひふへほゑまみむめもゃゅょゎをァィゥヴェォカヵキクケヶコサシスセソタチツッテトヰンナニヌネノハヒフヘホヱマミムメモャュョヮヲㄅㄉㄓㄚㄞㄢㄦㄆㄊㄍㄐㄔㄗㄧㄛㄟㄣㄇㄋㄎㄑㄕㄘㄨㄜㄠㄤㄈㄏㄒㄖㄙㄩㄝㄡㄥabcdefghigklmnopqrstuvwxyz123456789%@#$<>^&*_+';
$length: str-length($str);

@function randomChar() {
    $r: random($length);
    @return str-slice($str, $r, $r);
}

@function randomChars($number) {
    $value: '';

    @if $number > 0 {
        @for $i from 1 through $number {
            $value: $value + randomChar();
        }
    }
    @return $value;
}

p:nth-child(1)::before {
    content: randomChars(25);
}
p:nth-child(2)::before {
    content: randomChars(25);
}
p:nth-child(3)::before {
    content: randomChars(25);
}

簡單解釋下上面的代碼:

  1. $str 定義了一串隨機字符串,$length 表示字符串的長度
  2. randomChar() 中利用了 SASS 的 random() 方法,每次隨機選取一個 0 - $length 的整形數,記為 $r,再利用 SASS 的 str-slice 方法,每次從 $str 中選取一個下標為 $r 的隨機字符
  3. randomChars() 就是循環調用 randomChar() 方法,從 $str 中隨機生成一串字符串,長度為傳進去的參數 $number

這樣,每一列的字符,每次都是不一樣的:

當然,上述的方法我認為不是最好的,CSS 的偽元素的 content 是支持字符編碼的,譬如 content: '\3066'; 會被渲染成字符 ,這樣,通過設定字符區間,配合 SASS function 可以更好的生成隨機字符,但是我嘗試了非常久,SASS function 生成的最終產物會在 \3066 這樣的數字間添加上空格,無法最終通過字符編碼轉換成字符,最終放棄...

使用 CSS 實現打字效果

OK,繼續,接下來我們要使用 CSS 實現打字效果,就是讓字符一個一個的出現,像是這樣:

純 CSS 實現文字輸入效果

這里借助了 animation 的 steps 的特性實現,也就是逐幀動畫。

從左向右和從上向下原理是一樣的,以從左向右為例,假設我們有 26 個英文字符,我們已知 26 個英文字符組成的字符串的長度,那么我們只需要設定一個動畫,讓它的寬度變化從 0 - 100% 經歷 26 幀即可,配合 overflow: hidden,steps 的每一幀即可展出一個字符。

當然,這里需要利用一些小技巧,我們如何通過字符的數量知道字符串的長度呢?

划重點:通過等寬字體的特性,配合 CSS 中的 ch 單位

如果不了解什么是等寬字體族,可以看看我的這篇文章 -- 《你該知道的字體 font-family》

CSS 中,ch 單位表示數字 “0” 的寬度。如果字體恰巧又是等寬字體,即每個字符的寬度是一樣的,此時 ch 就能變成每個英文字符的寬度,那么 26ch 其實也就是整個字符串的長度。

利用這個特性,配合 animation 的 steps,我們可以輕松的利用 CSS 實現打字動畫效果:

<h1>Pure CSS Typing animation.</h1>
h1 {
    font-family: monospace;
    width: 26ch;
    white-space: nowrap;
    overflow: hidden;
    animation: typing 3s steps(26, end);
}

@keyframes typing {
    0{
        width: 0;
    }
    100% {
        width: 26ch;
     }
}

就可以得到如下結果啦:

純 CSS 實現文字輸入效果

完整的代碼你可以戳這里:

CodePen Demo -- 純 CSS 實現文字輸入效果

改造成豎向打字效果

接下來,我們就運用上述技巧,改造一下。將一個橫向的打字效果改造成豎向的打字效果。

核心的偽代碼如下:

<div>
    <p></p>
    <p></p>
    <p></p>
</div>
$str: 'ぁぃぅぇぉかきくけこんさしすせそた◁▣▤▥▦▧♂♀♥☻►◄▧▨♦ちつってとゐなにぬねのはひふへほゑまみむめもゃゅょゎをァィゥヴェォカヵキクケヶコサシスセソタチツッテトヰンナニヌネノハヒフヘホヱマミムメモャュョヮヲㄅㄉㄓㄚㄞㄢㄦㄆㄊㄍㄐㄔㄗㄧㄛㄟㄣㄇㄋㄎㄑㄕㄘㄨㄜㄠㄤㄈㄏㄒㄖㄙㄩㄝㄡㄥabcdefghigklmnopqrstuvwxyz123456789%@#$<>^&*_+';
$length: str-length($str);

@function randomChar() {
    $r: random($length);
    @return str-slice($str, $r, $r);
}

@function randomChars($number) {
    $value: '';

    @if $number > 0 {
        @for $i from 1 through $number {
            $value: $value + randomChar();
        }
    }
    @return $value;
}

p {
    width: 12px;
    font-size: 10px;
    word-break: break-all;
}

p::before {
    content: randomChars(20);
    color: #fff;
    animation: typing 4s steps(20, end) infinite;
}

@keyframes typing {
    0% {
        height: 0;
    }
    25% {
        height: 100%;
    }
    100% {
        height: 100%;
    }
}

這樣,我們就實現了豎向的打字效果:

當然,這樣看上去比較整齊划一,缺少了一定的隨機,也就缺少了一定的美感。

基於此,我們進行 2 點改造:

  1. 基於動畫的時長 animation-time、和動畫的延遲 animation-delay,增加一定幅度內的隨機
  2. 在每次動畫的末尾或者過程中,重新替換偽元素的 content,也就是重新生成一份 content

可以借助 SASS 非常輕松的實現這一點,核心的 SASS 代碼如下:

$n: 3;
$animationTime: 3;
$perColumnNums: 20;

@for $i from 0 through $n {
    $content: randomChars($perColumnNums);
    $contentNext: randomChars($perColumnNums);
    $delay: random($n);
    $randomAnimationTine: #{$animationTime + random(20) / 10 - 1}s;

    p:nth-child(#{$i})::before {
        content: $content;
        color: #fff;
        animation: typing-#{$i} $randomAnimationTine steps(20, end) #{$delay * 0.1s * -1} infinite;
    }

    @keyframes typing-#{$i} {
        0% {
            height: 0;
        }
        25% {
            height: 100%;
        }
        100% {
            height: 100%;
            content: $contentNext;
        }
    }
}

看看效果,已經有不錯的改觀:

當然,上述由橫向打字轉變為豎向打字效果其實是有一些不一樣的。在現有的豎向排列規則下,無法通過 ch 配合字符數拿到實際的豎向高度。所以這里有一定的取舍,實際放慢動畫來看,沒個字的現出不一定是完整的。

當然,在快速的動畫效果下幾乎是察覺不到的。

增加光影與透明度變化

最后一步,就是增加光影及透明度的變化。

最佳的效果是要讓每個新出現的字符保持亮度最大,同時已經出現過的字符亮度慢慢減弱。

但是由於這里我們無法精細操控每一個字符,只能操控每一行字符,所以在實現方式上必須另辟蹊徑。

最終的方式是借用了另外一個偽元素進行同步的遮罩以實現最終的效果。下面我們就來一步一步看看過程。

給文字增添亮色及高光

第一步就是給文字增添亮色及高光,這點非常容易,就是選取一個黑色底色下的亮色,並且借助 text-shadow 讓文字發光。

p::before {
    color: rgb(179, 255, 199);
    text-shadow: 0 0 1px #fff, 0 0 2px #fff, 0 0 5px currentColor, 0 0 10px currentColor;
}

看看效果,左邊是白色字符,中間是改變字符顏色,右邊是改變了字體顏色並且添加了字體陰影的效果:

給文字添加同步遮罩

接下來,就是在文字動畫的行進過程中,同步添加一個黑色到透明的遮罩,盡量還原讓每個新出現的字符保持亮度最大,同時已經出現過的字符亮度慢慢減弱。

這個效果的示意圖大概是這樣的,這里我將文字層和遮罩層分開,並且底色從黑色改為白色,方便理解:

蒙層遮罩原理圖

大概的遮罩的層的偽代碼如下,用到了元素的另外一個偽元素:

p::after {
    content: '';
    background: linear-gradient(rgba(0, 0, 0, .9), transparent 75%, transparent);
    background-size: 100% 220%;
    background-repeat: no-repeat;
    animation: mask 4s infinite linear;
}

@keyframes mask {
    0% {
        background-position: 0 220%;
    } 
    30% {
        background-position: 0 0%;
    }
    100% {
        background-position: 0 0%;
    }
}

好,合在一起的最終效果大概就是這樣:

通過調整 @keyframes mask 的一些參數,可以得到不一樣的字符漸隱效果,需要一定的調試。

完整代碼及效果

OK,拆解了一下主要的步驟,最后上一下完整代碼,應用了 Pug 模板引擎和 SASS 語法。

完整代碼加起來不過 100 行。

.g-container
    -for(var i=0; i<50; i++)
        p
@import url('https://fonts.googleapis.com/css2?family=Inconsolata:wght@200&display=swap');

$str: 'ぁぃぅぇぉかきくけこんさしすせそた◁▣▤▥▦▧♂♀♥☻►◄▧▨♦ちつってとゐなにぬねのはひふへほゑまみむめもゃゅょゎをァィゥヴェォカヵキクケヶコサシスセソタチツッテトヰンナニヌネノハヒフヘホヱマミムメモャュョヮヲㄅㄉㄓㄚㄞㄢㄦㄆㄊㄍㄐㄔㄗㄧㄛㄟㄣㄇㄋㄎㄑㄕㄘㄨㄜㄠㄤㄈㄏㄒㄖㄙㄩㄝㄡㄥabcdefghigklmnopqrstuvwxyz123456789%@#$<>^&*_+';
$length: str-length($str);
$n: 50;
$animationTime: 4;
$perColumnNums: 25;

@function randomChar() {
    $r: random($length);
    @return str-slice($str, $r, $r);
}

@function randomChars($number) {
    $value: '';

    @if $number > 0 {
        @for $i from 1 through $number {
            $value: $value + randomChar();
        }
    }
    @return $value;
}

body, html {
    width: 100%;
    height: 100%;
    background: #000;
    display: flex;
    overflow: hidden;
}

.g-container {
    width: 100vw;
    display: flex;
    justify-content: space-between;
    flex-wrap: nowrap;
    flex-direction: row;
    font-family: 'Inconsolata', monospace, sans-serif;
}

p {
    position: relative;
    width: 5vh;
    height: 100vh;
    text-align: center;
    font-size: 5vh;
    word-break: break-all;
    white-space: pre-wrap;
    
    &::before,
    &::after {
        position: absolute;
        top: 0;
        left: 0;
        right: 0;
        height: 100%;
        overflow: hidden;
    }
}

@for $i from 0 through $n {
    $content: randomChars($perColumnNums);
    $contentNext: randomChars($perColumnNums);
    $delay: random($n);
    $randomAnimationTine: #{$animationTime + random(20) / 10 - 1}s;
  
    p:nth-child(#{$i})::before {
        content: $content;
        color: rgb(179, 255, 199);
        text-shadow: 0 0 1px #fff, 0 0 2px #fff, 0 0 5px currentColor, 0 0 10px currentColor;
        animation: typing-#{$i} $randomAnimationTine steps(20, end) #{$delay * 0.1s * -1} infinite;
        z-index: 1;
    }

    p:nth-child(#{$i})::after {
        $alpha: random(40) / 100 + 0.6;
        content: '';
        background: linear-gradient(rgba(0, 0, 0, $alpha), rgba(0, 0, 0, $alpha), rgba(0, 0, 0, $alpha), transparent 75%, transparent);
        background-size: 100% 220%;
        background-repeat: no-repeat;
        animation: mask $randomAnimationTine infinite #{($delay - 2) * 0.1s * -1} linear;
        z-index: 2;
    }

    @keyframes typing-#{$i} {
        0% {
            height: 0;
        }
        25% {
            height: 100%;
        }
        100% {
            height: 100%;
            content: $contentNext;
        }
    }
}

@keyframes mask{
    0% {
        background-position: 0 220%;
    } 
    30% {
        background-position: 0 0%;
    }
    100% {
        background-position: 0 0%;
    }
}

最終效果也就是題圖所示:

Digital Char Rain Animation

完整的代碼及演示效果你可以戳這里:

CodePen Demo -- Digital Char Rain Animation

最后

靈感源自 袁川 老師的這個效果 CodePen Demo -- Matrix digital rain,原效果使用了 JavaScript 實現,本文利用純 CSS 進行了演繹。

更多精彩 CSS 效果可以關注我的 CSS 靈感

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

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

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


免責聲明!

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



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