在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切换的滑动效果,可能反而会更节省时间。以后拿到项目不能第一反应就是用库、插件,仔细分析需求与可用工具的契合度,酌情选择开发工具,否则可能会适得其反。

如有错误,欢迎指正!

你可能感兴趣的:(javascript)