swiper loop时碰到的问题

最近在做一个类似于抖音那样的下划视频的小项目,需要可以上下划动,同时当前视频自动开始播放,非当前视频全部暂停。

上下滑动的实现其实还挺复杂的,涉及动画等多个方面的问题。还好我们不用「造轮子」,直接用上经典的滑动工具swiper就行。果然,滑动效果非常流畅,通过渲染函数里的isActive参数来控制视频的播放也非常的简单方便。然而,在根据要求加上无限滚动(loop)之后,一切就不一样了。

出现两个同时播放的视频

在滑动之后,发现同时有两个视频正在播放,一个在可视范围内,一个在可视范围外。

这个其实是由于loop的实现机制导致的。swiper需要添加若干个假元素来实现无限滚动。这也很好理解,毕竟你滚动到最后一个元素时继续划动会看到最前面的一个元素,而这里你看到的那个第一个元素,就是swiper复制出来的假元素,如下图:

这里的A’ 和 B’ 就是swiper复制出来的假元素,而且这两个为了保持和真元素的一致,其active状态也是一致的。这也就导致划动之后,真元素和假元素同时开始了播放的问题。

当然,光这个问题其实不难解决,可以在播放之前判断一下当前视频元素是否在可视区域内,如果在可视区域内才开始播放。

真的解决问题了吗

通过判断可视区域的方法,我们表面上已经解决了播放的问题。不过你可能认为此处还有其他的解决方法,例如通过渲染函数中的参数 isDuplicate 来判断当前元素是否是假元素,如果是假元素则不开始播放。然而尝试过后你就会发现这个方法是行不通的,原因在于swiper的loopfix时机问题。

什么是loopfix呢,其实也很简单,在上面那个图里,如果我们划动到最后一块,也就是 A’ 的部分的时候,是不能停留在这的,停在这里就不能继续划动了。所以需要将真实的A的位置重置到可视区域内,这样一来就可以继续划动了,而这个过程就叫做loopfix,如下图所示:

一般来说,loopfix的时机应该是在假元素的位置已经稳定下来、也就是划动动作已经完成且过渡动画播放完成之后,马上进行。但是swiper的loopfix并不是发生在这个时机的,swiper实际会在下一次划动动作开始的瞬间进行loopfix。这也就是直接通过 isDuplicate 来判断假元素决定是否播放行不通的原因:在划动到 A’ 之后,并不会立即进行loopfix操作,所以最后的状态就会停留在上图左边的状态。此时可视区域内看到的其实是一个假元素 A’,又因为假元素不会播放,而在上方的真元素 A 反而开始播放,就会变成画面静止不动而声音在播放的奇特状态。

实际上笔者上面所写到的解决方法之所以加了「表面上」的原因也在这里:虽然在划动到 A’ 之后,由于判断的不是 isDuplicate 而是video元素是否在可视区域之内,当前可视区域内的视频正确播放了。但是一旦开始划动动作,就算中途放手不划动到下一个,swiper也会开始loopfix,随后 A 被重置到可视区域,然而此时正在播放的其实是 A’ ,又变成了画面静止不动而声音在播放的奇特状态。而且在loopfix进行的一瞬间还会出现闪烁的情况。

解决方案

既然我们知道了原因,那要解决就很简单了,只需要在正确的时机手动执行一下loopfix就行了。虽然swiper官方并没有提供这样的方法,不过通过查阅源码倒也找到了这个方法,给swiper加上这两行代码就能在正确的时机触发loopfix了:

<Swiper
    onTransitionEnd={(swiper: unknown) => {
        const swiperInternal = swiper as { loopFix: () => void }
        swiperInternal.loopFix()
    }}
/>