實現一個 Swiper

發表於2017-07-17
設計一個五圖的 Swiper,設計稿如下:
插圖Swiper 的功能如下:
  1. 左右切換
  2. 無限輪播
  3. 任意圖片數

接下來,詳細介紹這三個功能的實現過程:

左右切換

這裡指觸發左右切換的手指互動,目前主要是以下兩種:

方案 示意圖
手指拖拽 拖拽
手勢判斷 手勢

手指拖拽容易有效能問題並且實現相對麻煩,所以筆者果斷採用了手勢判斷,虛擬碼如下:

無限輪播

無限輪播要面對的是兩個問題:

  • 輪播的資料結構;
  • 前端渲染

資料結構

無限輪播筆者聯想到旋轉木馬。
輪圈圈

在資料結構中有一個叫迴圈連結串列的結構,可以完美地模擬旋轉木馬。
迴圈連結串列

javascript 沒有指標,連結串列需要由陣列來模擬。分析迴圈連結串列的兩個重點特徵:

  1. 資料項都由頭指標訪問
  2. 連結串列頭尾有指標串聯

筆者用 pop&unshift/shift&push APIs 模擬指標的前後移動,解決了連結串列頭尾串聯的問題,然後用陣列的第一個元素(Arrayy[0])作為頭指標。

迴圈連結串列

虛擬碼如下:

前端渲染

swiper 換個角度來看,它其實是一個金字塔:
實現一個 Swiper

梳理好層級問題再把過渡補間寫上,swiper 的渲染就已經OK了。以下是虛擬碼:

任意圖片數

圖片數可以分成三種情況來討論:count == 5; count > 5; count 。其中 count == 5 是理想條件,上幾節就是圍繞它展開的。本節將分析 count > 5 與 count 的解決思路。

count > 5

將迴圈連結串列(5節)擴容:
擴容

擴容後的工作過程如下:

  1. 迴圈連結串列指標移動;
  2. 渲染節點(1, 2, 3, n-1, n);
  3. 回收節點(4, 5, …, n-2)。

注:這裡的回收節點指隱藏節點(display: none/visibility: hidden)

渲染金字塔如下:

渲染金字塔

為了提高效能筆者在迴圈連結串列與節點中間建立了一個快照陣列 snapshot,snapshot 對映節點上的屬性,迴圈連結串列每一次變動都會生成一個新的快照陣列 nextSnap,通過 nextSnap 來更新 snapshot 與 節點樣式。以下是實現的虛擬碼:

count

count >= 5時,渲染節點是一個穩定的金字塔:

渲染金字塔

count 時,渲染金字塔變得不確定:

count 金字塔
1 渲染金字塔
2 渲染金字塔
3 渲染金字塔
4 渲染金字塔

由於只有 count == 1 ~ 4 四種情況,可以直接用個 swith 把狀態列表出來:

上面的虛擬碼顯得很冗長,並不是個好實現方式。不過仍能從上面程式碼獲得啟發: 渲染列表(renderList) 與迴圈連結串列(queue)的對應關係 —— [shift, pop, shift, pop, shift]。於是虛擬碼可以簡化為:

 

細節優化

筆者實現的 swiper: https://leeenx.github.io/mobile-swiper/v1.html

(count >= 5)執行效果如下:
v1

仔細觀察能看到切換效果上的小瑕疵:
v1

造成這個瑕疵是因為同值 z-index 節點的渲染層級與 DOM 樹的出現順序相關: 後出現的節點層級更高。

解決方案很簡單,為 swiper 新增一個 translateZ 。如下虛擬碼:

新增 z-index 後的swiper: https://leeenx.github.io/mobile-swiper/v2.html
v2

再看看 count 的執行效果:

count 效果圖 地址
1 實現一個 Swiper https://leeenx.github.io/mobile-swiper/v2.html?count=1
2 實現一個 Swiper https://leeenx.github.io/mobile-swiper/v2.html?count=2
3 實現一個 Swiper https://leeenx.github.io/mobile-swiper/v2.html?count=3
4 實現一個 Swiper https://leeenx.github.io/mobile-swiper/v2.html?count=4

count == 2 / count == 4 時,swiper 向右切換時怪怪的,總感覺有什麼不對!!其實問題出在渲染金字塔上,偶數swiper 在視覺在不是一個對稱的圖形:

渲染金字塔

由於筆者使用定勢渲染的原因造成金字塔底被固定在左側,當向右側切換時會覺得很奇怪。這裡其實只要加一個方向修正即可,以下是修正的虛擬碼:

修復後的效果如下:

count 效果圖 地址
2 實現一個 Swiper https://leeenx.github.io/mobile-swiper/index.html?count=2
4 實現一個 Swiper https://leeenx.github.io/mobile-swiper/index.html?count=4

總結

感謝閱讀完本文章的讀者。本文最終實現的 swiper 筆者託管在 Github 倉庫,有興趣的讀者可以看一下:https://github.com/leeenx/mobile-swiper

希望對你們有幫助。

相關文章