简单点!原子化CSS实现黑客帝国代码雨

1,502 阅读1分钟

最近看了《黑客帝国4:矩阵重启》,又见当年的代码雨,勾起了一些回忆,记得当初(1999年左右吧)实现这个效果还是在Flash中遮罩文字,这么多年过去了,网上也出现了很多的实现方法。ok,话不多说,动手试试。

本案实现方式

  • Windi CSS (个人觉得目前最佳原子化CSS解决方案,完全兼容TailwindCSS 2.0)
  • Anime.js(轻量级动效担当)
  • VS Code + Vue3.0 (这个您随意,我偷懒直接在项目里增加了一个页面,也想着之后如果有需要就把它组件化,类似弹幕、日志滚动等需求)
  • 例子文字取自徐志摩的《再别康桥》,回想当年装文青的情怀。

简简单单、清清爽爽三步搞定

  1. 文字排版

    <template>
      <!-- ul 暂不考虑自适应,所以加入宽高限制,背景为纯黑,内容超出容器隐藏,flex使列表横行排列 -->
      <ul class="h-100 w-200 bg-black overflow-hidden flex">
        <!-- li 限制宽度,水平居中,字体加粗加大,文本纵向排版;v-for循环出文本数组内容,并按索引分配id -->
        <li class="w-5 mx-auto font-bold text-xl write-vertical-right" v-for="(text, index) in texts" :key="index" :id="`text-${index}`">
          <span class="text-white">{{ text }}</span>
        </li>
      </ul>
    </template>
    
    <script lang="ts" setup>
    // 文本内容数组
    const texts = ['轻轻的我走了,', '正如我轻轻的来;', '我轻轻的招手,', '作别西天的云彩。', '那河畔的金柳,', '是夕阳中的新娘;', '波光里的艳影,', '在我的心头荡漾。', '软泥上的青荇,', '油油的在水底招摇;', '在康河的柔波里,', '我甘心做一条水草!', '那榆荫下的一潭,', '不是清泉,是天上虹;', '揉碎在浮藻间,', '沉淀着彩虹似的梦。', '寻梦?撑一支长篙,', '向青草更青处漫溯;', '满载一船星辉,', '在星辉斑斓里放歌。', '但我不能放歌,', '悄悄是别离的笙箫;', '夏虫也为我沉默,', '沉默是今晚的康桥!', '悄悄的我走了,', '正如我悄悄的来;', '我挥一挥衣袖,', '不带走一片云彩。', '轻轻的我走了,', '正如我轻轻的来;', '我轻轻的招手,', '作别西天的云彩。', '那河畔的金柳,', '是夕阳中的新娘;', '波光里的艳影,', '在我的心头荡漾。', '软泥上的青荇,', '油油的在水底招摇;', '在康河的柔波里,', '我甘心做一条水草!', '那榆荫下的一潭,', '不是清泉,是天上虹;', '揉碎在浮藻间,', '沉淀着彩虹似的梦。', '寻梦?撑一支长篙,', '向青草更青处漫溯;', '满载一船星辉,', '在星辉斑斓里放歌。', '但我不能放歌,', '悄悄是别离的笙箫;', '夏虫也为我沉默,', '沉默是今晚的康桥!', '悄悄的我走了,', '正如我悄悄的来;', '我挥一挥衣袖,', '不带走一片云彩。']
    </script>
    

    当前效果如下: 1650542733740.jpg

  2. 文字渲染

    <template>
      <!-- ul 暂不考虑自适应,所以加入宽高限制,背景为纯黑,内容超出容器隐藏,flex使列表横行排列 -->
      <ul class="h-100 w-200 bg-black overflow-hidden flex">
        <!-- li 限制宽度,水平居中,字体加粗加大,文本纵向排版;v-for循环出文本数组内容,并按索引分配id -->
        <li class="w-5 mx-auto font-bold text-xl write-vertical-right" v-for="(text, index) in texts" :key="index" :id="`text-${index}`">
          <!-- span 设置文字自身颜色透明,使用渐变背景从绿到黑,并把背景按字符填充,达到文字颜色渐变效果;单独用style的字符阴影样式可灵活控制字符的外发光效果;最后把文字内容排列翻转,形成向下坠落的姿势 -->
          <span class="text-transparent bg-gradient-to-t from-green-400 to-black bg-clip-text" style="text-shadow: 0px 5px 10px rgb(0 255 0 / 30%)">{{ reverseStr(text) }}</span>
        </li>
      </ul>
    </template>
    
    <!-- HTML 部分这里就完全了 -->
    
    <script lang="ts" setup>
    // 文本内容数组
    const texts = ['轻轻的我走了,', '正如我轻轻的来;', '我轻轻的招手,', '作别西天的云彩。', '那河畔的金柳,', '是夕阳中的新娘;', '波光里的艳影,', '在我的心头荡漾。', '软泥上的青荇,', '油油的在水底招摇;', '在康河的柔波里,', '我甘心做一条水草!', '那榆荫下的一潭,', '不是清泉,是天上虹;', '揉碎在浮藻间,', '沉淀着彩虹似的梦。', '寻梦?撑一支长篙,', '向青草更青处漫溯;', '满载一船星辉,', '在星辉斑斓里放歌。', '但我不能放歌,', '悄悄是别离的笙箫;', '夏虫也为我沉默,', '沉默是今晚的康桥!', '悄悄的我走了,', '正如我悄悄的来;', '我挥一挥衣袖,', '不带走一片云彩。', '轻轻的我走了,', '正如我轻轻的来;', '我轻轻的招手,', '作别西天的云彩。', '那河畔的金柳,', '是夕阳中的新娘;', '波光里的艳影,', '在我的心头荡漾。', '软泥上的青荇,', '油油的在水底招摇;', '在康河的柔波里,', '我甘心做一条水草!', '那榆荫下的一潭,', '不是清泉,是天上虹;', '揉碎在浮藻间,', '沉淀着彩虹似的梦。', '寻梦?撑一支长篙,', '向青草更青处漫溯;', '满载一船星辉,', '在星辉斑斓里放歌。', '但我不能放歌,', '悄悄是别离的笙箫;', '夏虫也为我沉默,', '沉默是今晚的康桥!', '悄悄的我走了,', '正如我悄悄的来;', '我挥一挥衣袖,', '不带走一片云彩。']
    
    // 字符串翻转方法
    const reverseStr = (str: string) => {
      return str.split('').reverse().join('')
    }
    </script>
    

    当前效果如下: 1650543304030.jpg

  3. 加入动效

    <script lang="ts" setup>
    // vue3 引入 onMounted
    import { onMounted } from 'vue'
    
    // 先安装anime.js库,在这里引入即可
    import anime from 'animejs'
    
    // 文本内容数组
    const texts = ['轻轻的我走了,', '正如我轻轻的来;', '我轻轻的招手,', '作别西天的云彩。', '那河畔的金柳,', '是夕阳中的新娘;', '波光里的艳影,', '在我的心头荡漾。', '软泥上的青荇,', '油油的在水底招摇;', '在康河的柔波里,', '我甘心做一条水草!', '那榆荫下的一潭,', '不是清泉,是天上虹;', '揉碎在浮藻间,', '沉淀着彩虹似的梦。', '寻梦?撑一支长篙,', '向青草更青处漫溯;', '满载一船星辉,', '在星辉斑斓里放歌。', '但我不能放歌,', '悄悄是别离的笙箫;', '夏虫也为我沉默,', '沉默是今晚的康桥!', '悄悄的我走了,', '正如我悄悄的来;', '我挥一挥衣袖,', '不带走一片云彩。', '轻轻的我走了,', '正如我轻轻的来;', '我轻轻的招手,', '作别西天的云彩。', '那河畔的金柳,', '是夕阳中的新娘;', '波光里的艳影,', '在我的心头荡漾。', '软泥上的青荇,', '油油的在水底招摇;', '在康河的柔波里,', '我甘心做一条水草!', '那榆荫下的一潭,', '不是清泉,是天上虹;', '揉碎在浮藻间,', '沉淀着彩虹似的梦。', '寻梦?撑一支长篙,', '向青草更青处漫溯;', '满载一船星辉,', '在星辉斑斓里放歌。', '但我不能放歌,', '悄悄是别离的笙箫;', '夏虫也为我沉默,', '沉默是今晚的康桥!', '悄悄的我走了,', '正如我悄悄的来;', '我挥一挥衣袖,', '不带走一片云彩。']
    
    // 字符串翻转方法
    const reverseStr = (str: string) => {
      return str.split('').reverse().join('')
    }
    
    // 动效处理方法(可根据不同的内容获取方式处理动效,不要拘泥,不要抬杠)
    const handleAnime = () => {
      str.map((e, index) => {
        // 动效关键点
        anime({
          targets: '#text-' + index, // 制定动效添加的目标元素
          translateY: [-500, 400], // 延Y轴运动,根据我限定的容器高度,-500可满足从容器顶部500像素位置开始动效,400至容器底部结束动效
          duration: Math.random() * 3000 + 500, // 随机持续时间,控制动效快慢
          delay: Math.random() * 1000, // 随机延时开始动效,控制错落下坠
          loop: true, // 开启循环
          easing: 'linear', // 匀速运动方式
        })
      })
    }
    
    // 调用动效方法的时机(这里只为展示)
    onMounted(() => {
      handleAnime()
    })
    </script>
    

    最终效果如下(gif呈现出来的效果不够丝滑,就不演示放动图了): 1650524036210.jpg


完整代码如下(vue3版本):

<template>
  <!-- ul 暂不考虑自适应,所以加入宽高限制,背景为纯黑,内容超出容器隐藏,flex使列表横行排列 -->
  <ul class="h-100 w-200 bg-black overflow-hidden flex">
    <!-- li 限制宽度,水平居中,字体加粗加大,文本纵向排版;v-for循环出文本数组内容,并按索引分配id -->
    <li class="w-5 mx-auto font-bold text-xl write-vertical-right" v-for="(text, index) in texts" :key="index" :id="`text-${index}`">
      <!-- span 设置文字自身颜色透明,使用渐变背景从绿到黑,并把背景按字符填充,达到文字颜色渐变效果;单独用style的字符阴影样式可灵活控制字符的外发光效果;最后把文字内容排列翻转,形成向下坠落的姿势 -->
      <span class="text-transparent bg-gradient-to-t from-green-400 to-black bg-clip-text" style="text-shadow: 0px 5px 10px rgb(0 255 0 / 30%)">{{ reverseStr(text) }}</span>
    </li>
  </ul>
</template>

<script lang="ts" setup>
// vue3 引入 onMounted
import { onMounted } from 'vue'

// 先安装anime.js库,在这里引入即可
import anime from 'animejs'

// 文本内容数组
const texts = ['轻轻的我走了,', '正如我轻轻的来;', '我轻轻的招手,', '作别西天的云彩。', '那河畔的金柳,', '是夕阳中的新娘;', '波光里的艳影,', '在我的心头荡漾。', '软泥上的青荇,', '油油的在水底招摇;', '在康河的柔波里,', '我甘心做一条水草!', '那榆荫下的一潭,', '不是清泉,是天上虹;', '揉碎在浮藻间,', '沉淀着彩虹似的梦。', '寻梦?撑一支长篙,', '向青草更青处漫溯;', '满载一船星辉,', '在星辉斑斓里放歌。', '但我不能放歌,', '悄悄是别离的笙箫;', '夏虫也为我沉默,', '沉默是今晚的康桥!', '悄悄的我走了,', '正如我悄悄的来;', '我挥一挥衣袖,', '不带走一片云彩。', '轻轻的我走了,', '正如我轻轻的来;', '我轻轻的招手,', '作别西天的云彩。', '那河畔的金柳,', '是夕阳中的新娘;', '波光里的艳影,', '在我的心头荡漾。', '软泥上的青荇,', '油油的在水底招摇;', '在康河的柔波里,', '我甘心做一条水草!', '那榆荫下的一潭,', '不是清泉,是天上虹;', '揉碎在浮藻间,', '沉淀着彩虹似的梦。', '寻梦?撑一支长篙,', '向青草更青处漫溯;', '满载一船星辉,', '在星辉斑斓里放歌。', '但我不能放歌,', '悄悄是别离的笙箫;', '夏虫也为我沉默,', '沉默是今晚的康桥!', '悄悄的我走了,', '正如我悄悄的来;', '我挥一挥衣袖,', '不带走一片云彩。']

// 字符串翻转方法
const reverseStr = (str: string) => {
  return str.split('').reverse().join('')
}

// 动效处理方法(可根据不同的内容获取方式处理动效,不要拘泥,不要抬杠)
const handleAnime = () => {
  str.map((e, index) => {
		// 动效关键点
    anime({
      targets: '#text-' + index, // 制定动效添加的目标元素
      translateY: [-500, 400], // 延Y轴运动,根据我限定的容器高度,-500可满足从容器顶部500像素位置开始动效,400至容器底部结束动效
      duration: Math.random() * 3000 + 500, // 随机持续时间,控制动效快慢
      delay: Math.random() * 1000, // 随机延时开始动效,控制错落下坠
      loop: true, // 开启循环
      easing: 'linear', // 匀速运动方式
    })
  })
}

// 调用动效方法的时机(这里只为展示)
onMounted(() => {
  handleAnime()
})
</script>