fix: replace scroll-then-snap with direction-based one-screen-at-a-time navigation
This commit is contained in:
parent
7a5430d40a
commit
c705bc96e9
177
index.html
177
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;
|
||||
}
|
||||
});
|
||||
return best;
|
||||
}
|
||||
|
||||
if (nearest) {
|
||||
isSnapping = true;
|
||||
const targetY = nearest.getBoundingClientRect().top + window.scrollY;
|
||||
window.scrollTo({ top: targetY, behavior: 'smooth' });
|
||||
// 吸附完成后解锁
|
||||
setTimeout(() => { isSnapping = false; }, 800);
|
||||
// 切换到指定 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);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
|
|
|
|||
Loading…
Reference in New Issue