1. 程式人生 > >在swiper中使用長頁面,以及巢狀多個swiper時滑動卡頓、無法滑動的問題。

在swiper中使用長頁面,以及巢狀多個swiper時滑動卡頓、無法滑動的問題。

前言

一般而言,swiper的應用場景大多是兩種:

  1. 滿屏切換的H5頁面
  2. pc&移動端各種樣式的輪播圖

但有的時候,面對奇怪的需求,我們需要改變,甚至讓swiper實現一些無法實現的功能。

需求

近期接到一個h5專案,主體頭部是一個選項卡,對應兩個子頁面,每個子頁面的第一屏為一個滿屏的kv,監測到向下滑動時平滑過渡到第二屏,而第二屏是一個長頁面

思路

首先,要做滑動體驗友好的抵抗/回彈效果,首先想到的是iScroll和Swiper,考慮到長頁面中有其他的輪播、燈箱部分,最後選擇使用Swiper做這次的專案。

疑問

之前並沒有接觸過slide高度不同的情況,就算有時做輪播,圖片的尺寸不同,slide的寬高也是固定的,裡邊圖片的部分新增如下css控制圖片不變形。

width:100%;
height:auto;

更不要說某個slide是一個高度不確定的長頁面了,直接給slide一個超出一屏的高度,是無法正常滑動的。

當然,如果把第二屏的長頁面也寫成滿屏,加上overflow:hidden讓頁面在一屏裡滾動,同時:-webkit-overflow-scrolling:touch提升一些使用者的滑動體驗,理論上是可以達到需求的效果,但是考慮如下結構:

<div class="swiper-container" id="swiper">
    <div class="swiper-wrapper">
        <div
class="swiper-slide" id="kv"></div> <div class="swiper-slide" id="long"> <div class="scroll-part"></div> </div> </div> </div>

假設#kv為第一屏的滿屏slide,#long為第二屏的長頁面,.scroll-part#long內與之寬高相同的一個可內部滾動的div,那麼當.scroll-part滑動到最頂部(同時也是#long

應該觸發slide切換事件的位置)時,並不會如預期那樣由#long向上切換到了#kv,而是.scroll-part#long發生黏連,一起向下移動並漏出了瀏覽器的背景,如果此時鬆開手指,再次同向滑動,才會發生正常的slide切換。

同時考慮到-webkit-overflow-scrolling在部分安卓機上的表現並不如人意,故不採用此種方法。

那麼,怎麼用swiper來開發一個包含不定長度slide的專案?

過程

網上並沒有類似的案例,不過幸運的是在github上,swiper的原作者給出了一種解決方案

var swiper = new Swiper('#swiper', {
    direction: 'vertical',
});
var startScroll, touchStart, touchCurrent;
swiper.slides.on('touchstart', function (e) {
    startScroll = this.scrollTop;
    touchStart = e.targetTouches[0].pageY;
}, true);
swiper.slides.on('touchmove', function (e) {
    touchCurrent = e.targetTouches[0].pageY;
    var touchesDiff = touchCurrent - touchStart;
    var slide = this;
    var onlyScrolling = 
            ( slide.scrollHeight > slide.offsetHeight ) && //allow only when slide is scrollable
            (
                ( touchesDiff < 0 && startScroll === 0 ) || //start from top edge to scroll bottom
                ( touchesDiff > 0 && startScroll === ( slide.scrollHeight - slide.offsetHeight ) ) || //start from bottom edge to scroll top
                ( startScroll > 0 && startScroll < ( slide.scrollHeight - slide.offsetHeight ) ) //start from the middle
            );
    if (onlyScrolling) {
        e.stopPropagation();
    }
}, true);

不過,這樣只是解決了在一個長頁面的slide裡可以正常的上下滑動,由於這個專案中,長頁面裡還有幾個橫向的輪播,我又發現了很奇怪的現象,
1. 當且僅當長頁面滑動到最頂端或者最底端時,內部的橫向swiper才能正常滾動,否則是處於“鎖定”的狀態,
2. 在上下滑動的時候,如果touchstart的區域是橫向swiper的所在的位置,此時的視窗可視區域和長頁面會發生黏連,導致頁面無法滾動。

列印上面程式碼中的關鍵所在onlyScrolling發現,只有在長頁面的頂端或者底端時,值才是false,從而得出結論:

onlyScrolling為true時,滑動事件被阻止向內部swiper冒泡,導致內部swiper行為異常

對程式碼進行修改:

var swiper = new Swiper('#swiper', {
     direction: 'vertical'
 })
 var startScroll, //開始滾動時slide的scrollTop
     touchStart, //滑動開始滑鼠y座標
     touchCurrent,//滑動實時滑鼠y座標
     ifContains;//是否包含內部swiper
 swiper.slides.on('touchstart', function (e) {

     startScroll = this.scrollTop;
     touchStart = e.targetTouches[0].pageY;
     var target = e.target;

     var contains_top = $.contains(document.getElementById('swiper_inner1'), target);
     var contains_middle = $.contains(document.getElementById('swiper_inner2'), target);
     var contains_bottom = $.contains(document.getElementById('swiper_inner3'), target);

    //swiper_inner1、swiper_inner2、swiper_inner3為內部swiper

     if (contains_top || contains_middle || contains_bottom) {
         ifContains = true
     }
 }, true);
 swiper.slides.on('touchmove', function (e) {
     touchCurrent = e.targetTouches[0].pageY;
     var touchesDiff = touchCurrent - touchStart;//滑鼠滑動y座標差值
     var slide = this;
     var onlyScrolling =
         (slide.scrollHeight > slide.offsetHeight) &&
         (
             (touchesDiff < 0 && startScroll === 0) ||
             (touchesDiff > 0 && startScroll === (slide.scrollHeight - slide.offsetHeight)) ||
             (startScroll > 0 && startScroll < (slide.scrollHeight - slide.offsetHeight))
         );
     if (onlyScrolling && !ifContains) {
         e.stopPropagation();
     }
 }, true);
 swiper.slides.on('touchend', function (e) {
     ifContains = false;
 }, true)

由於後期需求的更改,長頁面內的swiper僅支援點選按鈕切換slide,所以上面的問題2也就不再是問題了,所以程式碼沒有保留,主體思路是監聽touch事件的x座標,判斷是橫向滑動且滑動區域在內部swiper上,強制賦值onlyScrolling

此時如果還是存在垂直滑動不流暢的情況,可以考慮適時的將父swiper禁止切換slide,這樣就不會影響內部的滑動,同時將body固定,模擬原生APP體驗:

var LONG_SCROLLTOP = $('#long').get(0).scrollTop;

if(LONG_SCROLLTOP < 100){
    $('#long').removeClass('swiper-no-swiping');
}else{
    $('#long').addClass('swiper-no-swiping');
}

// .swiper-no-swiping是swiper外掛的配置項,作用為禁止切換slide,詳情參考swiper API

if(LONG_SCROLLTOP > 800){
    $('body').css('position','static');
}else{
    $('body').css('position','fixed');
}

結語

總之沒想到用swiper寫長頁面這麼多坑,其實最外層完全可以手寫一個類似swiper切換的滑動效果,可能反而會更節省時間。以後拿到專案不能第一反應就是用庫、外掛,仔細分析需求與可用工具的契合度,酌情選擇開發工具,否則可能會適得其反。

如有錯誤,歡迎指正!