React+ts手写轮播图组件(2/2)

前言

继续撸,这次会完成整个组件

正文

封装之后组件的使用代码

实现阶段,大致就三步

  1. 新增指示点
  2. 添加新的属性,自动播放、自动播放间隔、无限轮播、是否展示指示点
  3. 细节优化

新增指示点

那么我们就新增一个文件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组件文档也不错

相关推荐
陈随易1 小时前
农村程序员-关于小孩教育的思考
前端·后端·程序员
云深时现月1 小时前
jenkins使用cli发行uni-app到h5
前端·uni-app·jenkins
昨天今天明天好多天1 小时前
【Node.js]
前端·node.js
2401_857610031 小时前
深入探索React合成事件(SyntheticEvent):跨浏览器的事件处理利器
前端·javascript·react.js
雾散声声慢2 小时前
前端开发中怎么把链接转为二维码并展示?
前端
熊的猫2 小时前
DOM 规范 — MutationObserver 接口
前端·javascript·chrome·webpack·前端框架·node.js·ecmascript
天农学子2 小时前
Easyui ComboBox 数据加载完成之后过滤数据
前端·javascript·easyui
mez_Blog2 小时前
Vue之插槽(slot)
前端·javascript·vue.js·前端框架·插槽
爱睡D小猪2 小时前
vue文本高亮处理
前端·javascript·vue.js
开心工作室_kaic2 小时前
ssm102“魅力”繁峙宣传网站的设计与实现+vue(论文+源码)_kaic
前端·javascript·vue.js