農林漁牧網

您現在的位置是:首頁 > 林業

前端頁面雙向滾動方案

2021-08-21由 技術聯盟總壇 發表于 林業

移動端寬高怎麼設定

安箋 淘系技術

前端頁面雙向滾動方案

脫離canvas後,頁面如何實現上下左右雙滑動?又如何在安卓系統和iOS系統上實現?

背景

在許多業務場景中會遇到讓一個頁面在web端可以實現雙向滾動,且在滾動的時候可以上部吸頂,左側吸在最左邊這中類似excel表現的述求。

但是excel移動端本身使用canvas來畫的,所以能保持較好的體驗和效能,那麼脫離canvas,我們是否有別的方案可以去靠近實現呢?如何在移動端實現,並同時相容安卓和ios系統?

實現目標

如下圖所以,可以左右移動,也支援上下移動,在移動的對應方向的頂部需要吸頂。在pc端實現較為簡單,透過滾動就可以實現,在移動端要想實現這一套方案,並同時相容不同系統,這裡我踩了一些雷,下文將訴說實現這個的全部技術方案的改變和踩雷的一些點。

前端頁面雙向滾動方案

技術方案

▐ 初期思想

前端頁面雙向滾動方案

如上圖所示,不就是橫向滾動和豎向預設滾動嗎?

移動端螢幕寬高固定,本就可以左右滾動,那麼只需要元素本身的大小大於螢幕的寬高不就可以了嗎?

設定一個div,寬大於screen。width, 高大於screen。height即可,最終demo大概效果如下圖所示:

前端頁面雙向滾動方案

左右的確都相當的絲滑,能橫著滾,也能豎著滾了,就是全方位也都能滾動,著實詫異了一會兒。

▐ 更新思維

如果那麼當我朝著某個方向滾動的時候,禁掉一個方向的滾動,就可以解決了。

但是容器巢狀就出問題了,橫向滾動的時候,我們領容器高度變成視口高度,overflow-y設定為hidden,橫向依舊可以滾動,縱向設定為禁止滾動,在chrome裡模擬了一下,的確不錯,在真機上測試的時候,發現安卓端正常,ios端一旦手動設定overflow-y,發現縱向原先滾動到的位置會丟失,縱向內容直接回到最頂部,即縱向scrollTop變成了0。

設定縱向滾動,橫向overflow-x為hidden時,橫向原先滾動的距離也會被清除,回到最左側。

那麼透過手動記錄之前的滾動位置,在設定某一方向禁止滾動時,把高度給他還原回去,就能解決了。

思路是對的,但是會出現強烈的閃屏現象,尤其在這種資料密集型的頁面上(ios頁面也閃,低端安卓機沒做嘗試,meta30存在一定延遲的閃屏)。

▐ 改變方案

雙向滾動到此基本上算是失敗了。

單向滾動肯定沒問題,另一個方向我們可以使用模擬滾動的方式來自己寫一套“滾動”效果。

首先我們判斷橫向滾動和縱向滾動那一方向使用原生滾動呢?

我選擇了縱向,縱向資料體量大,容易有滾動的訴求,橫向存在資料量不足可能不需要滾動。

效果如下圖所示:

前端頁面雙向滾動方案

左側第一列和頂部第一行吸頂這裡使用了css的屬性 position:sticky。

也有人可以考慮使用一些特殊佈局,例如左側做絕對定位,右側內容自行滾動等。

模擬滾動的方式,我們採用touch事件來仿造,透過touchStart、touchMove、touchEnd 這3個事件來模擬scroll事件。

首先在touchStart中獲取手指頭接觸到螢幕的點的 (x,y) 座標,然後在touchMove事件中獲取螢幕出點的第二個觸點的(x,y)座標,來獲取觸控方向。

//獲得角度function getAngle(angx, angy) { return Math。atan2(angy, angx) * 180 / Math。PI;};function getDirection(startx, starty, endx, endy) { const angx = endx - startx; const angy = endy - starty; let result = 0; //如果滑動距離太短 if (Math。abs(angx) < 2 && Math。abs(angy) < 2) { return result; } const angle = getAngle(angx, angy); if (angle >= -150 && angle <= -30) { return ‘top’; } else if (angle > 30 && angle < 150) { return ‘down’; } else if ((angle >= 150 && angle <= 180) || (angle >= -180 && angle < -150)) { return ‘left’; } else if (angle >= -30 && angle <= 30) { return ‘right’ }}

由於大家在使用手機時,橫向滑動的行為軌跡並非是一個水平180度的橫線,所以在這裡設定了一定值,例如多少角度到多少角度認為是橫向滾動。

方向定好,多少角度這個可以根據自己業務上的判斷做修改。

前端頁面雙向滾動方案

連續觸控滑動的座標軸我們已經能拿到了,透過css3的transform來使元素進行移動,透過不斷修改移動的值達到元素滾動的效果。

這裡有一點需要注意,並非我們觸控劃過多少畫素,元素就移動多少畫素,這裡還涉及一個速度和距離的概念。

還有我們手指頭鬆開以後,元素應該還會滾動一部分,然後速度慢慢降下來,直到0為止。

先分析一直處於觸點滑動這個階段,我們並非劃過多少畫素,元素就跟著滾動多少畫素,這裡看情況來放大或者縮小這裡的滾動值,體現給使用者的感受就是滑的快還是劃得慢。

我們由於橫向資料量並不大,所以這裡我們所有滾動距離都做了一定縮小處理。

// 程式碼只留個框架function rowScrollAction(scrollDirection, endx, isEnding = false) { scrollGap = (endx - startx) * 0。85; // 每個滾動距離都乘上0。85 let scrollLen = preX + scrollGap; // preX為之觸控之前已經滾動到的位置 if (isEnding) { scrollLen = endx } if (scrollDirection === ‘right’) { 。。。。 } if (scrollDirection === ‘left’) { 。。。。 } }

這裡還需要注意的點是,如果已經滾動到橫向邊界了,那麼就不允許在滾動了,這裡需要特殊處理一下。

那麼觸控結束以後,元素還應該按照慣性繼續向對應方向進行滾動,速度慢慢下降,直到結束為止。

速度不能直線下降,不能是一次函式,因為:

y=ax+b

a代表加速度,加速的值一直不變,那麼就會勻速降下去,體驗上並不好,這裡選擇了二次函式的概念。

前端頁面雙向滾動方案

如圖所示,慢慢靠近目標值,那麼就要口子朝下的拋物線,且是對稱軸左側的這一部分函式。根據函式定義:

前端頁面雙向滾動方案

透過a、b引數來產出一個對應計算函式:

// 將“當前時間”對映[0, 1]間(currentTime /= duration)// 根據緩入公式計算出“當前時間”應該移動的百分比(currentTime * currentTime)// 根據“總移動長度”和“移動百分比”算出應移動的具體值(changeValue * 。。。)// 加上初始位置(+ startValue)function easeOut(currentTime, startValue, changeValue, duration) { currentTime /= duration; return -changeValue * currentTime * (currentTime - 2) + startValue;}

本業務場景中的實現程式碼如下:

/*** target: 滾動最終畫素值*/const scrollAnimation = (target) => { function easeOut(t, b, c, d) { return -c * (t /= d) * (t - 2) + b; // 用時間t做x變數 } var toTarget = target; var endTimer = 500; var startTimer = 0; var step = function () { let value = easeOut(startTimer, preX, toTarget, endTimer); // 返回一個距離值 // rowScrollAction(tempDirection, value, true); 執行對應元素滾動多少畫素的方法 startTimer += 25; if (startTimer <= endTimer) { // 繼續運動 requestAnimationFrame(step); } else { // 動畫結束 touchTimes = 0; // 動畫結束後的回撥 } }; step(); }

當滾動到邊界時,記得清空迴圈計算哦

▐ 最後最佳化

當橫向滾動到邊界時,模擬ios的彈簧效果(安卓和ios端保持一致)

前端頁面雙向滾動方案

實現方式就是對3。3中的滾動到邊界時做對應的處理。我們達到邊界以後,在滾動距離上多加50~100個畫素值,然後利用拋物線的原理:

前端頁面雙向滾動方案

利用兩邊的值,先慢慢靠近最高點,然後再回到對應y值上。

結論

接手一個專案,要嚴謹一點,如果是之前沒接觸過這種技術方案開發的,應該拿到專案時先寫個小demo,然後再去評估開發時間。

不然腦海中出現的技術方案可以讓你當場拍板,但是在實踐中會發現出現預估錯誤,可是開發排期影響的就是整個小team的時間,你的delay很可能導致大家的排期都被打亂,不得不從拼命加班搶回開發時間。

還要學會給自己留充足的buffer能夠去試錯,能夠應對突發情況。