滚动穿透的解决方案

在移动端开发中,遇到固定定位遮罩层下DOM结构仍可滚动的问题,即滚动穿透现象。常见于pc模拟器正常,真机测试时出现。解决方法包括监听touchmove事件,动态控制body和弹窗内滚动。当触摸目标为遮罩层时阻止默认滚动,反之则允许。同时需判断滚动边界,防止在顶部或底部时继续穿透。在弹窗显示和隐藏时,启用或禁用相应事件处理。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

在移动端中,如果我们使用了一个固定定位的遮罩层,且其下方的dom结构的宽度|高度超出屏幕的宽度|高度,那么即使遮罩层弹出后铺满了整个屏幕,其下方的dom结构依然可以滚动,这就是大家所说的“滚动穿透”。

而且经常是你在pc模拟器上没有问题,但是真机打开就一定会出现。

解决方案:body滚动 + 弹层内部滚动[js-检测touchmove的target]

简单粗暴,一针见血:谁能动谁动,谁不能动就禁止touchmove事件的preventEvent默认行为。

适用以下场景:

body可滚动
触发弹层出现的按钮可以在任意位置
弹层可以滚动

简单来说,就是适用任何场景!

解决方案:

检测touchmove事件,如果touch的目标是弹窗不可滚动区域(背景蒙层)就禁掉默认事件,反之就不做控制。

但是同样的问题是,需要判断滚动到顶部和滚动到底部的时候禁止滚动。否则,触碰到上下两端,弹窗可滚动区域的滚动条到了顶部或者底部,依旧穿透到body,使得body跟随弹窗滚动。

所以依旧需要代码,对可滚动区域的touchmove做监听:若到顶或到底,同样阻止默认事件。

需要做的事情有:

预存一个全局变量targetY。
监听可滚动区域的touchstart事件,记录下第一次按下时的e.targetTouches[0].clientY值,赋值给targetY。
后期touchmove里边获取每次的e.targetTouches[0].clientY与第一次的进行比较,可以得出用户是上滑还是下滑手势。
如果手势是向上滑,且页面现在滚动的位置刚好是整个可滚动高度——弹窗内容可视区域高度的值,说明上滑到底,阻止默认事件。

同理,如果手势是向下滑,并且当前滚动高度为0说明当前展示的已经在可滚动内容的顶部了,此时再次阻止默认事件即可。

两个判断条件可以写到一个if中,用 || (或)表示即可。我这里为了代码可读性,分开写了:

layerBox.addEventListener('touchmove', function(e){
	// 检测可滚动区域的滚动事件,如果滑到了顶部或者底部,阻止默认事件
	var NewTargetY = Math.floor(e.targetTouches[0].clientY), //本次移动时的位置
	    sTop = layerBox.scrollTop, //当前滚动的距离
	    sH = layerBox.scrollHeight, //可滚动区域的高度
	    lyBoxH = layerBox.clientHeight; //可视区域的高度
	if(sTop <= 0 && (NewTargetY - targetY) > 0){
		// console.log("下拉到顶部")
		e.preventDefault();
	}else if(sTop >= (sH - lyBoxH) && (NewTargetY - targetY) < 0){
		// console.log("上翻到底部")
		e.preventDefault();
	}
}, false);

完整代码:

出现弹窗时:

var btn = document.getElementById('btn'); //弹窗触发按钮
btn.onclick = function(){
	layer.style.display = 'block';
	layer.addEventListener('touchmove', function(e){
		e.stopPropagation();
		if(e.target == layer){
			// 让不可以滚动的区域不要滚动
			console.log(e.target);
			e.preventDefault();
		}
	}, false)
	var targetY = null;
	layerBox.addEventListener('touchstart', function(e){
		// clientY-客户区坐标Y、pageY-页面坐标Y
		targetY = Math.floor(e.targetTouches[0].clientY);
	});
	layerBox.addEventListener('touchmove', function(e){
		// 检测可滚动区域的滚动事件,如果滑到了顶部或者底部,阻止默认事件
		var NewTargetY = Math.floor(e.targetTouches[0].clientY), //本次移动时的位置
		    sTop = layerBox.scrollTop, //当前滚动的距离
		    sH = layerBox.scrollHeight, //可滚动区域的高度
		    lyBoxH = layerBox.clientHeight; //可视区域的高度
		if(sTop <= 0 && (NewTargetY - targetY) > 0){
			// console.log("下拉到顶部")
			e.preventDefault();
		}else if(sTop >= (sH - lyBoxH) && (NewTargetY - targetY) < 0){
			// console.log("上翻到底部")
			e.preventDefault();
		}
	}, false);
}

隐藏弹窗时:

var closeBtn = document.getElementById('close'); //弹窗关闭按钮
closeBtn.onclick = function(){
	layer.style.display = 'none';
	// 弹窗关闭后,可解除所有禁止-懒人就不写了
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值