diff --git a/index.html b/index.html index cacdd8b..ca0bfe0 100644 --- a/index.html +++ b/index.html @@ -974,106 +974,115 @@ // 移动端:入场动画 + 视差滚动 if (window.innerWidth <= 900) { - // 移除 CSS scroll-snap(不可靠),用 JS 实现 - document.body.style.scrollSnapType = 'none'; - document.querySelectorAll('[style*="scroll-snap"]').forEach(el => { - el.style.scrollSnapType = 'none'; - el.style.scrollSnapAlign = 'none'; - el.style.scrollSnapStop = 'normal'; - }); - - // 1. 入场动画(一次性) - const mobileItems = document.querySelectorAll('#pain-points .pain-item, #approach .approach-item'); - const mobileObserver = new IntersectionObserver((entries) => { - entries.forEach(entry => { - if (entry.isIntersecting) entry.target.classList.add('in-view'); - }); - }, { threshold: 0.05, rootMargin: "0px 0px -5% 0px" }); - mobileItems.forEach(item => mobileObserver.observe(item)); - - // 2. 数字编号滚动视差(基于卡片相对视口位置) - let ticking = false; - const painNums = document.querySelectorAll('#pain-points .pain-num'); - - function updateParallax() { - painNums.forEach(num => { - const card = num.parentElement; - if (!card) return; - const rect = card.getBoundingClientRect(); - const speed = 0.15; - const yOffset = -(rect.top * speed); - num.style.transform = `translateY(${yOffset}px)`; - }); - ticking = false; - } - - window.addEventListener('scroll', () => { - if (!ticking) { - requestAnimationFrame(updateParallax); - ticking = true; - } - }, { passive: true }); - updateParallax(); - - // 3. JS 逐屏吸附 - // 收集所有吸附点 + // 3. 逐屏滚动 - 方向感知,禁止自由滚动 const snapTargets = []; - // Hero const hero = document.querySelector('.hero'); if (hero) snapTargets.push(hero); - // 痛点每个卡片 document.querySelectorAll('#pain-points .pain-item').forEach(item => snapTargets.push(item)); - // 其他 section ['solutions', 'approach', 'geo', 'contact'].forEach(id => { const el = document.getElementById(id); if (el) snapTargets.push(el); }); - let scrollEndTimer = null; - let isSnapping = false; + let currentIndex = 0; + let isAnimating = false; let touchStartY = 0; - let lastScrollTop = 0; + let touchStartX = 0; + let animFrame = null; - document.addEventListener('touchstart', (e) => { - touchStartY = e.touches[0].clientY; - }, { passive: true }); - - window.addEventListener('scroll', () => { - // 清除之前的定时器 - clearTimeout(scrollEndTimer); - // 300ms 无滚动视为停止 - scrollEndTimer = setTimeout(() => { - if (isSnapping) return; // 正在吸附中不触发 - snapToNearest(); - }, 200); - lastScrollTop = window.scrollY; - }, { passive: true }); - - function snapToNearest() { - const vh = window.innerHeight; - const center = window.scrollY + vh * 0.4; // 吸附点偏上 40% 位置 - - let nearest = null; - let minDist = Infinity; - - snapTargets.forEach(target => { - const rect = target.getBoundingClientRect(); - const top = rect.top + window.scrollY; - const dist = Math.abs(top - center); - if (dist < minDist) { - minDist = dist; - nearest = target; + // 找到离视口顶部最近的 section 索引 + function findCurrentIndex() { + let best = 0; + let bestDist = Infinity; + snapTargets.forEach((target, i) => { + const top = target.getBoundingClientRect().top + window.scrollY; + const dist = Math.abs(top - window.scrollY); + if (dist < bestDist) { + bestDist = dist; + best = i; } }); - - if (nearest) { - isSnapping = true; - const targetY = nearest.getBoundingClientRect().top + window.scrollY; - window.scrollTo({ top: targetY, behavior: 'smooth' }); - // 吸附完成后解锁 - setTimeout(() => { isSnapping = false; }, 800); - } + return best; } + + // 切换到指定 section + function goTo(index) { + if (index < 0 || index >= snapTargets.length || isAnimating) return; + isAnimating = true; + const targetY = snapTargets[index].getBoundingClientRect().top + window.scrollY; + const startY = window.scrollY; + const diff = targetY - startY; + const duration = 600; + const startTime = performance.now(); + + // 自定义缓动:先快后慢,有惯性感觉 + function easeOutCubic(t) { + return 1 - Math.pow(1 - t, 3); + } + + function step(now) { + const elapsed = now - startTime; + const progress = Math.min(elapsed / duration, 1); + const eased = easeOutCubic(progress); + window.scrollTo(0, startY + diff * eased); + if (progress < 1) { + animFrame = requestAnimationFrame(step); + } else { + isAnimating = false; + currentIndex = index; + } + } + animFrame = requestAnimationFrame(step); + } + + // 初始化 + currentIndex = findCurrentIndex(); + + // 完全接管触摸滚动 - 禁止原生滚动 + let canScroll = true; + document.addEventListener('touchmove', (e) => { + e.preventDefault(); + }, { passive: false }); + + document.addEventListener('touchstart', (e) => { + if (!canScroll || isAnimating) return; + touchStartY = e.touches[0].clientY; + touchStartX = e.touches[0].clientX; + }, { passive: true }); + + document.addEventListener('touchend', (e) => { + if (!canScroll || isAnimating) return; + const dy = touchStartY - e.changedTouches[0].clientY; + const dx = touchStartX - e.changedTouches[0].clientX; + + // 水平滑动不处理(可能是浏览器前进后退手势) + if (Math.abs(dx) > Math.abs(dy)) return; + // 最小滑动阈值,防止误触 + if (Math.abs(dy) < 40) return; + + if (dy > 0) { + // 上滑 -> 下一屏 + goTo(currentIndex + 1); + } else { + // 下滑 -> 上一屏 + goTo(currentIndex - 1); + } + }, { passive: true }); + + // 导航链接点击:定位到对应 section 并更新索引 + document.querySelectorAll('.nav-links a, .btn').forEach(link => { + link.addEventListener('click', (e) => { + const href = link.getAttribute('href'); + if (!href || !href.startsWith('#')) return; + const targetId = href.substring(1); + const idx = snapTargets.findIndex(t => t.id === targetId); + if (idx >= 0) { + e.preventDefault(); + currentIndex = idx; + goTo(idx); + } + }); + }); }