美化首页样式,同时小小设计了一下文章和评论的样式

This commit is contained in:
2026-02-08 23:18:56 +08:00
parent a36c91a762
commit 3a19f3c311
13 changed files with 1407 additions and 1047 deletions
+43
View File
@@ -0,0 +1,43 @@
const langToggle = document.querySelector('.lang-toggle');
const body = document.body;
function getPreferredLang() {
const savedLang = localStorage.getItem('language');
if (savedLang) {
return savedLang;
}
return 'zh';
}
function setLang(lang) {
if (lang === 'en') {
body.setAttribute('lang', 'en');
if (langToggle) {
langToggle.querySelector('.lang-text').textContent = '中';
}
} else {
body.setAttribute('lang', 'zh');
if (langToggle) {
langToggle.querySelector('.lang-text').textContent = 'EN';
}
}
// 处理 Input 元素的值切换 (input[type=submit] cannot use CSS content replacement)
const inputs = document.querySelectorAll('input[type="submit"][data-cn][data-en]');
inputs.forEach(input => {
input.value = lang === 'en' ? input.getAttribute('data-en') : input.getAttribute('data-cn');
});
localStorage.setItem('language', lang);
}
const initialLang = getPreferredLang();
setLang(initialLang);
if (langToggle) {
langToggle.addEventListener('click', () => {
const currentLang = body.getAttribute('lang');
const newLang = currentLang === 'en' ? 'zh' : 'en';
setLang(newLang);
});
}
+100
View File
@@ -0,0 +1,100 @@
document.addEventListener('DOMContentLoaded', function() {
const container = document.querySelector('.hero-description');
if (!container) return;
const cnText = container.getAttribute('data-cn') || '';
const enText = container.getAttribute('data-en') || '';
// 标记动画是否已经完整播放过一次
let hasPlayedOnce = false;
// 存储当前的定时器,以便清理
let timeouts = [];
function getCurrentText() {
return document.body.getAttribute('lang') === 'en' ? enText : cnText;
}
function clearTimeouts() {
timeouts.forEach(t => clearTimeout(t));
timeouts = [];
}
// 渲染文本的核心函数
// text: 要显示的文本
// animate: 是否需要逐字流式动画
function render(text, animate) {
clearTimeouts();
// 使用 Array.from 正确处理 Unicode 字符
const chars = Array.from(text);
// 生成 HTML:每个字符包裹在 span 中
// 如果需要动画,初始透明度为 0
// 如果不需要动画(切换语言时),初始透明度直接为 1
const initialOpacity = animate ? '0' : '1';
// 生成 spans 字符串
const html = chars.map(char => {
// 对空格也可以处理,虽然空格看不见
return `<span class="stream-char" style="opacity: ${initialOpacity};">${char}</span>`;
}).join('');
// 一次性插入 DOM,这时容器会被透明字符撑开,布局位置固定
container.innerHTML = html;
// 如果不需要动画,直接结束
if (!animate) {
return;
}
// 开始执行流式淡入动画
const spans = container.querySelectorAll('.stream-char');
// 基础延迟累加器
let accumulatedDelay = 0;
spans.forEach((span, index) => {
// 模拟流式生成的随机节奏
// 基础 20ms,随机增加 0-30ms
const step = 20 + Math.random() * 30;
accumulatedDelay += step;
// 遇到标点符号稍微停顿一下
if (/[,.。;!?:]/.test(span.textContent)) {
accumulatedDelay += 100;
}
const t = setTimeout(() => {
span.style.opacity = '1';
// 当最后一个字符显示完毕,标记动画已播放
if (index === spans.length - 1) {
hasPlayedOnce = true;
}
}, accumulatedDelay);
timeouts.push(t);
});
}
// 监听语言切换
const observer = new MutationObserver(function(mutations) {
mutations.forEach(function(mutation) {
if (mutation.type === 'attributes' && mutation.attributeName === 'lang') {
const newText = getCurrentText();
// 需求:如果动画已经播放完成过一次,切换语言就不再播放
// 注意:如果用户在动画还没播放完时就切换语言,为了体验,通常也应该直接显示新语言(视为打断),防止混乱
// 所以只要是切换语言,我们将动画设为 false (除非你想每次刷新页面都看)
// 既然用户强调 "如果动画已经播放完成过一次...就不应该再播放",隐含初次加载要播放。
// 我们这里处理逻辑:切换语言时,强制不播放动画 (直接显示),并标记 hasPlayedOnce = true (以防万一)
hasPlayedOnce = true;
render(newText, false);
}
});
});
observer.observe(document.body, { attributes: true });
// 页面首次加载:播放动画
render(getCurrentText(), true);
});