乱码渐进显示多段文字

你已经看到效果了,就是网站首页标语以及页脚的那一行不断变动的小字。

另外:文字可以同时使用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;
}

评论

《“乱码渐进显示多段文字”》 有 1 条评论

  1. 对于每一个字符,它的生命周期是这样的:
    旧文字  -------->  乱码 (#$%@)  -------->  新文字
           [等待阶段]              [跳动阶段]
            长度由 start_time 决定    长度由 end_time 决定
    
    在代码内部,每一个字符都有自己独立的时间轴:
    start_time (启动延迟范围)
    含义:决定了一个字符要等多久才开始变成乱码。
    计算方式:程序会为每个字符随机生成一个 0 到 start_time 之间的数字。
    视觉效果:
    数值小 (如 5):所有字符几乎同时开始变化,整句话“唰”地一下就开始变了。
    数值大 (如 100):字符会陆陆续续地开始变化,有的先变,有的后变,像水流一样慢慢过渡。
    
    end_time (乱码持续范围)
    含义:决定了一个字符在开始变化后,要维持乱码状态多久才变成最终的新文字。
    计算方式:程序会为每个字符随机生成一个 0 到 end_time 之间的数字,加在启动时间后面。
    视觉效果:
    数值小 (如 5):乱码一闪而过,字符很快就定格成新字,感觉很干脆。
    数值大 (如 100):字符会疯狂跳动很久的乱码,像是在艰难地“解密”或“计算”一样。
    

回复 文止墨 取消回复

您的邮箱地址不会被公开。 必填项已用 * 标注