應用場景
僅僅應用於單頁應用的滑動操作,用swiper4.x
接管頁面的滾動操作。用來支援頂部和尾部的回彈效果,進一步來支援常見那種下拉重新整理動畫效果。不適用於輪播圖那種應用場景。
雖然只是針對swiper4.x
,但相關原理,在別的框架中也是有參考意義的。
出現的問題
一、慣性動畫不會在觸控時停止
快速滑動頁面,手離開螢幕時產生的慣性動畫還在執行時,此時觸控螢幕,動畫不會停止。導致連續快速滑動頁面,看起來有跳來跳去的感覺。
二、快速滑動手勢大概率識別成慢滑動手勢
連續快速多滑幾下,有那麼1、2下明顯能夠感知到滑動慣性動畫變慢的情景,給人的感覺就是滑動不流暢。
三、慣性動畫時長不合理
不管是很慢的滑動,還是很快的滑動,慣性動畫時長只能設定一個固定值。慢慢移動鬆手後也會有一個很長的動畫,快速滑的時候動畫又有點短,綜合看起來給人很卡的感覺。
解決手段
除了第一個問題,另外兩個問題不修改swiper4.x
原始碼似乎無法克服。
一、解決慣性動畫不會在觸控時停止的問題
滑動時手離開螢幕時產生的慣性動畫是css動畫
,並未直接提供停止動畫的方法。動畫還在執行時如果觸控螢幕,不改原始碼,那我們就監聽一下touchStart
事件。
用setTranslate(translate)
可以移除這個動畫css,需要提供頁面當前的滾動位置。用getTranslate()
就行了:
這與通過屬性
mySwiper.translate
獲取到的數值稍有不同,即使是在過渡時(animating
)也能獲取到,而後者精度較高
解決方案程式碼
//在touchStart事件中執行
mySwiper.setTranslate(mySwiper.getTranslate());
複製程式碼
二、解決快速滑動手勢大概率識別成慢滑動手勢問題
這個必須修改原始碼才能解決,問題出在計算慣性動畫起始速度時的計算引數精度不足。
原始碼
if (params.freeModeMomentum) {
if (data.velocities.length > 1) {
var lastMoveEvent = data.velocities.pop();
var velocityEvent = data.velocities.pop(); //此處取值方式會導致精度不夠
var distance = lastMoveEvent.position - velocityEvent.position;
var time = lastMoveEvent.time - velocityEvent.time;
swiper.velocity = distance / time;
swiper.velocity /= 2;
複製程式碼
可以看出,他計算起始速度velocity
參考的是onTouchMove
最後記錄的兩個點,單純取最後兩個點是不可靠的,可能因為最後兩次onTouchMove
觸發間隔比較長,導致計算出來的速度過低。從而導致偶爾會快速滑動但慣性效果是慢滑動的效果。
如果觸發間隔很短,導致動畫速度變快,變快了其實感知上並無區別。最要命的還是變慢,感覺很卡一樣。
解決方案
一個很短的時間內,人的滑動方向不太可能會產生變化,但這個短時間內產生的滑動位移能夠很好的代表手勢結尾的滑動速度。
經過反覆嘗試,用100毫秒內的位移來計算慣性起始速度最好,100毫秒內,如果是快速滑動,會觸發多次onTouchMove
,計算出來的速度是很接近實際的手勢速度。
問題的解決指向了100毫秒內的第一個點。解決程式碼:
if (params.freeModeMomentum) {
if (data.velocities.length > 1) {
//fix 慣性動畫
var velos=data.velocities;
var firstEvent=velos[0];
var lastMoveEvent = velos.pop();
var velocityEvent =velos.pop();
for(var i=velos.length-1;i>=0;i--){//找出100毫秒內的起始位置
var velo=velos[i];
if(lastMoveEvent.time-velo.time>100){
break;
}
velocityEvent=velo;
}
var distance = lastMoveEvent.position - velocityEvent.position;
var time = lastMoveEvent.time - velocityEvent.time;
swiper.velocity = distance / time;
swiper.velocity /= 2;
複製程式碼
三、解決慣性動畫時長不合理問題
慢滑動時慣性應該很小,動畫很短;快速滑動時慣性應該很大,動畫很長。swiper4.x
只能提供一個固定的慣性動畫時長,不改原始碼是解決不了的。
原始碼在第二問程式碼下面一點點
var momentumDuration = 1000 * params.freeModeMomentumRatio; //寫死了固定動畫時長
複製程式碼
優化動畫
其實也簡單,小的就小,大的就大,用初始速度velocity
來做乘法運算即可達到效果。
var momentumDuration = Math.abs(swiper.velocity) * 1000 * params.freeModeMomentumRatio;
複製程式碼
結果:比原始碼好很多,但跟別的app裡面的滑動還是區別蠻大,慢滑時還是太快了點。
App原生滑動動畫,感知上是慢的更慢,快的更快。輕微滑動慢的要死,快速動一點點,飛快。
對數曲線!1-0範圍蠻符合,緩的地方比線性的還緩,陡的地方奇陡無比。在上面優化的結果基礎上用對數加持一下,效果非常不錯。另外限定最長動畫不超過3秒。
//取對數曲線優化一下0.5-1.5倍之間,慢的越慢,快的越快
momentumDuration*=-Math.log10(Math.min(0.3,Math.max(0.03,(3000-momentumDuration)/3000)));
複製程式碼
解決方案程式碼
//根據初始速度來確定慣性動畫時長
var momentumDuration = Math.min(3000,Math.abs(swiper.velocity) * 1000 * params.freeModeMomentumRatio);
//取對數曲線優化一下0.5-1.5倍之間,慢的越慢,快的越快
momentumDuration*=-Math.log10(Math.min(0.3,Math.max(0.03,(3000-momentumDuration)/3000)));
momentumDuration=Math.min(3000,momentumDuration);
複製程式碼
最終結果
應用swiper4.x
修改後的單頁滾動效果,已經很接近App的滾動效果,雖然還是會有些細微的抖動卡頓感,但要做到完全和App一樣的流暢效果,估計蠻難,當前算是已經蠻優了。
還是iscroll省心些,但已經上了swiper4.x
這條船了就算了,遷移過來又遷移過去白折騰。
改良後的滾動效果,目前[2019-02-27]號以後可以到 jiebian.life/start/xcx/t… 體驗(這個時間之前新版本應該還沒有上線),小程式和H5共用的頁面。