fix: replace CSS scroll-snap with JS-based snap for reliable mobile behavior
This commit is contained in:
parent
e38cfed764
commit
7a5430d40a
91
index.html
91
index.html
|
|
@ -446,19 +446,10 @@
|
||||||
|
|
||||||
/* 移动端痛点沉浸式布局 */
|
/* 移动端痛点沉浸式布局 */
|
||||||
@media (max-width: 900px) {
|
@media (max-width: 900px) {
|
||||||
/* 逐屏吸附:整个页面每次滚动就是一屏 */
|
|
||||||
body {
|
|
||||||
scroll-snap-type: y mandatory;
|
|
||||||
scroll-behavior: smooth;
|
|
||||||
}
|
|
||||||
.hero, #solutions, #approach, #geo, #contact, footer {
|
|
||||||
scroll-snap-align: start;
|
|
||||||
}
|
|
||||||
/* 痛点半块单列全屏 */
|
/* 痛点半块单列全屏 */
|
||||||
#pain-points {
|
#pain-points {
|
||||||
overflow: visible;
|
overflow: visible;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
scroll-snap-align: start;
|
|
||||||
}
|
}
|
||||||
#pain-points .section-label {
|
#pain-points .section-label {
|
||||||
margin-bottom: 40px;
|
margin-bottom: 40px;
|
||||||
|
|
@ -516,8 +507,6 @@
|
||||||
overflow: visible;
|
overflow: visible;
|
||||||
position: relative;
|
position: relative;
|
||||||
z-index: 1;
|
z-index: 1;
|
||||||
scroll-snap-align: start;
|
|
||||||
scroll-snap-stop: always;
|
|
||||||
}
|
}
|
||||||
.pain-item::before { display: none; }
|
.pain-item::before { display: none; }
|
||||||
.pain-item:hover { background: transparent; }
|
.pain-item:hover { background: transparent; }
|
||||||
|
|
@ -985,6 +974,14 @@
|
||||||
|
|
||||||
// 移动端:入场动画 + 视差滚动
|
// 移动端:入场动画 + 视差滚动
|
||||||
if (window.innerWidth <= 900) {
|
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. 入场动画(一次性)
|
// 1. 入场动画(一次性)
|
||||||
const mobileItems = document.querySelectorAll('#pain-points .pain-item, #approach .approach-item');
|
const mobileItems = document.querySelectorAll('#pain-points .pain-item, #approach .approach-item');
|
||||||
const mobileObserver = new IntersectionObserver((entries) => {
|
const mobileObserver = new IntersectionObserver((entries) => {
|
||||||
|
|
@ -1000,21 +997,11 @@
|
||||||
|
|
||||||
function updateParallax() {
|
function updateParallax() {
|
||||||
painNums.forEach(num => {
|
painNums.forEach(num => {
|
||||||
// 获取数字所在的父卡片(.pain-item)
|
|
||||||
const card = num.parentElement;
|
const card = num.parentElement;
|
||||||
if (!card) return;
|
if (!card) return;
|
||||||
|
|
||||||
// 计算卡片相对于视口顶部的距离
|
|
||||||
const rect = card.getBoundingClientRect();
|
const rect = card.getBoundingClientRect();
|
||||||
|
|
||||||
// 核心视差逻辑:
|
|
||||||
// rect.top 越大(卡片在屏幕越下方),位移越大(向上推数字)
|
|
||||||
// 随着卡片向上滚动(rect.top 变小),数字向下回落
|
|
||||||
// 这会产生数字比文字"慢半拍"的悬浮感
|
|
||||||
// 速度因子 0.15 意味着视差幅度为卡片移动距离的 15%
|
|
||||||
const speed = 0.15;
|
const speed = 0.15;
|
||||||
const yOffset = -(rect.top * speed);
|
const yOffset = -(rect.top * speed);
|
||||||
|
|
||||||
num.style.transform = `translateY(${yOffset}px)`;
|
num.style.transform = `translateY(${yOffset}px)`;
|
||||||
});
|
});
|
||||||
ticking = false;
|
ticking = false;
|
||||||
|
|
@ -1026,9 +1013,67 @@
|
||||||
ticking = true;
|
ticking = true;
|
||||||
}
|
}
|
||||||
}, { passive: true });
|
}, { passive: true });
|
||||||
|
|
||||||
// 初始化一次
|
|
||||||
updateParallax();
|
updateParallax();
|
||||||
|
|
||||||
|
// 3. JS 逐屏吸附
|
||||||
|
// 收集所有吸附点
|
||||||
|
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 touchStartY = 0;
|
||||||
|
let lastScrollTop = 0;
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (nearest) {
|
||||||
|
isSnapping = true;
|
||||||
|
const targetY = nearest.getBoundingClientRect().top + window.scrollY;
|
||||||
|
window.scrollTo({ top: targetY, behavior: 'smooth' });
|
||||||
|
// 吸附完成后解锁
|
||||||
|
setTimeout(() => { isSnapping = false; }, 800);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
</body>
|
</body>
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue