ReactJs 轮播 - 如何在滑动后强制项目捕捉?

ReactJs carousel - how to force items to snap after a swipe?

我正在构建一个 React 轮播并设置了基本应用程序。它是一个在移动设备上跟随您的手指移动的滑块,基本上是一个带有 overflow: scroll 的 flexbox。这些按钮通过将容器滚动(项目索引 * 项目宽度)次来工作,因此如果我滚动到项目 #3,它将从起始位置滚动项目宽度的 300%。

代码如下:

function Carousel(props) {
    const {children} = props
    const [index, setIndex] = useState(0)
    const [length, setLength] = useState(children.length)
    const [touchPosition, setTouchPosition] = useState(null)
    const [movement, setMovement] = useState(0) // saves the distance swiped


    useEffect(() => {
        setLength(children.length)
    }, [children])


    const next = () => {
        if (index < (length-1))  {
            setIndex(prevState => prevState + 1)
        }
        // sets the index to next if you are not on the last slide
    }

    const previous = () => {
        if (index > 0) {
            setIndex(prevState => prevState - 1)
        }
        // sets the index to be the previous if you are further than first slide
    }

    const handleTouchStart = (e) => {
        const touchDown = e.touches[0].clientX
        setTouchPosition(touchDown)
        // saves the touch position on touch start
    }

    const handleTouchEnd = (e) => {
        const touchDown = touchPosition

        if (touchDown === null) {
            return
        }

        const currentTouch = e.changedTouches[0].clientX
        const diff = touchDown - currentTouch

        console.log(diff) // for testing
        setMovement(diff)

        if (diff > 5) {
            setTimeout(() =>  next(), 100)
        } 
        if (diff < -5) {
            setTimeout(() =>  previous(), 100)
        }
        setTouchPosition(null)
        // compares the starting and ending touch positions, calculates the difference
    }

    

    // to test the distance swiped
    const transformMultiplier =() => {
        let mu = (movement / window.innerWidth) * 100
        console.log(`m = ${mu}`)
    }

    useEffect(()=>{
        transformMultiplier()
    }, [movement])

  

    return (
        <div className="carousel-container">
            <div className="carousel-wrapper" 
            onTouchStart={handleTouchStart}
            onTouchEnd={handleTouchEnd}

            > 
               { index > 0 && <button className="left-arrow" onClick={previous}>
                &lt;
                </button> }
                <div className="carousel-content-wrapper">
               { index < children.length -1 &&    <button className="right-arrow" onClick={next}>
                    &gt;
                </button>}
                {/* the sliding is done by the translateX below. it translates by (100% of the slides * the index of the slide) from the starting position.  */}
                    <div className="carousel-content"
                    style={{transform: `translateX(-${index * (window.innerWidth > 480 ? 100 : (100 - (movement / window.innerWidth) * 100))}%)`}}
                    >
                        { children}
                    </div>
                </div>
            </div>
        </div>
    )
}
.carousel-container {
    width: 100vw;
    display: flex;
    flex-direction: column;
}

.carousel-wrapper {
    display: flex;
    width: 100%;
    position: relative;
}

.carousel-content-wrapper {
    overflow: scroll;
    -webkit-overflow-scrolling: auto;
    width: 100%;
    height: 100%;
    -ms-overflow-style: none;  /* hide scrollbar in IE and Edge */
    scrollbar-width: none;  /* hide scrollbar in Firefox */
}

.carousel-content {
    display: flex;
    transition: all 250ms linear;
    -ms-overflow-style: none;  /* hide scrollbar in IE and Edge */
    scrollbar-width: none;  /* hide scrollbar in Firefox */
}

.carousel-content::-webkit-scrollbar, .carousel-content::-webkit-scrollbar {
    display: none;
}
.carousel-content-wrapper::-webkit-scrollbar, .carousel-content::-webkit-scrollbar {
    display: none;
}

.carousel-content > * {
    width: 100%;
    flex-shrink: 0;
    flex-grow: 1;
}

.left-arrow, .right-arrow {
    position: absolute;
    z-index: 1;
    top: 50%;
    transform: translateY(-50%);
    width: 48px;
    height: 48px;
    border-radius: 24px;
    background-color: white;
    border: 1px solid #ddd;
}
.left-arrow {
    left: 24px;
}

.right-arrow {
    right: 24px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>

我以前在手机上滑动时,滑块以滑动量+默认过渡量过渡。我试图通过尝试从过渡量中减去滑动所占屏幕的百分比来解决这个问题,因此如果我滑动 15% 的屏幕,幻灯片应该过渡剩余的 85%。

问题:

  1. 我尝试将滑动移动量设置为状态值“移动”,这似乎造成了麻烦,并且在每次滑动时都没有正确更新。所以有时滑块会使用上一次滑动的“移动”状态。
  2. 移动设备上的惯性滚动使滑动变得不可预测。我用网上找的一些方法都关不掉。

如果有人可以看一下代码,并指出我应该如何尝试解决这些问题,那就太好了。或者更好的是,如果有人知道在保持手指跟随滚动 的同时使这样的旋转木马捕捉到 next/previous 项目 的不那么卡顿的方法,那将是完美的。

我知道这不理想,但是 SO 片段编辑器给我一个无法解释的错误,所以如果有人想在实际中使用它,这里是 codesandbox:https://codesandbox.io/s/hardcore-goodall-usspz?file=/src/carousel.js&resolutionWidth=320&resolutionHeight=675

用于在父容器上滚动使用所有图像的下一个样式>>

 scroll-snap-type: x mandatory;
  -webkit-overflow-scrolling: touch;
  display: flex;
  overflow-x: scroll;

然后对于每张幻灯片,每个父容器的子容器都是这种样式

scroll-snap-align: start;