Files
argon-theme/modern-enhancements.js
nanhaoluo 9a19ccb864 feat: 添加现代化布局和动画优化
- 新增 modern-enhancements.css 样式文件
  - Material Design 3 动画系统变量
  - 桌面端卡片悬停效果增强(阴影、位移、缩放)
  - 移动端触摸反馈优化(涟漪效果、按压缩放)
  - 滚动入场动画(文章列表、侧边栏交错入场)
  - 页面过渡效果(PJAX 加载动画)
  - 浮动按钮弹性动画
  - 顶栏滚动效果优化
  - 评论区入场动画
  - 表单元素聚焦动画
  - 滚动条美化
  - 减少动画偏好支持

- 新增 modern-enhancements.js 脚本文件
  - 触摸涟漪效果(移动端)
  - 图片懒加载动画
  - 滚动入场动画(Intersection Observer)
  - 页面加载进度条
  - PJAX 加载动画增强
  - 主题切换过渡动画

- 修改 header.php
  - 引入新的 CSS 和 JS 文件
2026-01-16 00:33:43 +08:00

319 lines
11 KiB
JavaScript

/**
* Argon Theme - Modern UI Enhancements JavaScript
* 现代化交互动画增强
*/
(function() {
'use strict';
// ========== 1. 触摸涟漪效果 ==========
function createRipple(event, element) {
// 只在移动端或触摸设备上启用
if (window.matchMedia('(hover: hover)').matches) return;
const rect = element.getBoundingClientRect();
const ripple = document.createElement('span');
const size = Math.max(rect.width, rect.height);
ripple.style.width = ripple.style.height = size + 'px';
ripple.style.left = (event.clientX || event.touches[0].clientX) - rect.left - size / 2 + 'px';
ripple.style.top = (event.clientY || event.touches[0].clientY) - rect.top - size / 2 + 'px';
ripple.className = 'touch-ripple';
// 移除旧的涟漪
const oldRipple = element.querySelector('.touch-ripple');
if (oldRipple) oldRipple.remove();
element.style.position = 'relative';
element.style.overflow = 'hidden';
element.appendChild(ripple);
// 动画结束后移除
setTimeout(() => ripple.remove(), 600);
}
// 为可点击元素添加涟漪效果
function initRippleEffect() {
const rippleElements = document.querySelectorAll(
'.btn, .card, .nav-link, .dropdown-item, .page-link, ' +
'.leftbar-mobile-menu-item > a, .leftbar-mobile-action, ' +
'.fabtn, .comment-reply, .tag, .badge'
);
rippleElements.forEach(el => {
if (el.dataset.rippleInit) return;
el.dataset.rippleInit = 'true';
el.addEventListener('touchstart', function(e) {
createRipple(e, this);
}, { passive: true });
});
}
// ========== 2. 图片加载动画 ==========
function initImageLoadAnimation() {
const images = document.querySelectorAll('article img[loading="lazy"], .post-thumbnail img');
images.forEach(img => {
if (img.dataset.loadAnimInit) return;
img.dataset.loadAnimInit = 'true';
if (img.complete) {
img.classList.add('loaded');
} else {
img.addEventListener('load', function() {
this.classList.add('loaded');
});
}
});
}
// ========== 3. 滚动入场动画 (Intersection Observer) ==========
function initScrollAnimations() {
if (!('IntersectionObserver' in window)) return;
const animatedElements = document.querySelectorAll(
'.article-list article.post, .comment-item, .timeline-item, ' +
'.friend-link-item, #leftbar .card, #rightbar .card'
);
const observer = new IntersectionObserver((entries) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
entry.target.classList.add('animate-in');
observer.unobserve(entry.target);
}
});
}, {
threshold: 0.1,
rootMargin: '0px 0px -50px 0px'
});
animatedElements.forEach(el => {
if (!el.classList.contains('animate-in')) {
observer.observe(el);
}
});
}
// ========== 4. 平滑滚动增强 ==========
function initSmoothScroll() {
document.querySelectorAll('a[href^="#"]').forEach(anchor => {
if (anchor.dataset.smoothScrollInit) return;
anchor.dataset.smoothScrollInit = 'true';
anchor.addEventListener('click', function(e) {
const targetId = this.getAttribute('href');
if (targetId === '#') return;
const target = document.querySelector(targetId);
if (target) {
e.preventDefault();
target.scrollIntoView({
behavior: 'smooth',
block: 'start'
});
}
});
});
}
// ========== 5. 页面加载进度条 ==========
function initLoadingBar() {
// 检查是否已存在
if (document.getElementById('page-loading-bar')) return;
const bar = document.createElement('div');
bar.id = 'page-loading-bar';
bar.style.width = '0%';
document.body.appendChild(bar);
// 模拟加载进度
let progress = 0;
const interval = setInterval(() => {
progress += Math.random() * 10;
if (progress >= 90) {
clearInterval(interval);
progress = 90;
}
bar.style.width = progress + '%';
}, 100);
// 页面加载完成
window.addEventListener('load', () => {
clearInterval(interval);
bar.style.width = '100%';
setTimeout(() => {
bar.style.opacity = '0';
setTimeout(() => bar.remove(), 300);
}, 200);
});
}
// ========== 6. PJAX 加载动画 ==========
function initPjaxAnimations() {
if (typeof jQuery === 'undefined') return;
jQuery(document).on('pjax:start', function() {
jQuery('#primary').addClass('pjax-loading');
// 显示加载进度条
let bar = document.getElementById('page-loading-bar');
if (!bar) {
bar = document.createElement('div');
bar.id = 'page-loading-bar';
document.body.appendChild(bar);
}
bar.style.opacity = '1';
bar.style.width = '30%';
setTimeout(() => bar.style.width = '60%', 200);
});
jQuery(document).on('pjax:end', function() {
jQuery('#primary').removeClass('pjax-loading');
const bar = document.getElementById('page-loading-bar');
if (bar) {
bar.style.width = '100%';
setTimeout(() => {
bar.style.opacity = '0';
setTimeout(() => bar.remove(), 300);
}, 200);
}
// 重新初始化动画
setTimeout(() => {
initRippleEffect();
initImageLoadAnimation();
initScrollAnimations();
initSmoothScroll();
}, 100);
});
}
// ========== 7. 卡片倾斜效果 (桌面端) ==========
function initCardTiltEffect() {
if (!window.matchMedia('(hover: hover)').matches) return;
const cards = document.querySelectorAll('article.post.card');
cards.forEach(card => {
if (card.dataset.tiltInit) return;
card.dataset.tiltInit = 'true';
card.addEventListener('mousemove', function(e) {
const rect = this.getBoundingClientRect();
const x = e.clientX - rect.left;
const y = e.clientY - rect.top;
const centerX = rect.width / 2;
const centerY = rect.height / 2;
const rotateX = (y - centerY) / 20;
const rotateY = (centerX - x) / 20;
this.style.transform = `perspective(1000px) rotateX(${rotateX}deg) rotateY(${rotateY}deg) translateY(-6px)`;
});
card.addEventListener('mouseleave', function() {
this.style.transform = '';
});
});
}
// ========== 8. 浮动按钮显示/隐藏动画 ==========
function initFabAnimation() {
const fab = document.getElementById('float_action_buttons');
if (!fab) return;
let lastScrollY = window.scrollY;
let ticking = false;
window.addEventListener('scroll', function() {
if (!ticking) {
window.requestAnimationFrame(function() {
const currentScrollY = window.scrollY;
// 向下滚动时隐藏,向上滚动时显示
if (currentScrollY > lastScrollY && currentScrollY > 300) {
fab.style.transform = 'translateY(100px)';
fab.style.opacity = '0';
} else {
fab.style.transform = '';
fab.style.opacity = '';
}
lastScrollY = currentScrollY;
ticking = false;
});
ticking = true;
}
}, { passive: true });
}
// ========== 9. 主题切换动画增强 ==========
function initThemeTransition() {
// 监听暗色模式切换
const observer = new MutationObserver(function(mutations) {
mutations.forEach(function(mutation) {
if (mutation.attributeName === 'class') {
const html = document.documentElement;
if (html.classList.contains('darkmode') || !html.classList.contains('darkmode')) {
// 添加过渡类
html.classList.add('theme-transitioning');
setTimeout(() => {
html.classList.remove('theme-transitioning');
}, 300);
}
}
});
});
observer.observe(document.documentElement, {
attributes: true,
attributeFilter: ['class']
});
}
// ========== 10. 减少动画偏好检测 ==========
function checkReducedMotion() {
if (window.matchMedia('(prefers-reduced-motion: reduce)').matches) {
document.documentElement.classList.add('reduced-motion');
}
}
// ========== 初始化 ==========
function init() {
checkReducedMotion();
initRippleEffect();
initImageLoadAnimation();
initScrollAnimations();
initSmoothScroll();
initPjaxAnimations();
initThemeTransition();
// 桌面端特效
if (window.matchMedia('(hover: hover)').matches) {
// initCardTiltEffect(); // 可选:卡片倾斜效果
}
// 移动端特效
if (!window.matchMedia('(hover: hover)').matches) {
// initFabAnimation(); // 可选:浮动按钮滚动隐藏
}
}
// DOM 加载完成后初始化
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', init);
} else {
init();
}
// 首次加载时显示进度条
if (document.readyState !== 'complete') {
initLoadingBar();
}
})();