60 行 JS 代码搞定一个下拉刷新组件

(点击上方公众号,可快速关注)


作者:elevenbeans

elevenbeans.github.io/2017/09/19/pull-to-refresh/


神马? 浏览器不都有自带的刷新按钮么?


Web 页为什么要下(zi)拉(xing)刷(che)新 ?


这里特指移动端,原因如下:


  • 相较于点击右上角刷新按钮(还有可能要点两次,第一次先展开 menu bar,然后才能看到 refresh 按钮),直接了当地下拉刷新无疑提供了更好的用户体验

  • 点击刷新按钮同步重载页面必然存在一定白屏时间,而通过下拉刷新的逻辑完全可以对于页面内容进行异步更新,其体验毫无疑问更加优秀

  • 移动端特有 touch 相关事件,用户在移动设备上的触摸、滑动操作频繁,习惯已经养成,下拉刷新在提供更好的体验的同时,丝毫没有增加用户的学习成本

  • 很多内容 + 社交的业务场景里面,主页面的存留时长极高且内容实时性强(如微博、知乎、头条等), 这些 Native App 已经普遍向用户提供了这种(下拉刷新)更新页面内容的交互方式。作为一个 Web 开发者,如有志于在移动领域让 Web App 和 Native App 在体验方面一较高下,那 H5 页(异步)下拉刷新功能也算是不可或缺的一环

  • 当然,还可以应对一些特殊场景 … (如 Webview 不提供刷新按钮 =,=)


精简成人话其实就是:被交互逼的


现有哪些轮子可以做到?


pulltorefresh.js:


目测这个是目前 Github 上最流行的 JS 下拉刷新组件了(15K stars)。highly customizable and dependency-free!


看使用方法,真的是简洁明了:


PullToRefresh.init({

  mainElement: 'body', // 想拖动(刷新)目标元素

  onRefresh: function(){

    window.location.reload(); // 刷新动作完后的会执行的回调函数

  }

});


不过这种单功能组件暴露了 20 个 API,着实有点多了。当然源码中有默认的一系列 default setting,个人觉得负担不大但不必要。


  • react-pull-to-refresh(https://github.com/bryaneaton13/react-pull-to-refresh)


看到一个 React 版本的就简直看到了亲人呐。虽然“只有” 200 stars,但是咱也不能完全以“星”取人啊,果断开始捣鼓。


<ReactPullToRefresh

  onRefresh={this.handleRefresh}

  className="your-own-class-if-you-want"

  style={{

    textAlign: 'center'

  }}>

  <h3>Pull down to refresh</h3>

  <div>{items}</div>

  <div>etc.</div>

</ReactPullToRefresh>


  • iscroll(https://github.com/cubiq/iscroll)


iScroll is a high performance, small footprint, dependency free, multi-platform javascript scroller. It works on desktop, mobile and smart TV.(并不是我喜欢复制粘贴凑篇幅,而是当我真正用过 TA 以后再回过头来再看这句话,就不厚道地笑了。平台通吃的东西在移动端性能真的可以很好么???)


不过,这是目前为止唯一一个让我成功完成任务的轮子。 然而还是放弃了,比较可惜,原因汇总如下:


我为什么不用上面的轮子?


  • pulltorefresh.js:很遗憾的是,我在 React 里面使用,页面第一次加载 OK,但下拉一次异步请求数据之后,再次下拉,整个组件源码报错了: “Cannot read property … of undefined …” 找不到目标 dom 元素了 =^=。目测应该和 React 组件更新机制有关,懒得追究,遂放弃

  • react-pull-to-refresh:我遇到了一个很多人都遇到过的问题:issues#12: Can’t scroll on mobile。作者的回复是: this is a due to the default touch-action in hammerjs is ‘none’. A fix could be to set the default to ‘pan-y’. Like this: let hammer = require(‘hammerjs’); hammer.defaults.touchAction = ‘pan-y’;。实力甩锅 hammerjs。小爷我也懒得继续死磕下去了,如果一行布丁代码就能解决的问题,干嘛不在自己组件里面兼容掉?果断弃之,不可惜

  • iscroll:性能问题


640?wx_fmt=png

640?wx_fmt=png

640?wx_fmt=png


对于一个 150 个元素的列表,左图为 iScroll 的 FPS;中间图为iScroll+lazyload 的 FPS;最右图为 native scroll +my pulltorefresh (下一段落详细说)的 FPS,看曲线,别看瞬时帧频,其实差距挺大但没有那么大。


所以,iscroll, 1 秒 3 帧。。。泪流满面,着实让我体会了一把页面卡成 PPT 的感觉。


对了,测试机器也提一句,是我的备用机。为了避免广告,我只能说是手机界说相声说的最好的那个胖子旗下的低端千元机。


不得已而为之,自造轮子

要把大象装冰箱,总共分几步?


第 1 步:监听原生 touchstart 事件,记录滑动起始位置ev.touches[0].pageY

第 2 步:监听原生 touchmove 事件,记录最新滑动位置与起始位置ev.touches[0].pageY的像素差 pullLength,如果大于 0 则为向下拖动,此时将pullLength结果同步设置成目标元素 translateY 值,实现元素跟随手势向下移动。当然,元素移动距离是要有上限的。

第 3 步:监听原生 touchend 事件,如果元素已在页面顶部,且下pullLength大于一个阈值(默认 60px),则触发 callback,并且将 translateY 的取值还原为 0,恢复目标元素位置 (有过渡效果是坠吼滴!)


源码很简单,短短 65 行,请戳 这里(https://github.com/elevenbeans/my-utils/blob/master/src/pulltorefresh.js)。在线 Demo 请戳 这里(http://elevenbeans.github.io/my-utils/src/pulltorefresh.html)(移动设备或模拟器查看)


React 版 PullToRefresh 新鲜出炉,源码请戳 这里(https://github.com/CtripFE/triplet/tree/master/src/components/pulltorefresh),在线 Demo 请戳 这里(https://ctripfe.github.io/triplet/demos/),Doc 请戳 这里(https://ctripfe.github.io/triplet/#/pulltorefresh)。


这可能是史上最简陋的 JS 下拉刷新组件了吧 =,=


示例代码:


请注意:组件本身,不提供默认的 loading 提示元素!不提供默认的 loading 提示元素!不提供默认的 loading 提示元素! 需要自行设计样式并正确绑定,如下方代码中的 “ptr-instructions”。(提供了真的会有人不改直接用么???)


<div id = "ptr-instructions">

<!--your loading icon here-->

<span class= "ptr-instructions-text"></span>

</div>

<div id = "main">

<div class = "item">1</div>

<div class = "item">2</div>

<div class = "item">3</div>

<div class = "item">4</div>

<div class = "item">5</div>

<div class = "item">6</div>

<div class = "item">7</div>

<div class = "item">8</div>

<div class = "item">9</div>

<div class = "item">10</div>

<div class = "item">11</div>

<div class = "item">12</div>

<div class = "item">13</div>

</div>


pullToRefresh.init({

// required

ptrElement: '#ptr-instructions', // 'pull to refresh' intructions element

ptrTextElement: '.ptr-instructions-text', // intructions' text element

targetElement: '#main', // target element that will be dragged and refreshed

// optional

instructionsPullToRefresh: 'pull to refresh', // text

instructionsReleaseToRefresh: 'Release to refresh', //text

instructionsRefreshing: 'refreshing', // text

threshold: 60, // minimum distance required to trigger the onPull callback

onPull: function(){ // callback fn

console.log('onPull fired');

}

});


Done ~


觉得本文对你有帮助?请分享给更多人

关注「前端大全」,提升前端技能

640?wx_fmt=png

阅读更多
想对作者说点什么?

博主推荐

换一批

没有更多推荐了,返回首页