Web動(dòng)畫(huà)之科技感十足的暗黑字符雨動(dòng)畫(huà)
本文將使用純 CSS,帶大家一步一步實(shí)現(xiàn)一個(gè)這樣的科幻字符跳動(dòng)背景動(dòng)畫(huà)。類似于這樣的字符雨動(dòng)畫(huà):

Digital Char Rain Animation
或者是類似于這樣的:

CodePen Home Matrix digital rain (animated version) By yuanchuan
運(yùn)用在一些類似科技主題的背景之上,非常的添彩。
文字的豎排
首先第一步,就是需要實(shí)現(xiàn)文字的豎向排列:

這一步非常的簡(jiǎn)單,可能方法也很多,這里我簡(jiǎn)單羅列一下:
1.使用控制文本排列的屬性 writing-mode 進(jìn)行控制,可以通過(guò) writing-mode: vertical-lr 等將文字進(jìn)行豎向排列,但是對(duì)于數(shù)字和英文,將會(huì)旋轉(zhuǎn) 90° 展示:
- <p>1234567890ABC</p>
- <p>中文或其他字符ォヶ</p>
- p {
- writing-mode: vertical-lr;
- }

當(dāng)然這種情況下,英文字符的展示不太滿足我們的需求。
2.控制容器的寬度,控制每行只能展示 1 個(gè)中文字符。
這個(gè)方法算是最簡(jiǎn)單便捷的方法了,但是由于英文的特殊性,要讓連續(xù)的長(zhǎng)字符串自然的換行,我們還需要配合 word-break: break-all :
- p {
- width: 12px;
- font-size: 10px;
- word-break: break-all;
- }
效果如下,滿足需求:

使用 CSS 實(shí)現(xiàn)隨機(jī)字符串的選取
為了讓我們的效果更加自然。每一列的字符的選取最好是隨機(jī)的。
但是要讓 CSS 實(shí)現(xiàn)隨機(jī)生成每一列的字符可太難了。所以這里我們請(qǐng)出 CSS 預(yù)處理器 SASS/LESS 。
而且由于不太可能利用 CSS 給單個(gè)標(biāo)簽內(nèi),譬如
標(biāo)簽插入字符,所以我們把標(biāo)簽內(nèi)的字符展示,放在每個(gè)
元素的偽元素 ::before 的 content 當(dāng)中。
我們可以提前設(shè)置好一組字符串,然后利用 SASS function 隨機(jī)生成每一次元素內(nèi)的 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);
- }
簡(jiǎn)單解釋下上面的代碼:
- $str 定義了一串隨機(jī)字符串,$length 表示字符串的長(zhǎng)度
- randomChar() 中利用了 SASS 的 random() 方法,每次隨機(jī)選取一個(gè) 0 - $length 的整形數(shù),記為 $r,再利用 SASS 的 str-slice 方法,每次從 $str 中選取一個(gè)下標(biāo)為 $r 的隨機(jī)字符
- randomChars() 就是循環(huán)調(diào)用 randomChar() 方法,從 $str 中隨機(jī)生成一串字符串,長(zhǎng)度為傳進(jìn)去的參數(shù) $number
這樣,每一列的字符,每次都是不一樣的:

當(dāng)然,上述的方法我認(rèn)為不是最好的,CSS 的偽元素的 content 是支持字符編碼的,譬如 content: '\3066'; 會(huì)被渲染成字符 て,這樣,通過(guò)設(shè)定字符區(qū)間,配合 SASS function 可以更好的生成隨機(jī)字符,但是我嘗試了非常久,SASS function 生成的最終產(chǎn)物會(huì)在 \ 和 3066 這樣的數(shù)字間添加上空格,無(wú)法最終通過(guò)字符編碼轉(zhuǎn)換成字符,最終放棄...
使用 CSS 實(shí)現(xiàn)打字效果
OK,繼續(xù),接下來(lái)我們要使用 CSS 實(shí)現(xiàn)打字效果,就是讓字符一個(gè)一個(gè)的出現(xiàn),像是這樣:

純 CSS 實(shí)現(xiàn)文字輸入效果
這里借助了 animation 的 steps 的特性實(shí)現(xiàn),也就是逐幀動(dòng)畫(huà)。
從左向右和從上向下原理是一樣的,以從左向右為例,假設(shè)我們有 26 個(gè)英文字符,我們已知 26 個(gè)英文字符組成的字符串的長(zhǎng)度,那么我們只需要設(shè)定一個(gè)動(dòng)畫(huà),讓它的寬度變化從 0 - 100% 經(jīng)歷 26 幀即可,配合 overflow: hidden,steps 的每一幀即可展出一個(gè)字符。
當(dāng)然,這里需要利用一些小技巧,我們?nèi)绾瓮ㄟ^(guò)字符的數(shù)量知道字符串的長(zhǎng)度呢?
劃重點(diǎn):通過(guò)等寬字體的特性,配合 CSS 中的 ch 單位。
如果不了解什么是等寬字體族,可以看看我的這篇文章 -- 《你該知道的字體 font-family》[1]。
CSS 中,ch 單位表示數(shù)字 “0” 的寬度。如果字體恰巧又是等寬字體,即每個(gè)字符的寬度是一樣的,此時(shí) ch 就能變成每個(gè)英文字符的寬度,那么 26ch 其實(shí)也就是整個(gè)字符串的長(zhǎng)度。
利用這個(gè)特性,配合 animation 的 steps,我們可以輕松的利用 CSS 實(shí)現(xiàn)打字動(dòng)畫(huà)效果:
- <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;
- }
- }
就可以得到如下結(jié)果啦:

純 CSS 實(shí)現(xiàn)文字輸入效果
完整的代碼你可以戳這里:
CodePen Demo -- 純 CSS 實(shí)現(xiàn)文字輸入效果[2]
改造成豎向打字效果
接下來(lái),我們就運(yùn)用上述技巧,改造一下。將一個(gè)橫向的打字效果改造成豎向的打字效果。
核心的偽代碼如下:
- <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%;
- }
- }
這樣,我們就實(shí)現(xiàn)了豎向的打字效果:

當(dāng)然,這樣看上去比較整齊劃一,缺少了一定的隨機(jī),也就缺少了一定的美感。
基于此,我們進(jìn)行 2 點(diǎn)改造:
- 基于動(dòng)畫(huà)的時(shí)長(zhǎng) animation-time、和動(dòng)畫(huà)的延遲 animation-delay,增加一定幅度內(nèi)的隨機(jī)
- 在每次動(dòng)畫(huà)的末尾或者過(guò)程中,重新替換偽元素的 content,也就是重新生成一份 content
可以借助 SASS 非常輕松的實(shí)現(xiàn)這一點(diǎn),核心的 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;
- }
- }
- }
看看效果,已經(jīng)有不錯(cuò)的改觀:

當(dāng)然,上述由橫向打字轉(zhuǎn)變?yōu)樨Q向打字效果其實(shí)是有一些不一樣的。在現(xiàn)有的豎向排列規(guī)則下,無(wú)法通過(guò) ch 配合字符數(shù)拿到實(shí)際的豎向高度。所以這里有一定的取舍,實(shí)際放慢動(dòng)畫(huà)來(lái)看,沒(méi)個(gè)字的現(xiàn)出不一定是完整的。
當(dāng)然,在快速的動(dòng)畫(huà)效果下幾乎是察覺(jué)不到的。
增加光影與透明度變化
最后一步,就是增加光影及透明度的變化。
最佳的效果是要讓每個(gè)新出現(xiàn)的字符保持亮度最大,同時(shí)已經(jīng)出現(xiàn)過(guò)的字符亮度慢慢減弱。
但是由于這里我們無(wú)法精細(xì)操控每一個(gè)字符,只能操控每一行字符,所以在實(shí)現(xiàn)方式上必須另辟蹊徑。
最終的方式是借用了另外一個(gè)偽元素進(jìn)行同步的遮罩以實(shí)現(xiàn)最終的效果。下面我們就來(lái)一步一步看看過(guò)程。
給文字增添亮色及高光
第一步就是給文字增添亮色及高光,這點(diǎn)非常容易,就是選取一個(gè)黑色底色下的亮色,并且借助 text-shadow 讓文字發(fā)光。
- 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;
- }
看看效果,左邊是白色字符,中間是改變字符顏色,右邊是改變了字體顏色并且添加了字體陰影的效果:

給文字添加同步遮罩
接下來(lái),就是在文字動(dòng)畫(huà)的行進(jìn)過(guò)程中,同步添加一個(gè)黑色到透明的遮罩,盡量還原讓每個(gè)新出現(xiàn)的字符保持亮度最大,同時(shí)已經(jīng)出現(xiàn)過(guò)的字符亮度慢慢減弱。
這個(gè)效果的示意圖大概是這樣的,這里我將文字層和遮罩層分開(kāi),并且底色從黑色改為白色,方便理解:
蒙層遮罩原理圖
大概的遮罩的層的偽代碼如下,用到了元素的另外一個(gè)偽元素:
- 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%;
- }
- }
好,合在一起的最終效果大概就是這樣:

通過(guò)調(diào)整 @keyframes mask 的一些參數(shù),可以得到不一樣的字符漸隱效果,需要一定的調(diào)試。
完整代碼及效果
OK,拆解了一下主要的步驟,最后上一下完整代碼,應(yīng)用了 Pug 模板引擎和 SASS 語(yǔ)法。
完整代碼加起來(lái)不過(guò) 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[3]
最后
靈感源自 袁川[4] 老師的這個(gè)效果 CodePen Demo -- Matrix digital rain[5],原效果使用了 JavaScript· 實(shí)現(xiàn),本文利用純 CSS 進(jìn)行了演繹。
好了,本文到此結(jié)束,希望對(duì)你有幫助 :)
參考資料
[1]《你該知道的字體 font-family》:
https://github.com/chokcoco/iCSS/issues/6
[2]CodePen Demo -- 純 CSS 實(shí)現(xiàn)文字輸入效果:
https://codepen.io/Chokcoco/pen/WXGoGB
[3]CodePen Demo -- Digital Char Rain Animation:
https://codepen.io/Chokcoco/pen/bGWvNme
[4]袁川:
https://github.com/yuanchuan
[5]CodePen Demo -- Matrix digital rain:
https://codepen.io/yuanchuan/pen/YoqWeR