You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何避免React移动端Testimonial滑动至索引0时回滑崩溃

问题

我有一个供用户滚动浏览的testimonials组件,在移动端实现了滑动检测功能,可根据滑动方向切换数组索引,正向循环正常,但当索引为0时回滑会导致应用崩溃。如何确保索引回滑时循环到数组末尾?

问题代码片段

const [touchPosition, setTouchPosition] = useState(null)
 
const handleTouchStart = (e) => {
    const touchDown = e.touches[0].clientX
    setTouchPosition(touchDown)
}
  
const handleTouchMove = (e) => {
    const touchDown = touchPosition
  
    if(touchDown === null) {
        return
    }
  
    const currentTouch = e.touches[0].clientX
    const diff = touchDown - currentTouch
  
    if (diff > 5) {
        setIdx((prevIndex) =>
        prevIndex === testimonials.length - 1 ? 0 : prevIndex + 1
    )
    }
  
    if (diff < -5) {
        setIdx((prevIndex) =>
        prevIndex === testimonials.length - 1 ? 0 : prevIndex - 1
    )
    }
  
    setTouchPosition(null)
}

完整组件代码

const Testimonials = props =>{

const testimonials = [
    {
        name: 'Name1',
        position: 'Title1',
        photo: require('./img/chrisphoto.png'),
        text: 'Body1'
    },
    {
        name: 'Name2',
        position: 'Title2',
        photo: require('./img/jillphoto.png'),
        text: 'Body2'
    },
    {
        name: 'Name3',
        position: 'Title3',
        photo: require('./img/mikephoto.png'),
        text: 'Body3'
    },
];
  
const [idx, setIdx] = useState(0);

let name = testimonials[idx].name;
let position= testimonials[idx].position;
let photo= testimonials[idx].photo;
let text = testimonials[idx].text;
const [touchPosition, setTouchPosition] = useState(null)
 
const handleTouchStart = (e) => {
    const touchDown = e.touches[0].clientX
    setTouchPosition(touchDown)
}
  
const handleTouchMove = (e) => {
    const touchDown = touchPosition
  
    if(touchDown === null) {
        return
    }
  
    const currentTouch = e.touches[0].clientX
    const diff = touchDown - currentTouch
  
    if (diff > 5) {
        setIdx((prevIndex) =>
        prevIndex === testimonials.length - 1 ? 0 : prevIndex + 1
    )
    }
  
    if (diff < -5) {
        setIdx((prevIndex) =>
        prevIndex === testimonials.length - 1 ? 0 : prevIndex - 1
    )
    }
  
    setTouchPosition(null)
}
   
useEffect(() => {
    const interval = setInterval(
        () => setIdx(idx => (idx + 1) % testimonials.length), 120000,
    );
    return () => {
        clearInterval(interval);
    };
}, []);

return (
<div className="testimonials_main">
 <div className="home-testimonial-container" 
onTouchStart={handleTouchStart} onTouchMove={handleTouchMove}
 >
    <img className="quotes" src={quotes}/>
    <img className="quotes2" src={quotes2}/>
    <div className='testimonial-entry'>
    <button className="next-testimonial" onClick={() => {
            setIdx(idx => (idx + 1) % testimonials.length);
          }}style={{ 
            
            backgroundImage: `url(${nextarrow})`, 
            backgroundRepeat: 'no-repeat',
            backgroundPosition: 'center',
            backgroundSize: '10px'}}></button>
             <button className="last-testimonial" onClick={() => {
            setIdx(idx => (idx - 1) % testimonials.length);
          }}style={{ 
            backgroundImage: `url(${lastarrow})`, 
            backgroundRepeat: 'no-repeat',
            backgroundPosition: 'center',
            backgroundSize: '10px'}}></button>
    
    <img className='testimonial-photo'
     src={photo} 
     ></img>
    <div className='testimonial-text'>
        <h3 className='testimonial-name2' >{name}</h3></div>
        <div className='testimonial-text2'><h3 className='testimonial-title2' >{position}</h3></div>
        
        <div className='testimonial-body-container'><h3 className='testimonial-body2'  style={{fontStyle:"italic"}}>{text}</h3>
        </div>
        </div>
        <div className="testimonialDots">
        {testimonials.map((_, index) => (
          <div key={index} className={`testimonialDot${index === idx ? " active" : ""}`} onClick={() => {
            setIdx(index);
          }}></div>
        ))}
      </div>
        </div>
</div>
);
}
    
export default Testimonials;
解决方案

核心错误修复

崩溃的原因是回滑逻辑的判断条件完全写反了:左滑(diff>5)切换下一条的逻辑是对的,但右滑(diff<-5)切换上一条时,你错误地判断「如果是最后一条就回到0」,实际应该判断「如果是第一条(索引0)就跳到最后一条」,否则索引减1。

修正后的handleTouchMove代码:

const handleTouchMove = (e) => {
    const touchDown = touchPosition
  
    if(touchDown === null) {
        return
    }
  
    const currentTouch = e.touches[0].clientX
    const diff = touchDown - currentTouch
    const total = testimonials.length
  
    if (diff > 5) {
        // 左滑:下一条,到最后则回到0
        setIdx((prevIndex) => prevIndex === total - 1 ? 0 : prevIndex + 1)
    }
  
    if (diff < -5) {
        // 右滑:上一条,到第一条则跳到最后
        setIdx((prevIndex) => prevIndex === 0 ? total - 1 : prevIndex - 1)
    }
  
    setTouchPosition(null)
}

额外修复:按钮点击的负模运算问题

你的「上一条」按钮用了(idx - 1) % testimonials.length,但JavaScript中负数取模会得到负数(比如0-1=-1,-1%3=-1),这同样会导致testimonials[-1]报错。需要改成:

<button className="last-testimonial" onClick={() => {
    setIdx(idx => (idx - 1 + testimonials.length) % testimonials.length);
}}>...</button>

通过加上数组长度确保计算结果为非负数,再取模实现循环。

优化建议

可以统一用模运算简化循环逻辑,避免分支判断,代码更简洁不易出错:

  • 下一条:(prevIndex + 1) % total
  • 上一条:(prevIndex - 1 + total) % total

优化后的handleTouchMove:

const handleTouchMove = (e) => {
    const touchDown = touchPosition
    if(touchDown === null) return

    const currentTouch = e.touches[0].clientX
    const diff = touchDown - currentTouch
    const total = testimonials.length

    if (diff > 5) {
        setIdx(prev => (prev + 1) % total)
    } else if (diff < -5) {
        setIdx(prev => (prev - 1 + total) % total)
    }

    setTouchPosition(null)
}

内容的提问来源于stack exchange,提问作者Keith Lavoie

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.24 09:03:19