我正在参加「掘金·启航计划」
前言
最近在开发公司的门户网站,对于首页的轮播图自动播放有了一些更深层次的研究。在调查了多个竞品方案后,决定使用 swiper/react
自动轮播的方式。(文档见官网 swiper8)
但是有一个问题,swiper
库自带的滚动进入条是显示当前幻灯片在全部轮播图片中的位置(比如一共10张,当前在第4张,就会显示 4/10),并没有每一个幻灯片持续时间的进度提示。
但是,我还是通过自定义动画的方式实现了。今天就分享出来供大家参考。效果如下:
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 变动,动画就会从头开始;
- 监听浏览器失焦动作,关闭动画。