60 行 JS 程式碼搞定一個下拉重新整理元件

發表於2018-04-11

神馬? 瀏覽器不都有自帶的重新整理按鈕麼?

Web 頁為什麼要下(zi)拉(xing)刷(che)新 ?

這裡特指移動端,原因如下:

  • 相較於點選右上角重新整理按鈕(還有可能要點兩次,第一次先展開 menu bar,然後才能看到 refresh 按鈕),直接了當地下拉重新整理無疑提供了更好的使用者體驗
  • 點選重新整理按鈕同步過載頁面必然存在一定白屏時間,而通過下拉重新整理的邏輯完全可以對於頁面內容進行非同步更新,其體驗毫無疑問更加優秀
  • 移動端特有 touch 相關事件,使用者在移動裝置上的觸控、滑動操作頻繁,習慣已經養成,下拉重新整理在提供更好的體驗的同時,絲毫沒有增加使用者的學習成本
  • 很多內容 + 社交的業務場景裡面,主頁面的存留時長極高且內容實時性強(如微博、知乎、頭條等), 這些 Native App 已經普遍向使用者提供了這種(下拉重新整理)更新頁面內容的互動方式。作為一個 Web 開發者,如有志於在移動領域讓 Web App 和 Native App 在體驗方面一較高下,那 H5 頁(非同步)下拉重新整理功能也算是不可或缺的一環
  • 當然,還可以應對一些特殊場景 … (如 Webview 不提供重新整理按鈕 =,=)

精簡成人話其實就是:被互動逼的

現有哪些輪子可以做到?

目測這個是目前 Github 上最流行的 JS 下拉重新整理元件了(15K stars)。highly customizable and dependency-free!
看使用方法,真的是簡潔明瞭:

不過這種單功能元件暴露了 20 個 API,著實有點多了。當然原始碼中有預設的一系列 default setting,個人覺得負擔不大但不必要。

看到一個 React 版本的就簡直看到了親人吶。雖然“只有” 200 stars,但是我們也不能完全以“星”取人啊,果斷開始搗鼓。

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:效能問題
    img
    img
    img

對於一個 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 行,請戳 這裡。線上 Demo 請戳 這裡(移動裝置或模擬器檢視)

======= Updated 2018-01-17 ======

React 版 PullToRefresh 新鮮出爐,原始碼請戳 這裡,線上 Demo 請戳 這裡,Doc 請戳 這裡

====== end ======

這可能是史上最簡陋的 JS 下拉重新整理元件了吧 =,=

生產環境也在使用:大豚廠 IBU H5 首發航班動態求公測

示例程式碼:

請注意:元件本身,不提供預設的 loading 提示元素!不提供預設的 loading 提示元素!不提供預設的 loading 提示元素! 需要自行設計樣式並正確繫結,如下方程式碼中的 “ptr-instructions”。(提供了真的會有人不改直接用麼???)

Done ~

At last

360 度托馬斯迴旋跪地求星星: Github Repo

相關文章