乐趣区

移动端-局部滚动

起因

我司最近在做一个 H5, 有一个模拟微信对话框的需求,具体需求如下。

  1. 对话内容固定,但需要一句一句显示
  2. 对话内容超过一屏后,需要使对话内容上移
  3. 对话内容结束后,用户可以上下滑动对话框,查看详细对话内容
  4. 图中灰色头像表示获取的用户头像

示例:请看第二屏

初步设想

  1. 使对话内容一句一句显示,脑子里立马闪现出 setInterval 定时器。
  2. 对话内容超过一屏,使对话内容上移,当然是改变父元素的 scrollTop 值啦
  3. 用户可以上下滑动对话框,就类似于滚动条效果,设置父元素高度并且 overflow:hidden,子元素高度 auto 即可。
  4. 获取用户头像,这个薛微复杂,留做下一篇文章。

遇到问题

局部滚动效果,以上想法(设置父元素高度并且 overflow:hidden)在 PC 端可以正常滑动,但 在移动端失效
这种写法,单独写没有问题,但是 IOS 端出现卡顿现象,可以添加 -webkit-overflow-scrolling:touch; 解决。

但是,我司的 H5 页面使用的 swiper 制作,大概是这个有一些影响,用户滑动屏幕首先触发了 swiper 的事件。(仅做设想,后续做进一步实践)

于是在网上查了几番,有以下几种解决方法

  • 用户在解发 touchmove 事件时,改变元素的 transform 值
  • 使用 iscroll.js
  • 使用 swiper

改变元素的 transform 值

改变元素的 transform 值,需要判断用户的滑动方向。
判断滑动方向时,先了解两个事件

  • touchstart : 用户手指按在屏幕上时触发
  • touchmove:用户滑动屏幕时触发

了解了这两个事件,我们可以在用户触发 touchstart 事件时,记录手指位置,在 touchmove 记录获取手指最后停留的位置
判断 最后停留位置 – 初始位置 = pageY- startY = 即用户滑动方向

(pageY-startY)为正数时,说明用户向下滑动; 为负数时,说明用户向上滑动。

$(".message-wrapper").on("touchstart", function (e) {startY = e.type === 'touchmove' ? e.targetTouches[0].pageY : e.pageY;
})
$(".message-wrapper").on("touchmove", function (e) {pageY = e.type === 'touchmove' ? e.targetTouches[0].pageY : e.pageY;
     
})

使用 iscroll.js

网上有很多关于 iscroll 的资料,但是我查了一下官方的 github, 最近的更新在 5 年前,果断不敢用~

使用 swiper

查了一番,swiper 里的 swiper-scrollbar可以完美的实现这一功能,单独写这一功能,在真机测试没有问题。然后移入到我们的项目中。出现以下几个问题

局部滚动后的 slide 元素不显示

分析原因

出现这个问题的原因,是由于我司的 H5 项目也是由 swiper 制作,这意味着每一屏就相当于一个 slide, 当添加用 swiper 完成的局部滚动时,会造成后面父元素的 slide 元素不显示。

解决办法

这涉及到多个 swiper 嵌套使用的问题,具体修改如下:

  1. 当页面存在多个 swiper,初始化时,尽量避免使用一样的类名,如 .swiper-container, 每个 swiper 有它单独的类名
<div class="swiper-container main-swiper"> // 父元素 swiper
  <div class="swiper-wrapper">
        <div class="swiper-slide slide1"></div>
        <div class="swiper-slide slide2">
            <div class="swiper-container message-warp"> // 子元素 swiper
              <div class="swiper-wrapper message-wrapper">
                <div class="swiper-slide message-slide"></div>
              </div>
           </div>
        </div>
        <div class="swiper-slide slide3"></div>
  </div>
</div>


//-------------------------------------------------------------swiper 初始化

    
  1. 如果类名分开,父元素后续 slide 元素依然无法显示
    将子元素的初始化,写在父元素初始化之前, 加载时,优先初始化子元素 swiper


    
    // 初始化子 swiper
      var scrollSwiper = new Swiper('.message-warp', {
            observer: true,
            observeParents: false,
            scrollbar: '.swiper-scrollbar',
            direction: 'vertical',
            slidesPerView: 'auto',
            mousewheelControl: true,
            freeMode: true,
     })

   var swiper = new Swiper('.main-swiper', {
        direction: 'vertical',
        touchRatio: 0.5,
        loop: false,
        on: {init: function () {swiperAnimate(this);
            },
            slideChangeTransitionEnd: function (e) {swiperAnimate(this)
            }
        }
    });
  1. 以上方法都不能使后续 元素显示

swiper 运行时,会先给元素添加 visiblity:hidden; 使元素隐藏,只给当前页的 visiblity 设置为 visible; 而 swiper 中,改元素显示状态的依据就是 swiper-slide-active;

swiper 默认给当前的 slide 添加 swiper-slide-active 类名。当页面中存在 swiper 嵌套时,父元素的当前 slide 会添加该类名,子元素的当前 slide 也会添加该类名。

这样当用户滑出父元素的当前 slide 时父元素的 swiper-slide-active 被移除,而子元素的 swiper-slide-active 类名并没有移除,造成 swiper 混乱,所以父元素后续 slide 的元素会无法显示

解决办法

我的做法是在父元素切换 slide 后,判断页面中 swiper-slide-active 的个数,如果存在一个以上,则说明子元素的类名没有移除。
手动将子元素的 swiper-slide-active 类名移除即可。
暂时还没有想到更好的方法,如果你有更好的方法,欢迎一起讨论。

    if ($(".swiper-slide-active").length == 2) {$(".message-slide").removeClass("swiper-slide-active")
    }
退出移动版