/** * 智慧时间轴组件 - 横向展开式 * Smart Timeline Component - Horizontal Expansion */ (function() { 'use strict'; document.addEventListener('DOMContentLoaded', initTimeline); function forEachNode(list, callback) { Array.prototype.forEach.call(list, callback); } function supportsSmoothScroll() { return 'scrollBehavior' in document.documentElement.style; } function initTimeline() { var container = document.querySelector('.timeline-container'); if (!container) return; var items = container.querySelectorAll('.timeline-item'); if (!items.length) return; var hasIntersectionObserver = 'IntersectionObserver' in window; var touchStartX = null; var scrollLeft = null; var isDragging = false; var startX; var dragScrollLeft; var currentIndex = 0; var autoPlayIndex = 0; var autoPlayTimer = null; var isUserInteracting = false; var interactionTimer = null; var autoPlayInterval = 3000; var pauseAfterInteraction = 5000; var observerOptions = { threshold: 0.1, rootMargin: '0px 0px -50px 0px' }; function updateExpandedState(active) { if (active) { container.classList.add('has-auto-expanded'); } else { container.classList.remove('has-auto-expanded'); } } function clearExpandedItems() { forEachNode(items, function(item) { item.classList.remove('auto-expanded'); }); updateExpandedState(false); } function animateItems(useDelay) { forEachNode(items, function(item, index) { var delay = useDelay ? index * 80 : 0; setTimeout(function() { item.style.opacity = '1'; item.style.transform = 'translateY(0)'; }, delay); }); } forEachNode(items, function(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'; }); if (hasIntersectionObserver) { var observer = new IntersectionObserver(function(entries) { for (var entryIndex = 0; entryIndex < entries.length; entryIndex += 1) { if (entries[entryIndex].isIntersecting) { animateItems(true); observer.unobserve(entries[entryIndex].target); } } }, observerOptions); observer.observe(container); } else { animateItems(false); } container.addEventListener('touchstart', function(e) { touchStartX = e.touches[0].pageX; scrollLeft = container.scrollLeft; }, { passive: true }); container.addEventListener('touchmove', function(e) { if (!touchStartX) return; var x = e.touches[0].pageX; var walk = (touchStartX - x) * 1.5; container.scrollLeft = scrollLeft + walk; }, { passive: true }); container.addEventListener('touchend', function() { touchStartX = null; }); container.addEventListener('wheel', function(e) { if (container.scrollWidth > container.clientWidth) { if (Math.abs(e.deltaX) < Math.abs(e.deltaY)) { e.preventDefault(); container.scrollLeft += e.deltaY; } } }, { passive: false }); container.addEventListener('mousedown', function(e) { if (container.scrollWidth <= container.clientWidth) return; if (e.target.closest && e.target.closest('.timeline-item')) return; isDragging = true; container.style.cursor = 'grabbing'; startX = e.pageX - container.offsetLeft; dragScrollLeft = container.scrollLeft; }); container.addEventListener('mouseleave', function() { isDragging = false; container.style.cursor = ''; }); container.addEventListener('mouseup', function() { isDragging = false; container.style.cursor = ''; }); container.addEventListener('mousemove', function(e) { if (!isDragging) return; e.preventDefault(); var x = e.pageX - container.offsetLeft; var walk = (x - startX) * 2; container.scrollLeft = dragScrollLeft - walk; }); forEachNode(items, function(item) { item.addEventListener('click', function() { var dot = item.querySelector('.dot'); if (dot) { dot.style.transform = 'translate(-50%, -50%) scale(2)'; setTimeout(function() { dot.style.transform = ''; }, 200); } }); }); document.addEventListener('keydown', function(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; var item = items[index]; if (!item) return; var containerRect = container.getBoundingClientRect(); var itemRect = item.getBoundingClientRect(); var targetScrollLeft = container.scrollLeft + (itemRect.left - containerRect.left) - (containerRect.width / 2) + (itemRect.width / 2); if (typeof container.scrollTo === 'function' && supportsSmoothScroll()) { container.scrollTo({ left: targetScrollLeft, behavior: 'smooth' }); return; } container.scrollLeft = targetScrollLeft; } function expandItem(index) { clearExpandedItems(); if (items[index]) { items[index].classList.add('auto-expanded'); updateExpandedState(true); } } 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; } clearExpandedItems(); } function pauseAutoPlay() { isUserInteracting = true; stopAutoPlay(); if (interactionTimer) { clearTimeout(interactionTimer); } interactionTimer = setTimeout(function() { isUserInteracting = false; if (hasIntersectionObserver && isElementInViewport(container)) { startAutoPlay(); } }, pauseAfterInteraction); } container.addEventListener('mouseenter', pauseAutoPlay); container.addEventListener('touchstart', pauseAutoPlay, { passive: true }); if (hasIntersectionObserver) { var autoPlayObserver = new IntersectionObserver(function(entries) { for (var entryIndex = 0; entryIndex < entries.length; entryIndex += 1) { if (entries[entryIndex].isIntersecting && !isUserInteracting) { startAutoPlay(); } else { stopAutoPlay(); } } }, { threshold: 0.3 }); autoPlayObserver.observe(container); } } function isElementInViewport(el) { var rect = el.getBoundingClientRect(); return ( rect.top < window.innerHeight && rect.bottom > 0 ); } })();