你已经看到效果了,就是网站首页标语以及页脚的那一行不断变动的小字。
另外:文字可以同时使用HTML标签和换行符\n;如果你想要换行,那你最好使用\n而不是<br>,后者会导致句子在所有字符都变换完成后才换行(见下文第四个示例);且与HTML默认行为不同,你的多个空格不会合并为一个空格而是原封不动的展现出来(见示例五),这可能会帮助你排版,也可能反过来。
另外的另外:本博客所有页面均引用了此脚本,因此你只需通过以下代码即可创建一个实例:
HTML
<div id="你的id名" class="text-container"></div>
<script type="text/javascript">
createGlitch('#你的id名', {
phrases: ["文于止墨", "热爱创造的极地空想家", ],
obfu_chars: "░▒▓▖▗▘▙▚▛▜▝▞▟",
color: "#ffff",
fontFamily: "FZXS12", // 设置字体
fontSize: "23px", // 设置大小
fontWeight: "bold",
disp_time: 2500,
start_time: 80,
end_time: 150
});
</script>示例一
示例二
示例三
示例四
示例五
空几行以免示例四换行跳来跳去的烦
HTML
/* 来自https://scp-wiki-cn.wikidot.com/cn40raphmandas,又名“今天你领工资了吗” */
/*代码经过AI改动,适配多实例*/
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>Advanced Text Animation</title>
<style>
body {
background-color: #111;
color: #fff;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
min-height: 100vh;
margin: 0;
}
.text-container {
white-space: pre-wrap;
/* 默认居中,具体样式由 JS 覆盖 */
text-align: center;
margin: 15px 0;
min-height: 1.5em; /* 防止文字切换时高度塌陷 */
line-height: 1.2;
}
/* 乱码字符的通用样式 */
.dud {
opacity: 0.6;
}
</style>
</head>
<body>
<!-- 实例容器 -->
<div id="demo1" class="text-container"></div>
<div id="demo2" class="text-container"></div>
<div id="demo3" class="text-container"></div>
<div id="demo4" class="text-container"></div>
<div id="demo5" class="text-container"></div>
<script type="text/javascript">
// --- 核心类:TextScramble ---
class TextScramble {
constructor(el, chars) {
this.el = el
this.chars = chars || "░▒▓▖▗▘▙▚▛▜▝▞▟"
this.update = this.update.bind(this)
}
setText(newHtml, config) {
const oldText = this.el.innerText
// 1. 创建一个临时的 DOM 元素来提取纯文本
const tempDiv = document.createElement('div');
tempDiv.innerHTML = newHtml;
// 2. 获取“肉眼可见”的文本 (浏览器会自动把 <br> 转为 \n)
const targetVisibleText = tempDiv.innerText;
const length = Math.max(oldText.length, targetVisibleText.length)
const promise = new Promise((resolve) => this.resolve = resolve)
this.queue = []
// 保存最终的 HTML,以便动画结束时还原
this.finalHtml = newHtml;
const start_time = config.start_time || 40
const end_time = config.end_time || 40
for (let i = 0; i < length; i++) {
const from = oldText[i] || ''
// 注意:这里我们现在的目标是 targetVisibleText,而不是含有标签的 newHtml
const to = targetVisibleText[i] || ''
const start = Math.floor(Math.random() * start_time)
const end = start + Math.floor(Math.random() * end_time)
this.queue.push({ from, to, start, end })
}
cancelAnimationFrame(this.frameRequest)
this.frame = 0
this.update()
return promise
}
update() {
let output = ''
let complete = 0
for (let i = 0, n = this.queue.length; i < n; i++) {
let { from, to, start, end, char } = this.queue[i]
if (this.frame >= end) {
complete++
output += to
} else if (this.frame >= start) {
if (!char || Math.random() < 0.28) {
char = this.randomChar()
this.queue[i].char = char
}
output += `<span class="dud">${char}</span>`
} else {
output += from
}
}
// 在动画进行时,我们只设置纯文本内容的 HTML (包含 class="dud" span)
this.el.innerHTML = output
if (complete === this.queue.length) {
// 动画完全结束时,瞬间替换回带有 <br> 或 <span style...> 的完整 HTML
this.el.innerHTML = this.finalHtml;
this.resolve()
} else {
this.frameRequest = requestAnimationFrame(this.update)
this.frame++
}
}
randomChar() {
return this.chars[Math.floor(Math.random() * this.chars.length)]
}
}
// 工厂函数
function createGlitch(selector, options) {
const el = document.querySelector(selector);
if (!el) return;
if (options.color) el.style.color = options.color;
if (options.fontFamily) el.style.fontFamily = options.fontFamily;
if (options.fontSize) el.style.fontSize = options.fontSize;
const fx = new TextScramble(el, options.obfu_chars);
let counter = 0;
const phrases = options.phrases || ["Text"];
const disp_time = options.disp_time || 2000;
const loop = options.loop !== false;
const next = () => {
fx.setText(phrases[counter], {
start_time: options.start_time,
end_time: options.end_time
}).then(() => {
setTimeout(next, disp_time);
});
if (!loop && counter < phrases.length) {
counter = counter + 1;
} else if (counter <= phrases.length) {
counter = (counter + 1) % phrases.length;
}
};
setTimeout(next, options.delay || 0);
}
// ==========================================
// 自定义配置区域
// ==========================================
// 实例 1:大标题,衬线体,红色
createGlitch('#demo1', {
phrases: ["SCP-CN-40K", "今天你领工资了吗?", "不是?这<del><em>他妈</em></del>几个意思?"],
obfu_chars: "░▒▓█▓▒░",
color: "#ff3333",
fontFamily: "'Times New Roman', serif", // 设置字体
fontSize: "48px", // 设置大小
fontWeight: "bold",
disp_time: 1500,
start_time: 30,
end_time: 30
});
// 实例 2:代码风格,等宽字体,绿色,小字体
createGlitch('#demo2', {
phrases: [
"console.log('Hello World');",
"System.out.println('Error');",
"rm -rf /"
],
obfu_chars: "{}[]<>/\\!@#",
color: "#00ff00",
fontFamily: "'Courier New', monospace", // 设置字体
fontSize: "16px", // 设置大小
disp_time: 800,
start_time: 200,
end_time: 0,
delay: 500
});
// 实例 3:现代风格,无衬线,紫色,中等大小
createGlitch('#demo3', {
phrases: ["DESIGN", "DEVELOPMENT", "FUTURE"],
obfu_chars: "†‡µ±≠≈",
color: "#a64dff",
fontFamily: "Arial, Helvetica, sans-serif", // 设置字体
fontSize: "32px", // 设置大小
fontWeight: "normal",
disp_time: 2500,
start_time: 60,
end_time: 60,
delay: 1000
});
// 实例 4:演示 <br> 换行
createGlitch('#demo4', {
phrases: [
"正常的第一行<br>正常的第二行", // 这里的 <br> 在动画时不会暴露,结束时会生效
"长文本测试<br>使用标签换行<br>再换一行",
"使用换行符\\n换行\n这里是第二行\n以及第三行等等等等……"
],
color: "#ff3333",
fontSize: "28px",
start_time: 40,
end_time: 150
});
// 实例 5:演示样式标签
createGlitch('#demo5', {
phrases: [
"普通文字",
"接下来是<span style='color: yellow;'>黄色高亮</span>文字", // 动画时显示白色,结束瞬间变黄
"甚至可以<span style='background: white; color: black;'> 反色显示 </span>",
"很多 空 格 也会被保留"
],
color: "#44ff44",
fontSize: "24px",
start_time: 40,
end_time: 40,
delay: 500
});
</script>
</body>
</html>下面是C语言版本:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <time.h>
struct Text_Animation{
char obfu_chars[20]; //过渡用乱码字符串
int start_tim; //多少帧后开始渐变
int end_tim; //多少帧后结束渐变
int disp_time; //每句话显示帧数
int loop; //是否循环显示
int idx; //当前显示的是第几句话
int fram; //当前是第几帧
char phrases[10][100]; //要显示的话
char showstr[100]; //当前显示的内容
};
struct task{
char from; //原字符
char to; //目标字符
int start; //开始时间
int end; //结束时间
};
struct task tasks[100];
struct Text_Animation t={
.obfu_chars = "asdfghjklqwertyuiop",
.start_tim = 0,
.end_tim = 40,
.disp_time = 80,
.loop = 1,
.idx = 0,
.fram = 0,
.phrases = {"Hello, world!\0", "This is a test1.", "dkajhsgdfkjhgasdfash2.", "我也不知道我要说什么.", "This is a test4.\0", "This is a test5.\0", "This is a test6.\0", "This is a test7.\0", "This is a test8.\0", "This is a test9.\0"},
.showstr = {'1','3','2','1','1','1','1','1',},
};
int Random(int n) { //以当前时间(秒为单位)为种子生成一个0~99的随机数
static unsigned int t = 0 ;
t++;
if(t>=9999)t=0;
srand((unsigned)time(NULL)+n+t);
return rand() % 100;
}
void set_tasks(struct Text_Animation text){//设置任务,比较两个字符串的不同字符,并记录下来,用于后面动画显示%text.end_tim
static int r =0;
int new_len = strlen(text.phrases[text.idx]);
int old_len = strlen(text.phrases[text.idx - 1]);
int lenth = new_len > old_len ? new_len : old_len;
for(int i=0;i<lenth;i++){
if(text.showstr[i] != text.phrases[text.idx][i]){
tasks[i].from = text.showstr[i];
tasks[i].to = text.phrases[text.idx][i];
tasks[i].start = Random(r++) % 10 ;
tasks[i].end = Random(r++) % text.end_tim;
}else{
tasks[i].from = text.showstr[i];
tasks[i].to = text.phrases[text.idx][i];
tasks[i].start = 0;
tasks[i].end = 0;
}
//printf("taks:%d\n",i);
}
tasks[lenth].from = '\0';
tasks[lenth].to = '\0';
tasks[lenth].start = 0;
tasks[lenth].end = 0;
}
void update_showstr(struct Text_Animation *text){//更新显示字符串
char new_str[100];
text->fram++;//更新帧数
//printf("%d\n",text->fram);
if(text->fram > text->disp_time){//如果超过显示时间,则切换到下一句话
text->fram = 0;
for(int i=strlen(text->phrases[text->idx]);i<100;i++){
text->showstr[i] =' ';
}
do{
text->idx++;
}while(text->phrases[text->idx][0] == '\0' && text->idx < 10);
if(text->idx >= 10){
if(text->loop){
text->idx = 0;
}else{
return;
}
}
set_tasks(*text);
}
int new_len = strlen(text->phrases[text->idx]);
int old_len = strlen(text->showstr);
int lenth = new_len > old_len ? new_len : old_len;
for(int i=0;i<100;i++){
new_str[i] = text->showstr[i];//复制当前显示字符串
}
for(int i=0;i<lenth;i++){//遍历任务数组,如果任务开始时间在当前帧数之前,且当前帧数在任务结束时间之前,则显示乱码字符
if(tasks[i].start <= text->fram && text->fram <= tasks[i].end){
if(Random(i+text->fram)<70){
new_str[i] = text->obfu_chars[Random(i+text->fram)%3];
}
}else if(tasks[i].end < text->fram){
new_str[i] = tasks[i].to;
}
}
for(int i=0;i<100;i++){
text->showstr[i] = new_str[i];//更新显示字符串
}
}
int main(){
set_tasks(t);
Random(5);
while(1){
system("cls");
update_showstr(&t);
printf("%s",t.showstr);
usleep(10);
}
return 0;
}


发表回复