• iOS 与 惯性滚动


    注:以下所有例子均  在 iOS 的微信中测试过,但对于饿了么APP的内置浏览器同样适用(两者使用相同内核)

    引题

    工作中常常有需要显示大量信息的情况,列表超出一屏就涉及到滚动的问题。例如

    - var n = 1
    ul  
      while n <= 100
        li= n++
    

    在 iOS 中用微信打开,滚动非常顺滑,so far so good!但某天产品需求有变,要求加一个固定在头部的标题,于是改成这样:

    - var n = 1
    h1= "Momentum Scrolling on iOS"  
    ul  
      while n <= 100
        li= n++
    
    body, ul {  
      margin: 0;
    }
    html, body {  
      height: 100%;
    }
    body {  
      display: flex;
      flex-direction: column;
    }
    h1 {  
      flex-shrink: 0;
    }
    ul {  
      flex: 1;
      overflow: scroll;
    }
    

    直接用 flex 盒模型实现,动态适应标题的高度,很简单不是么。但是这时在 iOS 上打开后测试,发现有问题,下半部分区域滚动起来感觉很不顺滑,用老板的话说就像“卡齿轮”

    这时就有大牛推荐了传说中的神器:-webkit-overflow-scrolling: touch

    ul {  
      flex: 1;
      overflow: scroll;
      -webkit-overflow-scrolling: touch;
    }
    

    很简单的一个属性,顺滑滚动效果就回来了!虽然不太明白是怎么回事,解决问题就好。 但是产品经理又说了,需要在滚动时获取滚动条的位置做些其他操作。太简单了,加个 scroll 事件搞定。

    document.querySelector('ul').addEventListener('scroll', function() {  
      this.previousElementSibling.textContent = 'ScrollTop: ' + this.scrollTop;
    })
    

    随手写好在浏览器中测试通过,然而在手机上测试就不太对劲:那个值是会变,然而滚动的时候不变,只有在滚动结束后变一次。

    整个滚动过程中 scroll 事件只在滚动结束后会被触发一次,问题是出在这个所谓的神器 -webkit-overflow-scrolling 上面

    -webkit-overflow-scrolling 究竟是什么鬼?

    一个只有 iOS 设备支持的非标准属性。苹果自己的解释:指定是否在 overflow: scroll 的元素中使用“原生”的滚动方式

    他包含两个可选值:auto 和 touch

    • auto:就是普通的无惯性滚动效果
    • touch:原生的滚动效果。使用此效果会构造一个 stacking context

    什么是 stacking context?这可以说是CSS里一个阴暗面,极其晦涩。有兴趣的朋友可以去看高人的解释,这里不做讨论(其实笔者自己也不是非常明白:cry:),总之所有的坑都是由此而起。

    -webkit-overflow-scrolling 引发了那些坑?

    下面列出我遇到过的坑:

    滚动中 scrollTop 属性不会变化。

    严格来说,上面的 scroll 事件不触发只是本坑的一个副作用,所以说不必考虑通过 touchmove 事件转发 scroll 事件等点子,scroll 事件触发了一样检测不到 scrollTop 属性的变化(当然检测手指的移动距离另说)。同样,检测滚动区域内部元素的 getBoundingClientRect 同样无效。

    例中起了一个无限的rAF循环不停地获取 scrollTop 的值,然并卵。

    手势可穿过其他元素触发元素滚动

    这个更奇葩。例中用一个半透明的 div 盖在了滚动区域 ul 上面(实践中可能是一个弹框的背部蒙版),甚至给 ul 自己加上了 pointer-events: none,手指在 div 上滑动仍然会触发 ul 的滚动。你可以在显示半透明蒙版时将 ul 的 -webkit-overflow-scrolling: touch 或 overflow: scroll 去掉,但是会造成屏幕明显的闪烁。如果给 body 的 touchmove事件 preventDefault() 可以防止触发滚动,但是是所有滚动区域都会失效。

    运行时通过 JS 动态添加元素溢出高度导致滚动失效

    Google 上一搜一片,但是笔者没有遇到过,或许在新版本系统中已经修正,这里不展开讨论。

    滚动时暂停其他 transition

    还有没有其他未踩的坑呢?

    ……

    有没有什么好的解决方案

    使用 WKWebView 替换 UIWebView 内核

    可能有些读者已经发现,scroll 事件不能触发的坑在 iOS Safari 和 iOS Chrome 浏览器中不存在,为什么呢?这里要从 iOS 上浏览器的发展史说起。

    由于苹果公司对安全性等原因的考虑,苹果公司静止第三方浏览器在 iOS 设备上使用自己的浏览器的内核,换句话说,使用自己内核的浏览器都被禁止上架 AppStore。各大厂商无奈,于是长久以来,包括 Chrome 在内的所有第三方浏览器,都只是使用 iOS 系统内置的浏览器控件包一层外壳,这个控件就是 UIWebView。这个 UIWebView 不仅速度差,HTML5 支持率低,占用内存高,还有各种各样奇怪的问题。然而苹果公司却给自己的 Safari 浏览器开了后门。首先 Safari 使用的支持 JIT 编译的 JS 引擎内核 Nitro 比 UIWebView 里老旧的解释性 JavaScriptCore 内核速度搞数倍,然后 HTML5 支持度也比 UIWebView 高,还少了某些奇葩bug。久而久之就形成了 iOS 设备上 Safari 浏览器全面碾压其他第三方浏览器的现象。

    在乔帮主撒手人寰不久之后,苹果公司口气终于松动,虽然没有放开第三方浏览器内核的限制,但把 Safari 的浏览器内核提取了出来开放第三方浏览器使用,那就是如今的 WKWebView(WK 即 Webkit 的缩写)。但由于 WKWebView 只支持 iOS8 以上系统,各大浏览器厂商并未立刻跟进。直到最近的 iOS9 时代,Chrome 成为第一个吃螃蟹的 APP,使用了 WKWebView 内核。测试数据表明,使用 WKWebView 内核的 Chrome 浏览器在速度和 HTML5 支持率上已经与 Safari 浏览器不相上下。紧接着 Mozilla 公司宣布 Firefox 登录 iOS 平台,使用的也是 WKWebView 内核(于是有了第一款基于 Webkit 内核的火狐浏览器 :)

    不知苹果做了什么手脚,也许苹果的开发人员认为 WKWebView 的效能已经足以支撑在 scroll 事件中执行额外代码而不造成 UI 卡顿,总之在 WKWebView 内核中滚动可以正常触发 scroll 事件,当然也能正常获得 scrollTop 的值。然而经过测试第二个问题仍然存在。

    在这里笔者强烈建议各个 APP 迁移内嵌浏览器至新的 WKWebView 内核。但是就我看到的,包括微信和饿了么在内,几乎所有的国产 APP 都还在使用 UIWebView 内核,这不得不说是一大前端开发之殇。

    自己实现一套滚动逻辑

    比如前段时间很火的 iScroll,笔者曾近也使用过一段时间。最后得出的结论是:iScroll 挖出的坑不比它填上的坑少,比如在 iScroll 里加个 click 事件都要小心翼翼、特别对待(因为绝大多数情况绑定 touch 事件的回调函数里第一件做的事情就是 preventDefault)。

    最值得一提的是 iScroll 的速度问题,比原生实在相差太多,在中低端安卓机型上卡顿明显,如果还要绑定 scroll 事件做些别的事情就更卡了。

    总之笔者并不建议使用。

    放弃 H5,拥抱 Native

    也许这才是真正的终极解决之道

  • 相关阅读:
    其他
    Win10
    Win10
    面向对象与设计模式
    Git
    Java
    Git
    Git
    Git
    一、I/O操作(File文件对象)
  • 原文地址:https://www.cnblogs.com/chris-oil/p/6164966.html
Copyright © 2020-2023  润新知