联系我们
This commit is contained in:
252
assets/js/timeline.js
Normal file
252
assets/js/timeline.js
Normal file
@@ -0,0 +1,252 @@
|
||||
/**
|
||||
* 智慧时间轴组件 - 横向展开式
|
||||
* Smart Timeline Component - Horizontal Expansion
|
||||
*/
|
||||
|
||||
(function() {
|
||||
'use strict';
|
||||
|
||||
// 等待DOM加载完成
|
||||
document.addEventListener('DOMContentLoaded', initTimeline);
|
||||
|
||||
function initTimeline() {
|
||||
const container = document.querySelector('.timeline-container');
|
||||
if (!container) return;
|
||||
|
||||
const items = container.querySelectorAll('.timeline-item');
|
||||
if (!items.length) return;
|
||||
|
||||
// 入场动画 - 使用 Intersection Observer
|
||||
const observerOptions = {
|
||||
threshold: 0.1,
|
||||
rootMargin: '0px 0px -50px 0px'
|
||||
};
|
||||
|
||||
const observer = new IntersectionObserver((entries) => {
|
||||
entries.forEach(entry => {
|
||||
if (entry.isIntersecting) {
|
||||
animateItems();
|
||||
observer.unobserve(entry.target);
|
||||
}
|
||||
});
|
||||
}, observerOptions);
|
||||
|
||||
observer.observe(container);
|
||||
|
||||
// 节点逐个入场动画
|
||||
function animateItems() {
|
||||
items.forEach((item, index) => {
|
||||
setTimeout(() => {
|
||||
item.style.opacity = '1';
|
||||
item.style.transform = 'translateY(0)';
|
||||
}, index * 80);
|
||||
});
|
||||
}
|
||||
|
||||
// 初始化节点样式(用于动画)
|
||||
items.forEach(item => {
|
||||
item.style.opacity = '0';
|
||||
item.style.transform = 'translateY(20px)';
|
||||
item.style.transition = 'opacity 0.5s ease, transform 0.5s ease, flex 0.5s cubic-bezier(0.25, 0.1, 0.25, 1), background 0.5s ease';
|
||||
});
|
||||
|
||||
// 触摸设备支持
|
||||
let touchStartX = null;
|
||||
let scrollLeft = null;
|
||||
|
||||
container.addEventListener('touchstart', (e) => {
|
||||
touchStartX = e.touches[0].pageX;
|
||||
scrollLeft = container.scrollLeft;
|
||||
}, { passive: true });
|
||||
|
||||
container.addEventListener('touchmove', (e) => {
|
||||
if (!touchStartX) return;
|
||||
const x = e.touches[0].pageX;
|
||||
const walk = (touchStartX - x) * 1.5;
|
||||
container.scrollLeft = scrollLeft + walk;
|
||||
}, { passive: true });
|
||||
|
||||
container.addEventListener('touchend', () => {
|
||||
touchStartX = null;
|
||||
});
|
||||
|
||||
// 鼠标滚轮横向滚动(移动端视图时)
|
||||
container.addEventListener('wheel', (e) => {
|
||||
if (container.scrollWidth > container.clientWidth) {
|
||||
if (Math.abs(e.deltaX) < Math.abs(e.deltaY)) {
|
||||
e.preventDefault();
|
||||
container.scrollLeft += e.deltaY;
|
||||
}
|
||||
}
|
||||
}, { passive: false });
|
||||
|
||||
// 拖拽滚动(移动端视图时)
|
||||
let isDragging = false;
|
||||
let startX;
|
||||
let dragScrollLeft;
|
||||
|
||||
container.addEventListener('mousedown', (e) => {
|
||||
if (container.scrollWidth <= container.clientWidth) return;
|
||||
if (e.target.closest('.timeline-item')) return;
|
||||
isDragging = true;
|
||||
container.style.cursor = 'grabbing';
|
||||
startX = e.pageX - container.offsetLeft;
|
||||
dragScrollLeft = container.scrollLeft;
|
||||
});
|
||||
|
||||
container.addEventListener('mouseleave', () => {
|
||||
isDragging = false;
|
||||
container.style.cursor = '';
|
||||
});
|
||||
|
||||
container.addEventListener('mouseup', () => {
|
||||
isDragging = false;
|
||||
container.style.cursor = '';
|
||||
});
|
||||
|
||||
container.addEventListener('mousemove', (e) => {
|
||||
if (!isDragging) return;
|
||||
e.preventDefault();
|
||||
const x = e.pageX - container.offsetLeft;
|
||||
const walk = (x - startX) * 2;
|
||||
container.scrollLeft = dragScrollLeft - walk;
|
||||
});
|
||||
|
||||
// 节点点击事件(可扩展)
|
||||
items.forEach((item) => {
|
||||
item.addEventListener('click', () => {
|
||||
// 添加点击反馈动画
|
||||
const dot = item.querySelector('.dot');
|
||||
if (dot) {
|
||||
dot.style.transform = 'translate(-50%, -50%) scale(2)';
|
||||
setTimeout(() => {
|
||||
dot.style.transform = '';
|
||||
}, 200);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// 键盘导航支持
|
||||
let currentIndex = 0;
|
||||
|
||||
document.addEventListener('keydown', (e) => {
|
||||
if (!isElementInViewport(container)) return;
|
||||
|
||||
if (e.key === 'ArrowLeft') {
|
||||
currentIndex = Math.max(0, currentIndex - 1);
|
||||
scrollToItem(currentIndex);
|
||||
} else if (e.key === 'ArrowRight') {
|
||||
currentIndex = Math.min(items.length - 1, currentIndex + 1);
|
||||
scrollToItem(currentIndex);
|
||||
}
|
||||
});
|
||||
|
||||
function scrollToItem(index) {
|
||||
if (container.scrollWidth <= container.clientWidth) return;
|
||||
|
||||
const item = items[index];
|
||||
if (!item) return;
|
||||
|
||||
const containerRect = container.getBoundingClientRect();
|
||||
const itemRect = item.getBoundingClientRect();
|
||||
|
||||
const targetScrollLeft = container.scrollLeft + (itemRect.left - containerRect.left) - (containerRect.width / 2) + (itemRect.width / 2);
|
||||
|
||||
container.scrollTo({
|
||||
left: targetScrollLeft,
|
||||
behavior: 'smooth'
|
||||
});
|
||||
}
|
||||
|
||||
// ============================================
|
||||
// 自动轮播功能 - 从左到右循环展开
|
||||
// ============================================
|
||||
let autoPlayIndex = 0;
|
||||
let autoPlayTimer = null;
|
||||
let isUserInteracting = false;
|
||||
const autoPlayInterval = 3000; // 每个节点展开持续时间(毫秒)
|
||||
const pauseAfterInteraction = 5000; // 用户交互后暂停时间(毫秒)
|
||||
|
||||
// 添加自动展开的CSS类
|
||||
function expandItem(index) {
|
||||
// 移除所有节点的展开状态
|
||||
items.forEach(item => {
|
||||
item.classList.remove('auto-expanded');
|
||||
});
|
||||
// 给当前节点添加展开状态
|
||||
if (items[index]) {
|
||||
items[index].classList.add('auto-expanded');
|
||||
}
|
||||
}
|
||||
|
||||
// 自动轮播
|
||||
function autoPlay() {
|
||||
if (isUserInteracting) return;
|
||||
|
||||
expandItem(autoPlayIndex);
|
||||
autoPlayIndex = (autoPlayIndex + 1) % items.length;
|
||||
}
|
||||
|
||||
// 启动自动轮播
|
||||
function startAutoPlay() {
|
||||
if (autoPlayTimer) return;
|
||||
autoPlayTimer = setInterval(autoPlay, autoPlayInterval);
|
||||
// 立即展开第一个
|
||||
autoPlay();
|
||||
}
|
||||
|
||||
// 停止自动轮播
|
||||
function stopAutoPlay() {
|
||||
if (autoPlayTimer) {
|
||||
clearInterval(autoPlayTimer);
|
||||
autoPlayTimer = null;
|
||||
}
|
||||
// 移除所有展开状态
|
||||
items.forEach(item => {
|
||||
item.classList.remove('auto-expanded');
|
||||
});
|
||||
}
|
||||
|
||||
// 用户交互时暂停自动轮播
|
||||
function pauseAutoPlay() {
|
||||
isUserInteracting = true;
|
||||
stopAutoPlay();
|
||||
|
||||
// 一段时间后恢复自动轮播
|
||||
setTimeout(() => {
|
||||
isUserInteracting = false;
|
||||
if (isElementInViewport(container)) {
|
||||
startAutoPlay();
|
||||
}
|
||||
}, pauseAfterInteraction);
|
||||
}
|
||||
|
||||
// 监听用户交互
|
||||
container.addEventListener('mouseenter', pauseAutoPlay);
|
||||
container.addEventListener('touchstart', pauseAutoPlay, { passive: true });
|
||||
|
||||
// 使用 Intersection Observer 控制自动轮播
|
||||
const autoPlayObserver = new IntersectionObserver((entries) => {
|
||||
entries.forEach(entry => {
|
||||
if (entry.isIntersecting && !isUserInteracting) {
|
||||
startAutoPlay();
|
||||
} else {
|
||||
stopAutoPlay();
|
||||
}
|
||||
});
|
||||
}, { threshold: 0.3 });
|
||||
|
||||
autoPlayObserver.observe(container);
|
||||
|
||||
console.log("Timeline Loaded with Enhanced Labels and Positioning");
|
||||
}
|
||||
|
||||
// 工具函数:检查元素是否在视口中
|
||||
function isElementInViewport(el) {
|
||||
const rect = el.getBoundingClientRect();
|
||||
return (
|
||||
rect.top < window.innerHeight &&
|
||||
rect.bottom > 0
|
||||
);
|
||||
}
|
||||
})();
|
||||
Reference in New Issue
Block a user