前言
继续撸,这次会完成整个组件
正文
封装之后组件的使用代码
实现阶段,大致就三步
- 新增指示点
- 添加新的属性,自动播放、自动播放间隔、无限轮播、是否展示指示点
- 细节优化
新增指示点
那么我们就新增一个文件swiper-indicator.tsx和相应的样式文件
indicator内容不是很多,主要是根据swiper子元素的数量生成对应数量的指示点,同时确认正在活跃的index,支持样式自定义
swiper-indicator.tsx
怎么用呢,在swiper.tsx里,这里主要currentIndex的传值,我们要实现的是完整的切换到新图片,才更新我们的指示点,没到新图片之前仍然在原来的位置,slideRatioRef是我们定义的滑动比例,slideRatioRef是正数,代表我们在往后滑,所以正数的时候要向下取整,否则反之
至此,指示点功能完成。
添加新的属性,无限轮播、自动播放、自动播放间隔、是否展示指示点
更新一下我们是swiper类型,autoplay、autoplayDuration、loop、showIndicator、indicatorClassName
swiper.tsx
这里我们修改一下代码,方便我们后续调用
handleTouchend修改之前
ini
const handleTouchend = (e: TouchEvent) => {
const index = Math.round(slideRatioRef.current);
targetIndex = boundIndex(index + currentIndex);
setCurrentIndex(targetIndex);
};
handleTouchend修改之后,以便于我们在自动播放好调用
ini
const swiperTo = useCallback(
(finalIndex: number) => {
const targetIndex = boundIndex(finalIndex);
setCurrentIndex(targetIndex);
},
[boundIndex]
);
const handleTouchend = (e: TouchEvent) => {
const index = Math.round(slideRatioRef.current);
swiperTo(index + currentIndex);
};
autoplay、autoplayDuration
回到新属性,我们先看autoplay,自动播放,这里我们需要使用定时器
ini
const swiperTo = useCallback(
(finalIndex: number) => {
const targetIndex = boundIndex(finalIndex);
setCurrentIndex(targetIndex);
},
[boundIndex]
);
const swiperNext = useCallback(() => {
swiperTo(currentIndex + 1);
}, [currentIndex, swiperTo]);
const intervalRef = useRef(0); //接收定时器
const isAutoPlayRef = useRef(false); //是否在自动播放
useEffect(() => {
if (!props.autoplay || dragging) return;
intervalRef.current = window.setInterval(() => {
isAutoPlayRef.current = true;
swiperNext();
return () => {
clearInterval(intervalRef.current);//清除定时器
};
}, props.autoplayDuration);
}, [dragging, props.autoplay, props.autoplayDuration, swiperNext]);
同时当我们touch了轮播图,自动播放就没了,然后功能就完成了
ini
const handleTouchStart = (e: React.TouchEvent<HTMLDivElement>) => {
isAutoPlayRef.current = false;
clearInterval(intervalRef.current);
....
};
loop
loop是无限播放,效果就是最后一张的下一张是回到第一张,我看网上方法很多,我们这里主要是从样式改变,就是改变translate3d的X轴,这个过程有点复杂其实
- 实现无限轮播,改变样式position,实现1->4,4->1
- position改变前 position的X :0 -100 -200 -300
- 1234 => 2341 =>3412 => 4123
- 比如1234,currentIndex为0, 第一张是position是0,那么他的前一张是第4张,那么第四张是-100,那第二张就是100,0 100 X -100,X第三张其实就是看不见,不在前不在后
- position改变后 position的X 0 100 -200 -100,无论是1234还是2341都是这些值
大致结果是这样,那么我们要怎么实现呢
- 4=> 300, (300 + 200) % 400 = 100 - 200 = -100
- 1=> 0,(0 + 200) % 400 = 200 - 200 = 0
- 2=> 100,(100 + 200) % 400 = 100 - 200 = 100
- 3=> 200,(200 + 200) % 400 = 0 - 200 = -200
ini
const handleFinal = (value: number, totalWidth: number) => {
const r = value % totalWidth;
return r < 0 ? r + division : r;
};
const totalWidth = count * 100; //总宽度
const flagWidth = totalWidth / 2; // 无限轮播时,当前图的前后平均分配轮播图宽度
finalPosition = handleFinal(finalPosition + flagWidth, totalWidth) - flagWidth;
return finalPosition;
const swiperTo = useCallback(
(finalIndex: number) => {
const targetIndex = props.loop
? handleFinal(finalIndex, React.Children.count(props.children))
: boundIndex(finalIndex); //超过就下一张,
setCurrentIndex(targetIndex);
},
[boundIndex, props.children, props.loop]
);
const handleTouchmove = (e: TouchEvent) => {
....
// 当不支持循环时,第一张和最后一张不能在往前后滑动
if (!props.loop) {
slideIndex = boundIndex(slideIndex);
}
setCurrentIndex(slideIndex);
};
需要无限播放的时候,就不用限制index,同时我们知道第三张永远看不到,所以不需要添加动画,最终功能完成
kotlin
const getTransition = (position: number) => {
if (dragging) {
return '';
}
// 清除自动播放的时候的闪动
if (autoplayRef.current) {
if (position === -100 || position === 0) {
return 'transform 0.3s ease-out';
} else {
return '';
}
}
// 排除4张里位移看不见的, 4 1 2,第三张就看不见
if (position < -100) {
return '';
}
return 'transform 0.3s ease-out';
};
细节优化
Swiper的子组件是Swiper.item,那么我们怎么验证它是SwiperItem
javascript
// 对传入进来的子元素进行判断,是否是Swiper.Item
const { validChildren, count } = useMemo(() => {
let count = 0;
const validChildren = React.Children.map(props.children, (child) => {
// 验证对象是否是一个React元素
if (!React.isValidElement(child)) return null;
// 验证是否是一个SwiperItem类型
if (child.type !== SwiperItem) {
console.warn('Swiper children must be Swiper.Item components');
}
count++;
return child;
});
return { validChildren, count };
}, [props.children]);
if (count === 0 || !validChildren) {
console.warn('Swiper at least one child element is required');
return null;
}
然后我们可以将全局的prop.children替换成validChildren,React.Children.count(props.children)也可以换成count
最终
接下来会写loading和toast,然后再看看有啥,到时候把这些组件都发到npm上,或者搞个UI组件文档也不错