UNPKG

mini-app-swiper

Version:

在小程序环境内无限轮播效果,并且不满足外层容器宽度时不轮播, 自定义内部slide

201 lines (175 loc) 7.58 kB
import { useRef, useState, useEffect, memo, useCallback } from "react"; import Taro, { useDidShow, useReady, useLoad } from '@tarojs/taro'; import { View } from '@tarojs/components'; import IndexStyle from './index.module.less'; import { useMemo } from "react"; const MAX_RETRY = 5; // 最大重试次数 const RETRY_INTERVAL = 30; // 重试间隔(ms) const THISCOMNAME = "Hourse-", THISCOMNAMEVIEW = 'HourseSwiper-' const SlideItem = memo(({ dom, id, index, onGetDom }) => { const timerRef = useRef(null); useEffect(() => { let retryCount = 0; const getDom = () => { if (timerRef.current) { clearTimeout(timerRef.current); timerRef.current = null; } Taro.nextTick(() => { const query = Taro.createSelectorQuery(); query.select(`#${THISCOMNAME}${id}-${index}`).boundingClientRect(res => { if (res && res.height > 0 && res.width > 0) { onGetDom(res); return; } if (retryCount < MAX_RETRY) { retryCount++; console.log(`DOM获取失败,第${retryCount}次重试...`); timerRef.current = setTimeout(getDom, RETRY_INTERVAL); } else { console.error(`无法获取 #${id}${index} 的DOM元素`); } }).exec(); }); }; getDom(); return () => { if (timerRef.current) { clearTimeout(timerRef.current); } }; }, []); return ( <View id={`${THISCOMNAME}${id}-${index}`} className={IndexStyle.slideItem}> {dom} </View> ) }); const Carousel = ({ className, width, speed, data, children }) => { const uuid = useCallback(() => { var s = []; var hexDigits = "0123456789abcdef"; for (var i = 0; i < 36; i++) { s[i] = hexDigits.substr(Math.floor(Math.random() * 0x10), 1); } s[14] = "4"; // bits 12-15 of the time_hi_and_version field to 0010 s[19] = hexDigits.substr((s[19] & 0x3) | 0x8, 1); // bits 6-7 of the clock_seq_hi_and_reserved to 01 s[8] = s[13] = s[18] = s[23] = "-"; var uuid = s.join(""); return uuid; }, []) const thisComponentUUid = useRef(uuid()) const [propsOriginalSlides, setPropsOriginalSlides] = useState(data); const [extendedSlides, setextendedSlides] = useState([...propsOriginalSlides, ...propsOriginalSlides]); const [extendedSlidesDOM, setextendedSlidesDOM] = useState([...children, ...children]); // 使用 ref 存储状态 const stateRef = useRef({ slideWidths: [], totalWidth: 0, realContentWidth: 0, lastTimestamp: 0, duration: speed * data.length * 2, // 15秒完成一轮 animationFrameId: null }); const [translateX, setTranslateX] = useState(0); const [isMeasuring, setIsMeasuring] = useState(true); const ins = useMemo(() => { return Taro.getCurrentInstance()?.page }, []); const childDom = useRef([]); const [childDomProxy, setChildDomProxy] = useState([]) // 动态测量Slide宽度 useEffect(() => { if (childDom.current.length === extendedSlides.length) { (async () => { const measureSlides = (contentWidth) => { try { const measurements = childDom.current.map(item => item?.width) stateRef.current.slideWidths = measurements; stateRef.current.totalWidth = measurements.reduce((sum, width) => sum + width, 0); stateRef.current.realContentWidth = measurements .slice(0, measurements.length / 2) .reduce((sum, width) => sum + width, 0); if (stateRef.current.realContentWidth > contentWidth) { setIsMeasuring(false); } else { /** * * 去除多余的数据 * */ setextendedSlides(propsOriginalSlides) } } catch (error) { console.error('Error measuring slides:', error); } }; const getContentDomView = () => { return new Promise((resolve, rej) => { // 获取容器宽度 const query = Taro.createSelectorQuery(); query.select(`#${THISCOMNAMEVIEW}${thisComponentUUid.current}`).boundingClientRect(function (res) { resolve(res) }).exec(); }); } const data = await getContentDomView(); measureSlides(data?.width); })() } }, [childDomProxy]); // 自动滚动逻辑 - 使用requestAnimationFrame但只更新状态 useEffect(() => { if (isMeasuring || stateRef.current.slideWidths.length === 0) return; // 防抖 const animate = (timestamp) => { if (!stateRef.current.lastTimestamp) { stateRef.current.lastTimestamp = timestamp; } const elapsed = timestamp - stateRef.current.lastTimestamp; const progress = elapsed / stateRef.current.duration; // 计算当前位移 let newTranslateX = -progress * stateRef.current.totalWidth; // 边界检测 if (-newTranslateX >= stateRef.current.realContentWidth) { // 无缝跳转 newTranslateX = -stateRef.current.realContentWidth; // 重置时间基准 stateRef.current.lastTimestamp = timestamp; } // 更新状态 setTranslateX(newTranslateX); // 继续动画循环 stateRef.current.animationFrameId = requestAnimationFrame(animate); }; // 启动动画 stateRef.current.animationFrameId = requestAnimationFrame(animate); return () => { if (stateRef.current.animationFrameId) { cancelAnimationFrame(stateRef.current.animationFrameId); } }; }, [isMeasuring]); const onGetDom = (e) => { let copyArray = [...childDom.current]; copyArray.push(e) childDom.current = copyArray; if (childDom.current.length === extendedSlides.length) { setChildDomProxy(childDom.current) } } return ( <View id={THISCOMNAMEVIEW + thisComponentUUid.current} style={{ width }} className={[IndexStyle.carouselContainer, className].join(" ")}> <View className={IndexStyle.slidesWrapper} style={{ transform: `translateX(${translateX}px)`, transition: 'none', }} > {extendedSlides.map((slide, index) => ( <SlideItem onGetDom={onGetDom} dom={extendedSlidesDOM[index]} id={thisComponentUUid.current} key={`${thisComponentUUid.current}-${index}`} index={index} /> ))} </View> </View> ); }; export default Carousel