基于 swiper 实现匀速自动轮播(踩坑文)

您所在的位置:网站首页 solidworks轮子滚动前进 基于 swiper 实现匀速自动轮播(踩坑文)

基于 swiper 实现匀速自动轮播(踩坑文)

2023-06-14 01:41| 来源: 网络整理| 查看: 265

一、前情概要

业务逻辑早早写完,动画效果却迟迟不能交付,这其中的缘由听者伤心、闻者流泪,只留下开发与设计慢慢沟通、慢慢核对。 😭 这一切都是因 Swiper 而起......

P.S.嫌麻烦可直接跳至最后,结尾处罗列了本文提到的问题及处理方法,但最好别吧 :)

Swiper:一款免费以及轻量级的移动设备触控滑块的 js 框架(摘自官方中文网站)

基本概念 Swiper 组件: 业务代码中引入的 "swiper/react" Swiper:代码中 Swiper 是划动部分的容器,即swiper-container,代码中的 Slide:swiper 容器中的划动单元(即单个推荐商品),代码中的 业务背景

商品卡片自动轮播效果如下

finish.gif

业务需求

组件列表页的单张卡片中推荐商品部分,可看作一个swiper-container,swiper 功能的需求如下:

slide 循环 (对应 loop 参数) 支持触摸滑动 划动后 slide 不能停留在过渡位置 只有一个商品时,swiper 不划动 (noSwiping 参数) 匀速自动轮播(重点,要考) 首次加载页面时,第一张卡片自动轮播 屏幕滚动后,处在中间的卡片自动轮播 当改变轮播卡片时,上一个轮播的卡片应立即停止轮播 二、主体内容

知晓业务需求后,评估一番选择使用 swiper 组件来完成工作(swiper 组件具备丰富的 Api、较为完善的官方中文说明等优势)。 写完绝大部分交互代码后,开始折腾自动轮播功能,大体的实现思路如下:

父组件设置页面滚动监听,将变量activeIndex设置成当前处在屏幕中间卡片的index 通过 props 给到轮播子组件activeIndex,即每个轮播子组件都能拿到同样的activeIndex 每个子组件判断activeIndex === index?,true 代表当前应自动轮播 开启自动轮播开关时,更改当前轮播组件的相关配置参数;而上一个自动轮播组件则关闭自动轮播,调整相关配置参数

前面非常简单,精准获取并给到子组件当前屏幕中间卡片的activeIndex,并如下配置参数,预想能够动态控制自动播放。

/** * 动态改变配置的逻辑 * 在实际代码中肯定不会如此不优雅 * 此处这样写是为关注问题本身 */ autoplay: (activeIndex === index) && { delay: 0, stopOnLastSlide: false, disableOnInteraction: false, }, 复制代码

但是当activeIndex === index结果改变的时候,发现并不会引起autoplay参数的变化,即 swiper 组件并不能动态更改配置。 这也抛出了第一个问题,如何动态控制自动轮播的开/关?

问题 1:如何动态控制自动轮播

查阅官网发现:官方提供了autoplay.start()、autoplay.stop()两个方法。 既然不能偷懒动态改变 swiper 的参数,那就老老实实按照官方给的方法来控制 swiper 的自动轮播开/关吧。

代码如下:

useEffect(() => { const mySwiper = mySwiperRef.current; if (activeIndex === index) { mySwiper.autoplay.start(); } else { mySwiper.autoplay.stop(); } }, [activeIndex]); 复制代码

一番操作成功实现了动态开启/关闭自动轮播的功能,再把activeIndex的初始值设为 0,即可满足首次加载页面时第一个卡片自动轮播的需求。 但......下一个问题又出现了。

问题 2:无法立即停止

功能强大的 swiper 组件总会在“奇怪”的地方给你出道题 🤷‍♂️

虽然上面成功搞定了“动态控制自动轮播”的问题,但是在调用autoplay.stop()后,slide 并不会立即停止,而是过渡完成后再停止。 原因猜想:slide 的滚动是从一个坐标(即 mySwiper.translate)过渡到下一个坐标,autoplay.stop()在 slide 抵达坐标后才会生效。 继续查阅资料,可以通过 swiper 提供的自定义位移方法来实现立即停止。

「 你看,我 swiper 功能就是强大 」 「 对对对... 你说的都对,你知道为什么吗(捏紧拳头 👊)」

将问题 1 中的代码优化一(亿)点点,得到 👇

useEffect(() => { const mySwiper = mySwiperRef.current; if (activeIndex === index) { if (!firstFlag) { mySwiper.slideTo(mySwiper.activeIndex, slideSpeed); autoplayFlag.current = setTimeout(() => { mySwiper.autoplay.start(); }, lastNeedSwiperSpeed); } else { mySwiper.autoplay.start(); setFirstFlag(false); } } else { const newSpeed = (Math.abs( Math.abs(mySwiper.translate) - Math.abs(mySwiper.getTranslate()) ) / 168) * 5000; // 168是每个slider的宽度,5000是匀速滚动时间 if (Math.floor(newSpeed * 100) / 100) { setSlideSpeed(Math.floor(newSpeed * 100) / 100); } mySwiper.setTranslate(mySwiper.getTranslate()); mySwiper.autoplay.stop(); } }, [activeIndex]); 复制代码

* getTranslate()获取 slide 实时位移、translate是 slide 过渡完成的位移、setTranslate()设置 slide 的 translate

整个动态自动播放的逻辑条件是分为:

activeIndex === index 即当前 自动轮播 开启 else 即当前 自动轮播 关闭

先谈else部分的改动:

通过translate和getTranslate()先计算出 slider 从当前暂停轮播的位置到过渡完成后的位置需要的时间(按照原本设定的速度) 将newSpeed保留两位小数,如果newSpeed不存在(测试中发现会出现 0 的情况,原因暂不知)则不改变slideSpeed 通过setTranslate()方法,将 slide 过渡完成的位移设置为当前实时位移,从而实现在中间停顿,然后设置自动轮播autoplay.stop()

再来是activeIndex === index的部分:

如果是从中间停顿后再次开启自动轮播,需要先通过slideTo将 slide 移动到暂停前“原本的位移”,当 slide 经过slideSpeed时间抵达“原本的位移”后,再开启自动轮播(否则slideTo和autoplay.start()会有冲突,运动速度相当诡异) 如果当前swiper首次自动轮播,是不需要划动

这样就实现了自动轮播立即停止,并且能够重新从停顿位置开启自动轮播功能,这样看上去就大功告成了,但是在测试中发现,还差一小步。。。

问题 3:用户缓慢划动会造成轮播停止

实现卡片自动轮播效果的基础上仍然需要支持用户手动划动,测试中发现当用户缓慢划动卡片后,卡片会出现无法自动轮播的问题。 产生这个问题的原因暂不明,但可以通过官方提供的 swiper事件解决这个问题,代码如下:

on:{ touchStart:function(){ this.autoplay.stop(); }, touchEnd:function(){ setTimeout(()=>{ this.autoplay.start(); },500) } } 复制代码 结尾

解决这三个问题就基本实现了 swiper 自动轮播效果,总结下三个问题:

如何动态控制自动轮播? 答:获取到 swiper 实例对象,调用其mySwiper.autoplay.start(),mySwiper.autoplay.stop()方法来动态控制自动轮播。 无法立即停止? 答: 立即停止:利用mySwiper.getTranslate()获取到实时位移;再利用mySwiper.setTranslate()将过渡完成后的位移设置为当前实时位移。 重新开启:利用mySwiper.slideTo()将 slide 划动到立即停止前本应过渡到的位置;等 slide 划动完成后,再调用mySwiper.autoplay.start()。 用户缓慢划动会造成轮播停止? 答:用户开始划动时调用mySwiper.autoplay.stop()暂停轮播,而当用户停止划动后调用mySwiper.autoplay.start()重新开启轮播。

P.S. 如果轮播组件有跳转逻辑,跳转后回到轮播页面时,可能会导致自动轮播功能失效(mySwiper.autoplay.running为false的情况),查阅了很多帖子博客发现有一个神奇的解决方法,只需在需要开启自动轮播的位置加上如下代码:

if (!mySwiper.autoplay.running) { mySwiper.animating = false; mySwiper.autoplay.timeout = undefined; mySwiper.autoplay.running = false; mySwiper.autoplay.start(); } 复制代码

经过了大量调试最后得出的解决方案,至于为什么这样写,我也不知道 🤷‍♂️

如果文中有任何错误或者有歧义的地方,还请多指教



【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3