乱码渐进显示多段文字

22,354字

(不考虑代码影响的情况下)

95–142 分钟

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

另外:文字可以同时使用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,
      loop: true
  });
</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;
            /* 默认居中*/
            transition: height 0.3s ease;
            overflow: hidden;
            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)]
            }
        }

        // --- 工厂函数:createGlitch ---
        function createGlitch(selector, options) {
            const el = document.querySelector(selector);
            if (!el) return;

            // 1. 先应用基础样式,确保字体大小等影响高度的属性已生效
            if (options.color) el.style.color = options.color;
            if (options.fontFamily) el.style.fontFamily = options.fontFamily;
            if (options.fontSize) el.style.fontSize = options.fontSize;
            if (options.fontWeight) el.style.fontWeight = options.fontWeight;

            const phrases = options.phrases || ["Text"];

            // ==========================================
            // 预计算最大高度以防止抖动
            // ==========================================
            if (phrases.length > 0) {
                // 创建幽灵元素
                const ghost = el.cloneNode(true);
                // 设置为不可见但占位,或者绝对定位移除文档流进行测量
                ghost.style.visibility = 'hidden';
                ghost.style.position = 'absolute';
                ghost.style.top = '-9999px';
                ghost.style.left = '-9999px';
                ghost.style.width = getComputedStyle(el).width; // 确保宽度一致,以便计算自动换行
                ghost.style.height = 'auto';
                ghost.style.minHeight = '0'; // 重置可能存在的限制

                // 插入到父级以确保继承上下文样式
                el.parentNode.appendChild(ghost);

                let maxHeight = 0;

                phrases.forEach(phrase => {
                    ghost.innerHTML = phrase;
                    // 强制渲染并获取高度
                    const h = ghost.offsetHeight;
                    if (h > maxHeight) {
                        maxHeight = h;
                    }
                });

                // 移除幽灵元素
                el.parentNode.removeChild(ghost);

                // 锁定真实元素的高度
                // 使用 min-height 可以防止内容被截断,同时保证了最小占位
                // 如果想强制固定,可以用 el.style.height
                el.style.minHeight = `${maxHeight}px`;

                // 也可以选择用 height 固定死,配合 CSS transition
                // el.style.height = `${maxHeight}px`;
            }

            const fx = new TextScramble(el, options.obfu_chars);
            let counter = 0;
            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>

引用版(第二版)

再次提醒:考虑到本站运行时间不定以及捉襟见肘的带宽资源,保险的办法是将脚本下载到本地或上传到你自己的服务器使用。

此外,由于字体注入属于敏感行为,浏览器会判断脚本是否与页面同源:因此我推荐采取“云端页面”+“云端脚本”或“本地页面”+“本地脚本”的形式使用。当使用本地页面云端脚本时,很可能会字体加载失败(但其他功能不受影响)。

本地用户如何使用云端页面:下载VS Code,然后下载Live Preview插件,根据插件说明操作或向AI询问如何操作。

HTML
<!DOCTYPE html>
<html lang="zh-CN">
<!--V2版取消了依赖页面CSS设置,改为初始化时用脚本单独注入属性。此外,还支持使用网络字体-->

<head>
    <meta charset="UTF-8">
    <title>Glitch Text Demo</title>

    <style>
        body {
            background-color: #111;
            color: #fff;
            margin: 0;
            min-height: 100vh;

            display: flex;
            flex-direction: column;
            justify-content: center;
            align-items: center;
        }

        /* 乱码字符样式(TextScramble 使用) */
        .dud {
            opacity: 0.6;
        }
    </style>
</head>

<body>

    <!-- 示例容器 -->
    <div id="demo1"></div>
    <div id="demo2"></div>
    <div id="demo3"></div>
    <div id="demo4"></div>

    <!-- 核心脚本 -->
    <script src="https://www.wenzhimo.xyz/wp-content/uploads/2026/02/glitch-text.js"></script>

    <!-- 实例初始化 -->
    <script>
        // 示例 1:Google Fonts(中文)
        createGlitch('#demo1', {
            phrases: [
                "今天你领工资了吗?",
                "工资是否按时发放",
                "SCP-CN-40K 记录终止"
            ],
            obfu_chars: "░▒▓█▓▒░",
            webFont: {
                name: 'ZCOOL QingKe HuangYou',
                url: 'https://fonts.googleapis.com/css2?family=ZCOOL+QingKe+HuangYou&display=swap'
            },
            color: "#ff3333",
            fontSize: "36px",
            fontWeight: "700",
            start_time: 40,
            end_time: 40,
            disp_time: 1500,
            delay: 0,
            loop: true
        });

        // 示例 2:woff2 直链字体
        createGlitch('#demo2', {
            phrases: [
                "console.log('Hello World');",
                "System.out.println('Error');",
                "rm -rf /"
            ],
            obfu_chars: "{}[]<>/\\!@#",
            webFont: {
                name: 'Hanalei Fill',
                url: 'https://fonts.gstatic.font.im/s/hanaleifill/v23/fC1mPYtObGbfyQznIaQzPQi8UAjA.woff2'
            },
            color: "#00ff00",
            fontSize: "18px",
            fontWeight: "400",
            start_time: 200,
            end_time: 0,
            disp_time: 800,
            delay: 500,
            loop: true
        });

        // 示例 3:仅使用本地字体
        createGlitch('#demo3', {
            phrases: [
                "DESIGN",
                "DEVELOPMENT",
                "FUTURE"
            ],
            obfu_chars: "†‡µ±≠≈",
            fontFamily: "Arial, Helvetica, sans-serif",
            color: "#a64dff",
            fontSize: "32px",
            fontWeight: "400",
            start_time: 60,
            end_time: 60,
            disp_time: 2500,
            delay: 1000,
            loop: true
        });

        // 示例 4:多行文本 + HTML 标签
        createGlitch('#demo4', {
            phrases: [
                "正常的第一行<br>正常的第二行",
                "长文本测试<br>使用标签换行<br>再换一行",
                "使用换行符\n这里是第二行\n以及第三行"
            ],
            obfu_chars: "░▒▓",
            webFont: {
                name: 'Noto Serif SC',
                url: 'https://fonts.googleapis.com/css2?family=Noto+Serif+SC:wght@400;700&display=swap'
            },
            color: "#ffffff",
            fontSize: "28px",
            fontWeight: "400",
            start_time: 40,
            end_time: 150,
            disp_time: 2000,
            delay: 0,
            loop: true
        });
    </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;
}
22,354字
95–142 分钟

评论

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

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

发表回复

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