React自定义滚动条移动端无法拖动Thumb问题求助
React自定义滚动条在iPadOS Chrome中滑块拖动失效问题
我实现了一个React自定义滚动条,桌面端运行完全正常,但在搭载iPadOS 16.3的第9代iPad上使用Chrome浏览器时,拖动滚动条滑块(thumb)时,只有容器内容会滚动,滑块本身无法被拖动。
以下是完整的实现代码:
import React, { useState, useEffect, useRef, useCallback } from 'react'; const Scrollbar = ({ children, className, ...props }: React.ComponentPropsWithoutRef<'div'>) => { const contentRef = useRef<HTMLDivElement>(null); const scrollTrackRef = useRef<HTMLDivElement>(null); const scrollThumbRef = useRef<HTMLDivElement>(null); const observer = useRef<ResizeObserver | null>(null); const [thumbHeight, setThumbHeight] = useState(20); const [scrollStartPosition, setScrollStartPosition] = useState<number | null>( null ); const [initialScrollTop, setInitialScrollTop] = useState<number>(0); const [isDragging, setIsDragging] = useState(false); function handleResize(ref: HTMLDivElement, trackSize: number) { const { clientHeight, scrollHeight } = ref; setThumbHeight(Math.max((clientHeight / scrollHeight) * trackSize, 20)); } function handleScrollButton(direction: 'up' | 'down') { const { current } = contentRef; if (current) { const scrollAmount = direction === 'down' ? 200 : -200; current.scrollBy({ top: scrollAmount, behavior: 'smooth' }); } } const handleTrackClick = useCallback( (e) => { e.preventDefault(); e.stopPropagation(); const { current: trackCurrent } = scrollTrackRef; const { current: contentCurrent } = contentRef; if (trackCurrent && contentCurrent) { const { clientY } = e; const target = e.target as HTMLDivElement; const rect = target.getBoundingClientRect(); const trackTop = rect.top; const thumbOffset = -(thumbHeight / 2); const clickRatio = (clientY - trackTop + thumbOffset) / trackCurrent.clientHeight; const scrollAmount = Math.floor( clickRatio * contentCurrent.scrollHeight ); contentCurrent.scrollTo({ top: scrollAmount, behavior: 'smooth', }); } }, [thumbHeight] ); const handleThumbPosition = useCallback(() => { if ( !contentRef.current || !scrollTrackRef.current || !scrollThumbRef.current ) { return; } const { scrollTop: contentTop, scrollHeight: contentHeight } = contentRef.current; const { clientHeight: trackHeight } = scrollTrackRef.current; let newTop = (+contentTop / +contentHeight) * trackHeight; newTop = Math.min(newTop, trackHeight - thumbHeight); const thumb = scrollThumbRef.current; thumb.style.top = `${newTop}px`; }, []); const handleThumbMousedown = useCallback((e) => { e.preventDefault(); e.stopPropagation(); setScrollStartPosition(e.clientY); if (contentRef.current) setInitialScrollTop(contentRef.current.scrollTop); setIsDragging(true); }, []); const handleThumbMouseup = useCallback( (e) => { e.preventDefault(); e.stopPropagation(); if (isDragging) { setIsDragging(false); } }, [isDragging] ); const handleThumbMousemove = useCallback( (e) => { e.preventDefault(); e.stopPropagation(); if (isDragging) { const { scrollHeight: contentScrollHeight, offsetHeight: contentOffsetHeight, } = contentRef.current; const deltaY = (e.clientY - scrollStartPosition) * (contentOffsetHeight / thumbHeight); const newScrollTop = Math.min( initialScrollTop + deltaY, contentScrollHeight - contentOffsetHeight ); contentRef.current.scrollTop = newScrollTop; } }, [isDragging, scrollStartPosition, thumbHeight] ); // 内容和滚动轨道存在时,用ResizeObserver调整滑块高度并监听滚动事件移动滑块 useEffect(() => { if (contentRef.current && scrollTrackRef.current) { const ref = contentRef.current; const { clientHeight: trackSize } = scrollTrackRef.current; observer.current = new ResizeObserver(() => { handleResize(ref, trackSize); }); observer.current.observe(ref); ref.addEventListener('scroll', handleThumbPosition); return () => { observer.current?.unobserve(ref); ref.removeEventListener('scroll', handleThumbPosition); }; } }, []); // 监听鼠标事件以处理滑块拖动滚动 useEffect(() => { document.addEventListener('mousemove', handleThumbMousemove); document.addEventListener('mouseup', handleThumbMouseup); document.addEventListener('mouseleave', handleThumbMouseup); return () => { document.removeEventListener('mousemove', handleThumbMousemove); document.removeEventListener('mouseup', handleThumbMouseup); document.removeEventListener('mouseleave', handleThumbMouseup); }; }, [handleThumbMousemove, handleThumbMouseup]); return ( <div className="custom-scrollbars__container"> <div className="custom-scrollbars__content" ref={contentRef} {...props}> {children} </div> <div className="custom-scrollbars__scrollbar"> <button className="custom-scrollbars__button" onClick={() => handleScrollButton('up')} > ⇑ </button> <div className="custom-scrollbars__track-and-thumb"> <div className="custom-scrollbars__track" ref={scrollTrackRef} onClick={handleTrackClick} style={{ cursor: isDragging && 'grabbing' }} ></div> <div className="custom-scrollbars__thumb" ref={scrollThumbRef} onMouseDown={handleThumbMousedown} style={{ height: `${thumbHeight}px`, cursor: isDragging ? 'grabbing' : 'grab', }} ></div> </div> <button className="custom-scrollbars__button" onClick={() => handleScrollButton('down')} > ⇓ </button> </div> </div> ); }; export default Scrollbar;
我尝试过在监听鼠标事件的useEffect中添加document.addEventListener('touchmove', handleThumbMousemove);以及touchstart事件监听,但问题依然存在。
内容的提问来源于stack exchange,提问作者xclloe00
相关产品推荐
相关产品推荐

