【兼容性】H5滚动穿透解决方案 您所在的位置:网站首页 电脑下拉自动弹回顶端 【兼容性】H5滚动穿透解决方案

【兼容性】H5滚动穿透解决方案

2024-07-12 14:55| 来源: 网络整理| 查看: 265

小东西快快学快快记,大知识按计划学,不拖延

滚动穿透相信大家平常开发的时候也经常遇到,网上也有很多解决办法

今天我就谈下我对 滚动穿透的理解 和 总结下我们大佬写的一个比较完美的解决方案

不废话,本文分为3部分

1、什么是滚动穿透

2、为什么会滚动穿透

3、怎么解决滚动穿透

4、碰到的问题

什么是滚动穿透

大家肯定不陌生了,做移动端开发的,肯定都碰到过,比如 我明明滚动的是弹窗,但是底下的 document 却在滚动

不说这么多,直接看

为什么会滚动穿透

首先,这不是一个bug,这是一个合理且正常的表现

阅读了官方的文档之后,我也是理解了好久

https://www.w3.org/TR/cssom-view/#scrolling

以下是个人的理解

当用户开始滚动的时候,页面响应滚动有两种类型

1、document 滚动

2、可滚动 element 滚动

只有两种类型,就是说,一旦有滚动行为发生,那么就必然产生这两个类型其中之一

如果 element 可以滚动,那么就 滚动 element

如果 element 无法滚动,那么就让 document 响应滚动

是一个 if-else 的关系

这个element 无法滚动包括

没有设置可滚动overflow属性监听回调 设置了 preventDefault已经滚动到底端或顶端

为什么会觉得这个这个行为是合理性,我的理解是

用户产生滚动行为,浏览器就必须要响应这个行为,产生滚动的反馈,这才是正常的。尽可能响应,滚动一切当前操作可以滚动的元素

只是当把元素设置了 fixed 之后让人感觉是个bug,浏览器没有必要对 fixed 元素做特殊处理,两个不相关的东西,不可能耦合起来

怎么解决滚动穿透

我们理解了滚动穿透的原因之后,我们就可以对症下药了

既然 document 是备胎滚动选项,那么就让 document 不可滚动

1body overflow hidden

代码语言:javascript复制html, body { overflow: hidden; }

PC 可以,但是对移动端无效

那么我们限制body不超过一屏,那么自然就不能滚动了?

2body height 100%

代码语言:javascript复制html, body { overflow: hidden; height:100%}

是可以,但是会丢失 滚动高度,文档回到最顶部。体验不好

3记录滚动高度,弹窗关闭重新赋值

既然丢失滚动高度,那么就记录下滚动高度 scrollTop ?然后关闭弹窗的时候再赋值回去?

页面内容从 0 突然跳到 原先位置,可想而知会有 闪动,体验仍然不好

4避免页面跳回顶部

拿到 页面的滚动高度,在给 html 设置 这些样式的时候

代码语言:javascript复制html{ overflow: hidden; height:100%}

在设置 absolute,top 设置成之前拿到滚动高度(伪代码)

代码语言:javascript复制html { position:absolute; top: scrollTop }

利用这种方式保证内容处在同一位置,这样就可以避免页面的跳动,但是直接给 html 设置 absolute 风险太大,容易埋坑,不太建议大项目使用,小应用还是可以的,我在需求的小活动页7就使用过这种方式

5禁用页面滚动

除了在 css 限制页面滚动,还可以从 js 去限制

代码语言:javascript复制document.addEventListener( 'touchmove', e => e.preventDefault());

这里要注意一个问题,在 chrome51 中在监听回调更新了参数,如果你不加上这个参数,那么可能这样并不能禁用页面滚动

具体如下

以前 addEventlisener 参数 是

代码语言:javascript复制target.addEventListener(type, listener[, useCapture]);

第三个参数是 控制监听器是 捕获阶段还是 冒泡阶段执行,默认值是 false(冒泡阶段执行)

现在变成了

代码语言:javascript复制target.addEventListener(type, listener[, options]);

第三个参数变成了对象,包含一个属性 passive

这个参数主要是为了提高滚动流畅度

因为在一开始的时候,浏览器响应滚动 大概会有 200ms 的延迟

因为浏览器不知道监听的回调是否调用了 preventDefault 来取消滚动

所以只好等回调执行完毕,大概 200ms 后, 页面再开始响应滚动,所以会显得不那么跟手

现在通过 参数 passive 就可以事先告诉浏览器 这个监听回调不会 执行 preventDefault,你可以马上响应滚动不用等待

从而 提升了滚动的流畅度

但是 passive 是新出的标准,但是以前没有,所以我们需要做一个兼容

代码语言:javascript复制var options = false; window.addEventListener("test", null, { get passive() { options = { passive: true }; return undefined; }, }); elem.addEventListener("touchstart", fn, options);

具体可以看下 justjavac 写的文章

https://zhuanlan.zhihu.com/p/24555031

所以我们禁用页面滚动,可能得这么写,告诉浏览器我们需要禁用滚动

代码语言:javascript复制document.addEventListener( 'touchstart', e => e.preventDefault(), { passive: false} );

但是这样就会把页面所有滚动都禁止

所以我们需要开放一个白名单,当滚动的元素在白名单之内,我们就放开限制

这个白名单的设置就是 给元素加上 can-scroll 类名,这样就可以放开滚动

代码语言:javascript复制document.addEventListener( "touchmove", (e) => { const excludeEl = document.querySelectorAll(".can-scroll"); const isExclude = [].some.call(excludeEl, (el: HTMLElement) => el.contains(e.target) ); if (isExclude) { return true; } e.preventDefault(); }, { passive: false } );

但是对待白名单的元素放开限制之后,当元素滚动到顶部和底部的时候,再滚动,仍然会触发document 滚动

为什么呢?

之前我们说了,浏览器需要尽可能响应滚动行为,element 滚到两端 element 滚不了,那我就滚 document

所以我们最好监听 element 滚到 顶部和 底部的时机,继续禁止滚动行为

代码语言:javascript复制var initialY = 0; el.ontouchstart = function (e) { if (e.targetTouches.length === 1) { // 单点滑动 initialY = e.targetTouches[0].clientY; } }; el.ontouchmove = function (e) { if (e.targetTouches.length === 1) { // 单点滑动 var clientY = e.targetTouches[0].clientY - initialY; // 滑到底部 if (el.scrollTop + el.clientHeight >= el.scrollHeight && clientY < 0) { return e.preventDefault(); } // 滑到顶部 if (el.scrollTop 0) { return e.preventDefault(); } } };

碰到的问题

1父子元素也存在滚动穿透

这个问题测试了,只在 ios 中存在,滚动穿透的顺序是 子->父->document,而 安卓和 鸿蒙 则不会,子滚不了,直接滚document

这个是实际的dom 父子关系才会,视觉上的 父子关系没有这个问题

2子元素 e.stopPropagation() 会让 preventDefault 失效

比如这样

代码语言:javascript复制document.addEventListener( "touchmove", (e) => { e.preventDefault(); }, { passive: false } ); document.querySelector(".modal").addEventListener("touchmove", (e) => { e.stopPropagation(); });

虽然document 取消了默认事件,本来整个页面都不能滚了

但是子元素 调用了 stopPropagation() 之后,不仅元素可以滚了,还会导致滚动穿透(毕竟只要元素能滚就能发生穿透)

但是document 还是不会滚动的

3滚动穿透的触发条件

一次没有抬起的滚动行为(手没有离开屏幕)导致元素滚动到顶部或者 底部之后,如果手还在屏幕上往两端滑,并不会触发滚动穿透

如果你把元素滚动到 两端不可滚之后,抬起手,再按下去,往不可滚的方向移动,此时才会发生 滚动穿透

之前我们说了,滚动响应有两种对象,element 和 document

从这里可以意识到,单次的滚动行为 只会绑定一个滚动对象,不会切换响应对象

只是在开始滚动的时候,浏览器会根据情况,选择响应滚动的对象,选择时候不会切换



【本文地址】

公司简介

联系我们

今日新闻

    推荐新闻

    专题文章
      CopyRight 2018-2019 实验室设备网 版权所有