Интерактивная карусель с react

#javascript #reactjs

#javascript #reactjs

Вопрос:

Я пытался создать компонент, похожий на Instagram stories, в течение нескольких часов. Это не что иное, как интерактивная карусель, в которой вы можете перемещаться вперед и назад. Дело в том, что моя стратегия с setTimeout срабатывает каждый раз, когда я взаимодействую, и не отменяет состояние, поэтому, если пользователь находится на первой фотографии и нажимает 5 раз подряд, чтобы перейти к шестой фотографии, через несколько секунд settimeout будет подобен цунами, и компонент продвигает 5 историй подряд, начиная с 6. Я хотел каким-то образом сбрасывать свой таймер каждый раз, когда пользователь нажимает на новое изображение. Кажется сумасшедшим или это возможно?

Я не знаю, хватит ли у кого-нибудь терпения просмотреть код, если нет, то знает ли кто-нибудь хотя бы новый подход. Большое вам спасибо, я пытался решить эту проблему в течение некоторого времени.

 function Stories(){

  const files = [ 'image1.jpg', 'image2.jpg' , 'video3.mp4' ]

  const videoRef = useRef()
  const imageRef = useRef()

  const [ index , setIndex ] = useState(0); // the index starts at 0... first file...
  const [ focus, setFocus ] = useState(null); 


   // Every time the index of the storie is changed. This effect happens.

  useEffect(() => {
    
    const video = videoRef.current;
    const image = imageRef.current;
   
    if( files[index].includes('mp4')){

      video.style.display = "inline"
      video.src = files[index];



      // when i put it i put something in the "setFocus" 
      // triggers a useEffect for the "focus" 
      // that makes the setTimeOut which in the end runs
      // this same script again after a certain time.
      setFocus(files[index]);
      

      // if there is any image in the DOM, hide it
      if( image ) {
        image.style.display = "none";
      }

      //In case the files [index] is an image.
    } else {

      image.style.display = "inline"
      image.src = files[index];
      setFocus(files[index])
      

      // if there is any video in the DOM, hide it
      if ( video ){
        video.style.display = 'none';
        video.muted = true
      }
      
    }

  },[index])


  function back(){
    if( index <= 0 ) return 
    setIndex( index - 1);

  }
  function next(){
    if ( index < files.length - 1) setIndex( index   1 );
  }



  // This useeffect fires every time a file comes into focus.
  useEffect(() => {
    const timelapse = 5000;

    // I wait for the video to finish to advance to the next index
    if( files[index].includes('mp4')){

       videoRef.current.addEventListener('ended',() =>  setIndex( index   1 ));
     } 
     
     // If it's an image, so..
     else {

      setTimeout(() => {
        if( focus !== null amp;amp; index < files.length - 1){
        setIndex(index   1);
        }
      }, timelapse )
     }
  },[focus])


            // HTML
  return <>
           <video ref={videoRef}/> 
           <img ref={imageRef} />
         
          <button onClick={back}> back </button>
          <button onClick={next}> next </button>
  </>
}
  

Ответ №1:

Вы можете записать идентификатор timeout и отменить его следующим образом:

 const timeout = setTimeout(() => { console.log("woo") }, 5000)
window.clearTimeout(timeout)
  

Таким образом, одним из подходов было бы сохранить это и отменить время ожидания при вызове ваших функций back или next .

Другим подходом было бы использовать state для записи оставшегося времени и использовать таймер ( setInterval ), который отсчитывает каждую секунду и продвигает слайд, если он достигает нуля. Затем при нажатии вы можете просто повторно установить оставшееся время на 5 секунд.

 const CarouselExample = () => {
  const [time, setTime] = useState(5)
  const [slide, setSlide] = setState(1)
  
  useEffect(() => {
    const countdown = window.setInterval(() => {
      setTime(timeRemaining => {
        if (timeRemaining - 1 < 0) {
          setSlide(prevSlide => prevSlide   1)
          return 5
        } else {
          return timeRemaining - 1
        }
      })
    }, 1000)

    return () => {
      window.clearInterval(countdown)
    }
  }, [])

  return (
    <div>
      <div>Current Slide: {slide}</div>
      <button
        onClick={() => {
          setTime(5)
          setSlide(prev => prev   1)
        }}
      >
        Next
      </button>
    </div>
  )
}
  

Комментарии:

1. Мне понравился ваш второй подход, я постараюсь его реализовать. Как вы видите, представляется возможным создать некоторую логику, аналогичную моей, в которой таймер меняется в зависимости от того, является ли это видео, в котором я ожидаю событие ‘ended’ вместо установленного времени?

2. Иногда вы можете увидеть кое-что, материалы для пожилых людей.

3. Я понимаю логику, useEffect выполняется только один раз и к нему больше нет доступа. Так что вы вряд ли сможете остановить этот таймер, верно? Возможно, мне нужно попробовать что-то еще

4. Я пытаюсь до сих пор, ха-ха, я обнаружил, что событие «завершено» не создается для нескольких вызовов, в конечном итоге оно создает тот же эффект цунами. Мне придется что-то придумать, но я ценю ваши усилия

5. О, итак, для видео я бы установил оставшееся время равным длине видео, когда оно начнет воспроизводиться, а затем прослушал изменения в воспроизведении, чтобы сделать то, что наиболее подходит (например, если оно приостановлено, может быть, установить таймер на несколько секунд?)