ios 局部滚动的坑及解决方案

4,162 阅读6分钟

起因

最近几天在写一个滚动加载更多数据的插件(Scrollload),为局部滚动写demo时,遇到了很多局部滚动的坑,在这里分享一下这些坑的解决方案。如果你有遇到过什么坑,请评论告诉我,越详细越好,最好有demo。以下的坑只针对ios。

优势

  1. 每个局部滚动拥有自己的滚动条,这是全局滚动所不能取代的。最典型的是列子如局部滚动, 全局滚动。不难发现全局滚动不同的tab之间共享一个滚动条,也就是说其中一个tab滚动了,另一个tab也会跟着滚动。
  2. 全局滚动有出界情况,出界就是滑到最顶端和最底端后继续滑。这样会出现一个很恶心的效果。局部滚动虽然也会有这个情况,但是能修复,全局滚动至少我不会修。


约定

把产生滚动条的元素称之为视窗

全局滚动:滚动条在body或者body父级元素或者window上

局部滚动:滚动条在body里的子孙元素上。

坑(一)

你会发现ios浏览器上能滚动,但是没了弹性滚动的效果。解决方法是为body或者视窗加-webkit-overflow-scrolling: touch。其实加这两个地方都一样,虽然在文档中并没有说该属性有继承性的,不过我在safari下测试出来是有继承性的。该属性具体说明看这里


坑(二)

问题

先看一下视频效果:没用ScrollFix

ios局部滚动的出界情况,当你的滚动条在最顶端的时候,你会发现此时你的列表不再滚动而是产生全局滚动了。其实这个确实应该是这样的。如果此时你的视窗占了比整个window还要大,就会一直在视窗里滚动,你还让不让用户看其他内容了。但视窗的滚动条在最顶端的时候的时候下滑又迅速上滑你会发现还是在做全局滚动。这个也应该是这样的,全局滚动还没停下来不可能做局部滚动吧。同理当你滚动条在最下面的时候也会出现这样的状况。但有时候,你就不会想以上的效果。

解决方案

其实解决方案很简单,既然知道了问题是滚动条在最顶端和最底端的时候才会出现的,那么你只要在touchstart的时候判断scrollTop是否为这两个值,如果是就加1或者减1。这里有一个别人已经实现的库ScrollFix。视频效果: 用了ScrollFix。但是这个库一定要谨慎用。因为他监听了touchstart事件,这个事件会使滚动滞后(在这里并不明显), passive event listeners。当然你不能用文章里的解决方法,否则你快速滑动,由于touchstart事件的监听函数还没执行到就已经开始滚动了所以可能还是会发生上面的情况,ScrollFix这个库就无效了。最后说一下在Scrollload库中集成了这个现象的解决,设置useScrollFix为true。

坑(三)

还是先看一下视频效果: 有问题的视频

这个坑就是你不满视窗的内容不足一个屏幕的时候你向上滑动你会发现整个视窗都动了,也就成了全局滚动。这个现象是正常的,内容都不满一屏当然不需要滚动啊,甚至连滚动条都没产生。但是有时候你却不喜欢这样,你希望局部滚动完全替代全局滚动就不想这样的现象。

解决方案

理由也说了,内容不足一个屏幕产生的现象,那么让内容时刻保持在一屏之上不就可以了。这里我写了一个库,LocalScrollFix。解决方式在视窗内append一个元素。更新这个元素的paddingTop值来使视窗的内容超过一屏。这个用起来就要比ScrollFix多一步了。你每次更新完内容必须告诉我你已经update内容拉。具体使用看文档吧。在Scrollload库中也集成了现象的解决,设置useLocalScrollFix为true。

坑四坑五都很诡异。但是他们都能用这个解决方案,可以推出很多关于局部滚动很诡异的现象都能用这个,只要你内容满一屏就不会出现了。看到这里其实就够了,下面的你可能一辈子也遇不到。

坑(四)

接下来这个坑就诡异了,不是一般的诡异。滚动列表嘛,哪里少得了加载动画呀!那我就把我的关键代码贴一下。完整示例请看demo。这个应该算是bug了。局部滚动完全滚动不了。

<style>
  @keyframes loadingRotate {
    0% {
      transform: rotate(0deg) translateZ(0);
    }
    50% {
      transform: rotate(180deg) translateZ(0);
    }
    100% {
      transform: rotate(360deg) translateZ(0);
    }
  }
  .loading img{
    width: 18px;
    height: 18px;
    animation: loadingRotate .4s linear infinite forwards;
  }
</style>
<div class="window">
  <div class="container">
    <ul class="list">
    </ul>
    <div class="loading">
      <img src="https://gss0.bdstatic.com/5bd1bjqh_Q23odCf/static/wiseindex/img/fetch_ing_8_0.png">
    </div>
  </div>
</div>
<script>
  setTimeout(() => {
    document.querySelector('.loading').insertAdjacentHTML('beforebegin', `
    <li>....</li>//一定要足够多超过视窗的高度。
`)
}, 500)
</script>

产生这个局部滚动的bug有一些必须的条件:

  1. 一开始视窗中内容的高度不超过视窗
  2. 设置定时器或者ajax异步插入节点且一开始在list的高度不能超过视窗
  3. 视窗内有元素的css中设置animation属性的时候必须有infinite forwards存在一个即可(这里只确定infinite forwards一定会有问题)
  4. keyframes必须有transform转换(这里只确定transform一定会有问题)
  5. 视窗也就是window类不能显式设置100%的宽度

同时满足这四个个条件就会出现这么诡异的情况。解决方案就是随便破坏一个条件就可以了。

坑(五)

在之上的基础上给window类设置了100%的宽度。此时我们给window的父容器也就是body设置一个1000px的高度(也可以百分比),然后window设置一个50%的高度(一定要是百分比)。你会发现还是不能滚动。看demo

产生这个局部滚动的bug有一些必须的条件:

  1. 一开始视窗中内容的高度不超过视窗
  2. 设置定时器或者ajax异步插入节点
  3. 视窗内有元素的css中设置animation属性的时候必须有infinite forwards存在一个即可(这里只确定infinite forwards一定会有问题)
  4. keyframes必须有transform转换(这里只确定transform一定会有问题)
  5. 视窗父容器的设置了高度。
  6. 视窗的高度是百分比

同时满足以上五种情况就会出现这个bug。要解决这个bug也是随便破坏一个条件就可以了。

能遇到坑四和坑五过年的时候一定要诚心得去拜拜祖宗,辟邪。