仿小红书轮播图实现(pc版)
最近做pc的轮播需求时,遇到了一个问题,指示点太多了,可能会超出每个轮播的宽度,于是便参考小红书轮播图的指示点效果,实现了pc版的轮播图,最多显示五个指示点,超出则移动展示。
首先实现指示点的样式,有三种情况:
- 选中的点索引小于3时,是不需要移动的,第五个指示点开始,都是小号的指示点,条件如下:
selectedIndex < 3 && index >= 4
- 选中的点索引大于等于3并且小于倒数第三个点的索引时,是需要进行移动的,这个时候,只有中间的三个点是大号的指示点,其它的都是小号的指示点,条件如下:
selectedIndex >= 3 &&
selectedIndex < dotTotal - 3 &&
Math.abs(index - selectedIndex) >= 2
- 选中的点大于等于倒数第三个点的索引时,是不需要移动的,小于等于倒数第五个点的索引的指示点都是小号的,条件如下:
selectedIndex >= dotTotal - 3 && index <= dotTotal - 5
然后来处理位移:
只有指示点大于5时,才存在需要移动的情况。这种时候,又需要分为两种情况:
-
当选中的指示点的索引大于当前指示点的索引时,需要往左移,左移的情况又需要分为两种:
- 选中的指示点的索引小于等于2时,是不需要移动的
if (index <= 2) { offset = 0; }
- 选中的指示点的索引大于2时,才需要左移,需要计算将其移动到最中间位置所需要的位移
if (index > 2) { offset = (index - middleIndex) * 16 }
- 选中的指示点的索引小于等于2时,是不需要移动的
-
当选中的指示点的索引小于当前指示点的索引时,需要往右移动,右移的情况也需要分为两种:
-
选中的指示点的索引大于等于倒数第三个点的索引时,不需要移动
if (index >= dotTotal - 3) { offset = 0 }
-
选中的指示点的索引小于倒数第三个点的索引时,才需要往右移动,需要计算将其移动到最中间位置所需要的位移
if (index < dotTotal - 3) { offset = (index - middleIndex) * 24; }
-
上述中我们都需要计算将选中的指示点移动到最中间所需要的位移,所以每次移动,我们都需要记录最中间指示点的索引:
// 索引小于2时,2始终是最中间的指示点
if (index < 2) {
setMiddleIndex(2);
} else if (index >= 2 && index <= dotTotal - 3) {
// 选中的点需要移动到最中间,即它的索引为最中间的索引
setMiddleIndex(index);
} else {
// 索引大于倒数第三个点时,倒数第三个点始终是最中间的指示点
setMiddleIndex(dotTotal - 3);
}
完整代码如下:
App.jsx
import { useEffect, useRef, useState } from "react";
import { Carousel } from "antd";
import "./App.css";
export default function CustomCarousel() {
const [dataList, setDataList] = useState([]);
// 指示点的个数
const [dotTotal, setDotTotal] = useState(0);
// 当前选中的指示点的索引
const [selectedIndex, setSelectedIndex] = useState(0);
// 位于最中间的指示点的索引
const [middleIndex, setMiddleIndex] = useState(0);
const dotRef = useRef(null);
const carouselRef = useRef(null);
useEffect(() => {
const dataList = [];
for (let i = 0; i < 10; i++) {
dataList.push(i);
}
setDataList(dataList);
setDotTotal(dataList.length);
if (dataList.length > 5) {
setMiddleIndex(2);
}
}, []);
const handleSlide = (index) => {
// 当前选中的指示点和之前选中的指示点相同,则不做任何操作,返回
if (index === selectedIndex) return;
// 指示点偏移位置
let offset = 24;
// 只有大于5的时候,指示点才需要移动
if (dotTotal > 5) {
if (index > selectedIndex) {
if (index <= 2) {
offset = 0;
} else {
offset = (index - middleIndex) * 24;
}
} else {
if (index < dotTotal - 3) {
offset = (index - middleIndex) * 24;
} else {
offset = 0;
}
}
if (index < 2) {
setMiddleIndex(2);
} else if (index >= 2 && index <= dotTotal - 3) {
setMiddleIndex(index);
} else {
setMiddleIndex(dotTotal - 3);
}
// 设置平缓滑动动画
const duration = 1000;
const frames = (duration / 1000) * 24;
let frame = 0;
const animate = () => {
if (!dotRef.current) return;
dotRef.current.scrollLeft += offset / frames;
frame += 1;
if (frame < frames) {
window.requestAnimationFrame(animate);
}
};
window.requestAnimationFrame(animate);
}
carouselRef.current?.goTo(index);
setSelectedIndex(index);
};
return (
<div className="listBox">
<Carousel ref={carouselRef} dots={false}>
{dataList.map((item) => (
<div className="listItem">{item}</div>
))}
</Carousel>
<div className="dotWrapper">
<div className="dotList" ref={dotRef}>
{new Array(dotTotal).fill(" ").map((_, index) => {
return (
<div
className={`dot
${index === selectedIndex && "selectedDot"}
${
dotTotal > 5 &&
((selectedIndex < 3 && index >= 4) ||
(selectedIndex >= 3 &&
selectedIndex < dotTotal - 3 &&
Math.abs(index - selectedIndex) >= 2) ||
(selectedIndex >= dotTotal - 3 &&
index <= dotTotal - 5)) &&
"smallDot"
}`}
key={index}
onClick={() => {
handleSlide(index);
}}
>
{index}
</div>
);
})}
</div>
</div>
</div>
);
}
App.css
@import '~antd/dist/reset.css';
.listBox{
width: 300px;
margin: 100px auto;
}
.listItem{
width: 100%;
height: 100px;
border: 1px solid #ccc;
}
.dotWrapper{
width: 100%;
display: flex;
justify-content: center;
}
.dotList{
width: 108px;
overflow: hidden;
white-space: nowrap;
}
.dot {
display: inline-block;
width: 12px;
height: 12px;
border-radius: 12px;
background-color: #767684;
margin-right: 12px;
font-size: 12px;
text-align: center;
line-height: 12px;
transition: all .2s ease;
&:last-child {
margin-right: 0;
}
}
.selectedDot {
background: #586ee0;
}
.smallDot {
transform: scale(0.8);
}