最近看了《黑客帝国4:矩阵重启》,又见当年的代码雨,勾起了一些回忆,记得当初(1999年左右吧)实现这个效果还是在Flash中遮罩文字,这么多年过去了,网上也出现了很多的实现方法。ok,话不多说,动手试试。
本案实现方式
- Windi CSS (个人觉得目前最佳原子化CSS解决方案,完全兼容TailwindCSS 2.0)
- Anime.js(轻量级动效担当)
- VS Code + Vue3.0 (这个您随意,我偷懒直接在项目里增加了一个页面,也想着之后如果有需要就把它组件化,类似弹幕、日志滚动等需求)
- 例子文字取自徐志摩的《再别康桥》,回想当年装文青的情怀。
简简单单、清清爽爽三步搞定
-
文字排版
<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>当前效果如下:
-
文字渲染
<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>当前效果如下:
-
加入动效
<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呈现出来的效果不够丝滑,就不演示放动图了):
完整代码如下(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>