你已经看到效果了,就是网站首页标语以及页脚的那一行不断变动的小字。
另外:文字可以同时使用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;
}


回复 文止墨 取消回复