一次react滚动列表的实践---兼容ios安卓

一、背景

近期项目改版,对原有的h5页面进行了重新设计,数据呈现变成了瀑布流。希望新版兼容ios和安卓两端的情况下,无限制的刷新加载数据。大致效果如下:

图片描述

整个页面分4部分:

  • 顶部导航
  • 步数状态卡片
  • 用户信息卡片
  • 滚动列表

期望效果:列表滚动到用户信息卡片消失后,展示另一个吸顶的导航栏。

效果如下:
图片描述
分析可以发现,首先我们要做的就是适配iPhone X,其次我们需要监听列表的滚动高度,在pc和安卓上监听滚动事件是没有问题的,但是ios上滚动过程中不会触发scroll事件,而是滚动结束后会触发onscrollend事件,这就不能满足实时监听高度的要求。经过简单调研,决定站在巨人的肩膀上,通过iscrollbetter-scroll等js库实现。这两个库都是解决各种滚动兼容的js库,很多常见的轮播、picker组件都是基于这些库封装的。顺便说一句,还有个库也不错(simulation-scroll-y

二、进入正题

  • 1.适配iPhone X

PhoneX的适配,在iOS 11中采用了viewport-fit的meta标签作为适配方案;viewport-fit的默认值是auto。react app的渲染内容都在id为root的 div里面。我们给这个div加上iphoneX的safe-area-inset属性即可。更多相关内容,这篇文章写的挺详细

<meta name='viewport' content='width=device-width,initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no, viewport-fit=cover' />
#root{
    height:100vh;
    padding-top: constant(safe-area-inset-top);
    padding-left: constant(safe-area-inset-left);
    padding-right: constant(safe-area-inset-right);
    padding-bottom: constant(safe-area-inset-bottom);
    padding-top: env(safe-area-inset-top);
    padding-left: env(safe-area-inset-left);
    padding-right: env(safe-area-inset-right);
    padding-bottom: env(safe-area-inset-bottom);
  }
  1. 2.页面

    页面结构不多说,比较基础。
    div.container-> div#wrapper->div.list->div.list-item
    值得注意的是,wrapper需要设置绝对定位。同时,通过transform: translateZ(0);开启硬件加速,浏览器在渲染时会通过GPU进行渲染。有效缓解安卓端滚动卡顿的问题,类似的css还有不少,css硬件加速不要滥用,否则会导致不该使用gpu的layer使用gpu,占用内存过高,导致页面卡顿,甚至黑屏,一般情况下,给不同的硬件加速元素添加一个不同的z-index属性可以解决。-webkit-overflow-scrolling: touch使ios滚动顺滑。
    // 初始化BScroll伪代码,生产慎用:

    import BScroll from 'better-scroll';
      
    this.myScroll = new BScroll('#wrapper', {
                  mouseWheel : true,
                  // 无需scrollbar
                  scrollbar  : false,
                  // propType属性设置为3在惯性动画期间也触发onscroll事件
                  probeType  : 3,
                  // 允许滚动列表内可点击、touch
                  click      : true,
                  tap        : true,
                  // 上拉加载,正值自动触发加载
                  pullUpLoad : {
                      threshold: 50
                  }
              });

一开始,我将better-scroll初始化代码放在container组件的componentDidMount函数中,但由于初始数据也在这个函数获取,导致当返回较慢的时候初始化的#wrapper没有内容,此时需要手动点击加载更多才展现数据,不符合预期。所以考虑将初始化代码放到list组件渲染完成之后的componentDidUpdate函数中。list组件渲染完成后,就可以初始化我们的滚动类,这里使用的better-scroll,iscroll使用类似。具体参考上面链接。

    #wrapper {
        position:absolute;
        top:0;
        left:0;
        width:100vw;
        overflow:auto;
        height: 100vh;
        transform: translateZ(0);
        z-index: 33;
        -webkit-overflow-scrolling: touch
    }

具体的,可以将初始化代码放在list组建的container组件的handleScrollRefresh函数。这个函数作为props传到list组件,在list组件的componentDidUpdate钩子里面执行:
container组件:

handleScrollRefresh () {
      if (this.myScroll) {
          this.myScroll.refresh();
          console.log('refreshed ');
      } else {
          console.log('initialized');
          this.myScroll = new BScroll('#wrapper', {
              ...//初始化参数
          });
          this.myScroll.on('scroll',this.handleScroll, 10);
          this.myScroll.on('pullingUp', this.loadMore);
      }
  }

list 组件:

componentDidUpdate () {
      if (this.props.onRefresh) {
          this.props.onRefresh();
      }
  }

网上很多滚动卡顿的情况,大都是加载数据后没有执行refresh导致的。同时,加载数据成功后我们需要调用scroll的finishPullUp方法。下次上拉才能继续加载数据。这样,每当加载新的数据后,list组件就会执行componentDidUpdate,此时就调用了scroll的finishPullUp、refresh函数,使用起来无比顺滑。

三、优化

  1. 和大多数滚动处理一样,better-scroll的scroll事件也会频繁触发,这对性能还是有一定影响的,毕竟我们不需要过于频繁的执行回调函数。

    throttle (func, delay) {
      let lastTime = null;
      return function () {
          let context = this;
          let args = arguments;
          let now = new Date().getTime();
          if (!lastTime || (now - lastTime) > delay) {
              lastTime = now;
              func.apply(context, args);
          }
      };
    };

    不想写直接使用lodash也可以:

    //不精准的每秒十次
    this.myScroll.on('scroll', this.throttle(this.handleScroll, 100));
  2. 函数绑定,不传参的情况下在constructor中绑定this。而不是在render中使用this.xxx.bind(this)。
  3. list 图片大小限制,本次由于部分列表item图片过大,在安卓上导致黑屏的问题出现。排查了很久才发现这个问题。通过在图片url拼接参数限制大小解决了这个问题。

最后

感觉写得好乱,做事情和写文章果然是两回事。。。
有兴趣可以访问:https://3hours.taobao.com/new...
一起来做公益吧!

弱鸡全栈攻城狮

2k 声望
4.6k 粉丝
0 条评论
推荐阅读
复杂场景下的h5与小程序通信
在套壳小程序盛行的当下, h5调用小程序能力来打破业务边界已成为家常便饭,h5与小程序的结合,极大地拓展了h5的能力边界,丰富了h5的功能。使许多以往纯h5只能想想或者实现难度极大的功能变得轻松简单。但在套壳...

懒懒的技术宅16阅读 3k评论 5

ESlint + Stylelint + VSCode自动格式化代码(2023)
安装插件 ESLint,然后 File -&gt; Preference-&gt; Settings(如果装了中文插件包应该是 文件 -&gt; 选项 -&gt; 设置),搜索 eslint,点击 Edit in setting.json

谭光志34阅读 20.8k评论 9

安全地在前后端之间传输数据 - 「3」真的安全吗?
在「2」注册和登录示例中,我们通过非对称加密算法实现了浏览器和 Web 服务器之间的安全传输。看起来一切都很美好,但是危险就在哪里,有些人发现了,有些人嗅到了,更多人却浑然不知。就像是给门上了把好锁,还...

边城32阅读 7.3k评论 5

封面图
涨姿势了,有意思的气泡 Loading 效果
今日,群友提问,如何实现这么一个 Loading 效果:这个确实有点意思,但是这是 CSS 能够完成的?没错,这个效果中的核心气泡效果,其实借助 CSS 中的滤镜,能够比较轻松的实现,就是所需的元素可能多点。参考我们...

chokcoco24阅读 2.2k评论 3

在前端使用 JS 进行分类汇总
最近遇到一些同学在问 JS 中进行数据统计的问题。虽然数据统计一般会在数据库中进行,但是后端遇到需要使用程序来进行统计的情况也非常多。.NET 就为了对内存数据和数据库数据进行统一地数据处理,发明了 LINQ (L...

边城17阅读 2k

封面图
过滤/筛选树节点
又是树,是我跟树杠上了吗?—— 不,是树的问题太多了!🔗 相关文章推荐:使用递归遍历并转换树形数据(以 TypeScript 为例)从列表生成树 (JavaScript/TypeScript) 过滤和筛选是一个意思,都是 filter。对于列表来...

边城18阅读 7.8k评论 3

封面图
Vue2 导出excel
2020-07-15更新 excel导出安装 {代码...} src文件夹下新建一个libs文件夹,新建一个excel.js {代码...} vue页面中使用 {代码...} ===========================以下为早期的文章今天在开发的过程中需要做一个Vue的...

原谅我一生不羁放歌搞文艺14阅读 20k评论 9

弱鸡全栈攻城狮

2k 声望
4.6k 粉丝
宣传栏