web移动端浮层滚动阻止window窗体滚动JS/CSS处理

这篇文章发布于 2016年12月31日,星期六,20:41,归类于 JS实例。 阅读 128662 次, 今日 7 次 60 条评论

 

更新于2018-08-10
差不多快2年过去了,Chrome已经开始支持这么一个CSS属性,名为overscroll-behavior,可以设置滚动到边界时候的页面行为。

详见这篇文章 “CSS overscroll-behavior让滚动嵌套时父滚动不触发”。

一、温故而知新

之前有写过在PC端“子元素scroll父元素容器不跟随滚动”的实现,原理是滚动到边缘通过preventDefault()阻止浏览器的默认行为达到我们想要的效果。

最近做移动端项目,遇到个类似的需求,就是,在众多web浏览器中,当我们出现一个浮层,浮层里面也有滚动条的时候,且有部分背景半透明的时候,就会发现,当我们滚动浮层里面的小滚动条的时候,背后整个页面都跟着一起滚走了。

后来,以为会和PC端一样,直接给<html>标签设置一个overflow:hidden声明就可以干掉窗体的滚动条,结果发现自己有些天真了。

后来经过一些研究以及测试,得到了下面这种双管齐下的处理方法。

二、阻止移动端窗体滚动的JS/CSS处理

首先CSS层面,在<html>标签上增加一个类名,例如noscroll,然后配合如下CSS和JS代码:

.noscroll,
.noscroll body {
    overflow: hidden;
}
.noscroll body {
    position: relative;
}

然后,当浮层出现的时候:

$('html').addClass('noscroll');

当浮层隐藏的时候:

$('html').removeClass('noscroll');

可以让一部分浏览器的窗体不能滚动,但不包括Safari等浏览器,怎么办呢?

我们可以在浮层touchmove的时候,阻止默认事件达到避免滚动的问题,例如:

$('aside').on('touchmove', function(event) {
    event.preventDefault();
});

这种处理兼容性强,效果最好,但是有一个问题,就是如果浮层自己也有滚动,那么这种处理会让浮层里面自己的滚动行为也无法触发,因此,我们的处理要更进一步,如下:

  1. 当手指touchstart的元素不是滚动容器同时不失容器的子元素的时候,阻止默认行为,;
  2. 如果手指touchstart的元素是滚动容器或者容器子元素的时候,不阻止默认行为,但不包括滚动到容器边缘的时候。

根据上述原理,我自己抽象了一个简单的方法,方法名和语法如下,完整代码见这里

$.smartScroll(container, selectorScrollable);

依赖Zepto.js或者jQuery.js,其中:
container表示委托的浮层容器元素($包装器对象),或者页面其他比较祖先的元素,但是,非常不建议使用$(document)或者$(document.body)等对象作为委托容器,因为可能会出现类似下面这样的错误提示:

Unable to preventDefault inside passive event listener due to target being treated as passive.

浏览器可能会把这些窗体元素的组织默认行为认为是不可取的消极的,会不被支持。

selectorScrollable表示container中可以滚动的元素的选择器,表示真正的滚动的主体。

$.smartScroll()方法的完整代码可以参见演示demo,您可以狠狠地点击这里:浮层元素滚动窗体不滚动的CSS/JS处理demo

或者扫码访问:
演示文档的二维码地址

综合了上面的CSS noscroll处理和JS的$.smartScroll()方法处理。

demo页面有如下图所示的两个按钮:
阻止滚动按钮示意

其中,上面的按钮,仅仅是采用了CSS限制,会发现,手指移动左侧半透明边缘的时候,例如在iPhone的Safari浏览器下,后面的页面内容也被滚走了;但是,一旦点击下面的测试按钮,会发现,此时,仅仅浮层里面的容器可以滚动,一旦到了边缘或者其他任何地方,都不会引起后面页面内容的滚动。

移动半透明背景色与移动

这就是我们要实现的,浮层元素自己可以滚动,窗体不能滚动的效果。

更新于2017-01-12
这样的,此方法可以让95%+的场景都是正常。根据自己工作人员测试,有部分Android还会有微移的情况,可以试试边缘判断更钝一点。不过,最终我们并没有追求100%设备完全完美,这是成本和场景后权衡之后的一种策略。

三、2016年的最后一条结束语

12月份是个超级忙碌的1个月份,只能趁着元旦周末2016年的最后1天再写点什么。

2016年依然是非常忙碌奋斗的一年,每天都在学习与进步。不过而大多数人不一样,学习的重心依然是CSS基础知识,事无巨细和深入到底两个方向,其实基本上都没什么实用价值的东西,兴趣所在而已。并没有过多关注上层建筑,一些流行事物也就看看而已;没有参加任何论坛会议之类,没有任何的社交和抛头露面,坚持分享和知识传播,无论是公司内还是对外;也几乎没有花精力在开源项目上,无论是自己的还是协助贡献。以保证有限的精力能够专注在1~2件事情上。依旧奋斗在项目一线,尽量避免面试,人员管理这些琐碎的事情,目前对当领导不感兴趣,我只是个打杂的,所以,小伙伴写邮件提问与交流的时候,不要叫我领导,我不是;也不要叫我大神,更不是。

虽然工作快7年,但是还是认为自己在积累阶段,希望在2017年可以慢慢开点花结点果。可能因为钓鱼的缘故,我是个非常耐得住性子的人,不急功近利,不心浮气躁,保持自由,不被外在环境束缚,按照自己的目标和规划走。

就这些吧~

祝大家2017年升职加薪,幸福美满!

(本篇完)

分享到:


发表评论(目前60 条评论)

  1. tommyhu说道:

    一个div能否禁止向下滚动,即在向下滚动时(scorll,touchstart,touchemove)时阻止滚动,完成某件事情后 ,不拦截

    同时做到向上滚动不影响

  2. yucho说道:

    感谢张老师,此前一直为悬浮窗拖动时页面跟着滚动而烦恼,网上的方法都不管用,试了此文方法果然有用

  3. 无道交集说道:

    最后我个人采用了这个方法。但是我在这里提出一个疑问和一个问题。
    首先是以问题,在文章的dome中,如果用力向上或者向下滑动,浏览器(我用的是谷歌版本 76.0.3809.132(正式版本) (32 位))会报错:[Intervention] Ignored attempt to cancel a touchmove event with cancelable=false, for example because scrolling is in progress and cannot be interrupted.这个报错是一个必须解决的问题吗,如果是我应该如何解决呢?
    其次是思考:如果我想解决在安卓上的那个微移我应该向什么方向去着手呢?

  4. 一行说道:

    如果在弹框touchmove的时候,设置
    var i = $(window).scrollTop();
    $(“body .page”).css({
    “position”: “fixed”,
    “top”: -i,
    “left”: 0,
    “right”: 0,
    “bottom”: 0
    })
    弹框消失的时候,重新设回static,这样处理感觉简单点,不知道这样做有什么弊端吗

  5. 无悔四月说道:

    忘了留示例地址:http://wkm123.com/demos/areaTest/ 进去之后点击地区三级联动

  6. 无悔四月说道:

    用了这个方法,但是在滚动弹窗内容到达顶部或者底部的时候,底层的BODY还是有一定距离的滚动,这个能处理掉吗?

  7. 旧城说道:

    我这样子传参smartScroll(document.getElementById(‘container’),document.getElementById(‘selectorScrollable’)) JS里的方法会报错

  8. 旧城说道:

    我想请问的是在react项目要怎么使用呢

  9. 小煌弟说道:

    我用了张老师的方法可是会自动滑动到顶部,这是为什么呢?老师的不会我的会,请老师指导;
    是不是层级一定要按老师的来才可以实现呢?

  10. GerryLon说道:

    大神就是大神

  11. 风屋星说道:

    感谢!
    JQ 修改一下可以用
    var events = event.originalEvent.targetTouches[0] || event;

  12. 木兰说道:

    麻烦请问下,滚动的元素到底部时(不是touchstart时,滚动条就在底部),会触发body内容滚动,貌似是浏览器自动执行的动作,这个有解决方案吗

  13. 孙笑川说道:

    旭哥upup

  14. xxx说道:

    jq貌似得3.0.0版本及其以上的版本才兼容呀?不然里面的var events = event.touches[0] || event;会报错。

  15. ucan说道:

    旭哥,我在三星galaxy A5上试了,发现页面如果设置了animation动画的话,弹出层内的滚动内容滚动到上下边缘时底部还是会跟着滚动几像素,不知道是啥原因~伤心

  16. chang1ng说道:

    如果浮层里面有多个滚动元素,最多只能有一个元素可以滚动,其他元素滚动不了。
    这是一个问题。

    • ucan说道:

      我的做法是判断滚动元素的父元素的classlist是否有scroll,这样页面就可以有多个弹窗了

  17. 阿罗说道:

    真的好,跟旭哥混解决不少难题

  18. 贲伟说道:

    代码完成的不错,但是唯一有一个问题就是如果我在弹出层中加入了伸缩的菜单再用j
    Query来完成单击事件的时候就会造成冲突

  19. 曾小乱说道:

    是不是漏了一个height属性呢?是这样?
    .noscroll body {
    height: 100%;
    overflow: hidden;
    }

  20. 张小哥说道:

    你好,看到你的帖子我感到很给力,但是你这里也有一些不足,就是你说的稍微偏移,所以我也是个懒人,就想更简便的办法,于是,我是这样做的,就是弹出层弹出的时候,记录一下body的scrollTop()的值,然后弹出层滑动的时候,我不管基层是否滑动,反正人是看弹出层的,这样等他滑动完事了以后,点击返回或者关闭页面,这时候我把body的scrollTop(str)这个值拿出来让基层直接滑到这里,这样完美解决

    • 一只逗逼龙说道:

      您好,我觉得你的方法虽然简单粗暴,然而却忽略某些方面的体验:由于此方法会产生冒泡,有时候用户想拖动弹层频繁的失误拖动到了基层。而且对于弹层内容较多,滚动条越长的条件下,这种情况出现的频率会更高,这种体验会让人很烦躁。

      • 一只逗逼龙说道:

        手快了,纠正一句话:有时候用户想拖动弹层(却)频繁的失误拖动到了基层

  21. 哈哈说道:

    还是不错的

  22. 小小白说道:

    我用的mui框架写了一个demo,在浏览器模拟手机效果是对的,在手机上就全不能滚动了,求大神看看 http://appt.ibaodashi.com/ces/html/spxq5.html

  23. kevin说道:

    在浮层上边加个 宽高100% background:rgba(0,0,0,.0000001) 的层,对该层阻止默认事件 也可解决。

  24. jzz7280说道:

    如果在弹出层第一次滑动的元素是不可滚动的话,会报elScroll为null的错误,当然这也不影响使用

  25. magicbing说道:

    有没有小程序里的解决方案啊,没有document真是难搞

  26. 一只逗逼龙说道:

    感谢旭哥给予的解决方案,由于我们公司长年都会有大量的移动端活动页面,这个问题也困扰了我们很久,我们尝试过多种方案,但是之前的方案存在各种小缺陷。现在打算尝试采用您这种方案,如果覆盖面能达95%那就是巨牛掰的存在了。后续会通过大量实验给您反馈结果的

  27. imgream说道:

    selectorScrollable的左右滑动也被阻止了,比如有个分享框 里面的按钮是左右滑动的,用了$.smartScroll()方法就不能左右滑了。能否再优化下,实现container不可滚动,但selectorScrollable可以左右滑动。

    • 张 鑫旭说道:

      @imgream 如果同时上下左右都可以滑动那就啰嗦了,所以,可能并不会支持,成本和收益略低,不想把小工具弄得太复杂~

  28. 珠宏说道:

    我看到了isSBBrowser

  29. ziven27说道:

    张老师这个问题我之前的解决方案是(但结构不是父子关系),
    你的结构:html>body>{content}+#modal
    我的结构:html>body>(#page>{content})+#modal
    这样兄弟的关系的话就避免你的那个问题了,但是用起来给用户的感觉其实还是嵌套的,所以我觉得是个备选方案。
    还有一个点就是postitiion:fixed在iso上总是会出现很多不友好的地方,比如输入法弹出来的时候网页容易被输入法挡住。用position:absolute;可能会不需要考虑那么多的兼容性问题。
    以上/

  30. ziven27说道:

    说个题外话:
    不知道张老师是不是看过“血战钢锯岭“这个电影,感觉你和男主都有着自己坚持的东西。

  31. Howard说道:

    在body加了position:fixed可以解决您的问题吗?

  32. 旧时长安说道:

    之前项目上也遇到这样的问题,有两种解决方案,一个是禁止滚动,用touchmove模拟滚动,缺点是不够平滑;第二个是用控件iscroll解决; 现在又多了一种解决方案,谢谢啦,2017,我们一起进步~

    • 曾小乱说道:

      使用iscroll怎么解决呢?是设置哪个参数吗?我的参数是这样的:{
      mouseWheel: true,
      click: false,
      preventDefault: false,
      scrollbars: true,
      tap: false,
      bounce: false,
      disableTouch: true
      }

  33. king小龙说道:

    现在正在学习,很好奇公司内部对于开发时前端的工作流程能否给予一下具体的解答谢谢大神!

  34. 木铎骑黄牛说道:

    这个插件在魅族pro6 (系统版本android 6.0)的微信浏览器中(微信版本6.5.3)失效,滑动时,背景仍会滑动。

  35. 二少说道:

    scrollIntoView 的定位可以添加平滑过渡的效果嘛,试了好久没成功

  36. catalsdevelop说道:

    坚果手机,更新到了最新的系统,自带的浏览器,最后那个例子,两个按钮弹出来的滚动,还是会影响外层。。。

  37. pt涛说道:

    旭哥,你上面的东西很实用,希望你多多更新!祝工作愉快,事业顺心。。

  38. yukap说道:

    精力专注到1-2件事情上,说的太棒了。给结束语点赞

  39. karen说道:

    旭哥做的这个我用手机试了一下效果 ,第二种方法在只滑动一次的时候,底下的页面是不会移动的,但是在反复多次滑动之后,下面的页面会出现滑动的情况,这是什么原因呢???

  40. xfan说道:

    感谢这些基础css文章,它们真的很有用~~

  41. 最爱的是不二说道:

    看到最后一段要泪目了,我无法像你这么耐得住性子,一直大火的ES6、vue、node…等等,我也想去学习,可是更新太快,而我学习的速度远远更不上这些知识更新的速度,但我有无法耐心去专研css的内容,在公司只是css比较厉害好像无法站住脚。我也不知道改怎么办,好难受….新年快乐

  42. 盖大楼说道:

    …吆,你居然可以看见我,只有一种可能,我被你拐卖到外地了!
    真巧工作中遇到一个问题,要写了一个整屏滚动的东西。一开始第一页往下滑第二页,往上滑第一页。 但是设计给的图太长,要是手机短或者手机浏览器不是全屏模式,只能看到部分内容。就想着内部的能滑动 ,这样不管屏幕短还是长,都可以看到全部内容。再滑动的时候 切换页。但是QQ浏览器 向上滑没问题,向下滑会出现QQ的自带下拉框。无法向上换页。这个demo正好可以阻止 QQ浏览器自带的下拉框。网上找了很久都没有这种的!
    非常感谢!新年快乐!

  43. 奥丁海兹说道:

    新年快乐

  44. 黑暗骑士说道:

    新年快乐 你的博客解决了我很多问题 过年了 在这里 谢谢你的博客

  45. GIN说道:

    顶一个

  46. Snake说道:

    看到结尾想着说上一句,能够这样坚持和耐得住性子真心不简单,有时候自己看着身边的同事走向管理路线感觉心理痒痒

  47. n说道:

    见过一个纯 css 的方案 http://www.luxiyalu.com/playground/overlay/
    不过很少看到其他人提起……