实现一个Swiper轮播图的定时器进度条

1,844 阅读4分钟

我正在参加「掘金·启航计划」

前言

最近在开发公司的门户网站,对于首页的轮播图自动播放有了一些更深层次的研究。在调查了多个竞品方案后,决定使用 swiper/react 自动轮播的方式。(文档见官网 swiper8

但是有一个问题,swiper 库自带的滚动进入条是显示当前幻灯片在全部轮播图片中的位置(比如一共10张,当前在第4张,就会显示 4/10),并没有每一个幻灯片持续时间的进度提示。

但是,我还是通过自定义动画的方式实现了。今天就分享出来供大家参考。效果如下:

Kapture 2022-10-23 at 22.22.05.gif

Swiper 实现自动播放

使用 swiper 库, 首先要引入你所需要的相关库文件和css样式:

import { Autoplay, Navigation, Pagination } from 'swiper'
import { Swiper, SwiperSlide } from 'swiper/react'
import 'swiper/css/pagination'
import 'swiper/css/navigation';
import 'swiper/css'

然后在渲染函数中使用:

// 这里只展示跟本文自动滚动进度有关的参数:
<Swiper
    speed={2000}
    loop={true}
    onSlideChange={onSlideChange}
    autoplay={{
      disableOnInteraction: true,
      delay: 10000
    }}
    pagination={false}
    modules={[Autoplay]}
    effect="slide"
>
    // 自己定义的一组 div,SwiperItems 数组是我自定义的,可选
    {SwiperItems.map((item, index) => {
      return (
        <SwiperSlide key={index}>
           <div className="banner-item" style={{
                width: '1200px',
                margin: '0 auto',
                backgroundImage: item.backgroundImage,
                backgroundSize: '500px 486px'    
           }}>
              {item.content}
           </div>
        </SwiperSlide>
      )
    })}
  </Swiper>

主要参数说明:

  • speed:自动切换下一页的持续时间为 2s
  • loop:循环轮播
  • onSlideChange:每当切换了轮播图后的触发事件
  • modules:需要按需加载的内置模块,这里引入了 Autoplay
  • effect:切换效果,这里使用了滑动效果
  • autoplay:自动播放的参数。这里设置滚动中防止误操作 和 滚动间隔 10s
  • pagination:我禁掉了他自带的分页器,打算自定义

自定义分页器

上边也说了,swiper 没有自带的 API 来提供单个幻灯片的播放速度,而我们又想在分页器上显示当前进度,那就只能自己定义分页器了。

首先在外层报一个 div,设置相对定位,swiper 组件同级设置一个绝对定位的分页器组件:

<div style={{ position: 'relative', ... }}>
   <Pagination />
   <Swiper
   ... />
   ...
</div>

实现一下 Pagination 组件:

// 为了方便展示,全使用 inline-style
const bulletActiveClass = 'active'

const Pagination = () => <div style={{ position: 'absolute', bottom: '12px', left: 0, width: 100%, text-align: center }}>
    {SwiperItems.map((_item, index: number) => {
      {/* 给当前幻灯片加入特殊的 class */}
      return <span key={index} className={'custom-swiper ' + (current === index ? bulletActiveClass : ' ')}></span>
    })}
</div>;

然后监听幻灯片滑动事件:

const [current, setCurrent] = useState<number>(0);

const onSlideChange = (item: any) => {
    const index = item.realIndex || 0;

    // 设置轮播图导航动画样式
    setCurrent(index);
}

基本原理就是,通过 onSlideChange 拿到当前幻灯片的 index(item.realIndex属性),然后设置给 current,在幻灯片中,判断 current === index 索引时,就给一个激活的 class(这里我自定义了 bulletActiveClass)

接下来就是给分页器加一个 flex 布局,设置一下宽高,这里不再赘述。

进度动画

我们需要根据自己设置的时间来计算幻灯片的进度。上边我们设置了轮播的间隔为 10s,滚动时间为 2s,所以从开始切换到切换结束,一共是 12s:

:root {
  /* 轮播图定时器开关 */
  --bannerAnimationName: progress;
  --bannerAnimationDuration: 12s;
}

.custom-swiper {
  width: 80px;
  /* 进度标识的背景设置为暗色 */
  background-color: #2c4dc6;
}

.custom-swiper::before {
  width: 0;
  height: 0px;
  top: 0;
  left: 0;
  content: '';
  position: absolute;
  height: 4px;
}

.custom-swiper.current::before {
  /* 进度条动画覆盖在上边,较浅色的 */
  background-color: #f8f8f9;
  border-radius: 0px;
  /* 间隔10s + 过渡动画2s,过渡时间写在这里,每次active都清零 */
  -o-animation-name: var(--bannerAnimationName);
  -moz-animation-name: var(--bannerAnimationName);
  -webkit-animation-name: var(--bannerAnimationName);
  animation-name: var(--bannerAnimationName);
  -o-animation-duration: var(--bannerAnimationDuration);
  -moz-animation-duration: var(--bannerAnimationDuration);
  -webkit-animation-duration: var(--bannerAnimationDuration);
  animation-duration: var(--bannerAnimationDuration);
  /* animation-play-state: var(--bannerSwitch) */
}

设置动画progress:

@keyframes progress {
  from {
    width: 0;
  }
  to {
    width: 80px;
  }
}

其实原理很简单,每一个进度标识都是 80 像素宽。动画中,设置更亮的宽度从 0 到 80 即可。

一些bug

关于 swiper 这个库,它内置了一个有趣的特性,当视口切换走了后,切换定时器会停止,而不是暂停,所以 animation-play-state 属性就不起作用了。这里使用class添加动画时,视口切换时动画并不会停止,所以要特殊处理。还好 浏览器内置了一个监听事件:

  useEffect(() => {
    document.addEventListener("visibilitychange", onVisibilitychange);

    return () => {
      document.removeEventListener("visibilitychange", onVisibilitychange);
    }
  }, []);

事件实现:

// 将激活 class 换为一个 state
const [bulletActiveClass, setBulletActiveClass] = useState<string>('current');

const onVisibilitychange = () => {
  // 页面 不在可视区域,自动关闭轮播动画
  setBulletActiveClass(!document.hidden ? 'current' : ' ');
}

这样就大功告成啦!

总结

  • 通过 swiper 内部切换事件,获取当前内部展示的 index;
  • 通过该 index 给当前自定义分页器 dom 添加激活 class;
  • 给有激活 class 的 dom 加入一个伪元素,伪元素宽度从 0 变化到总长度;每当 class 变动,动画就会从头开始;
  • 监听浏览器失焦动作,关闭动画。

案例

GitHub