javascript - Simple Carousel in React - How to register Scrolling to next Slide? - Stack Overflow

This is a relatively simple problem, but I haven't been able to solve it. I have built the followi

This is a relatively simple problem, but I haven't been able to solve it. I have built the following carousel / slider:

const BlogPostCardSlider = ({ children }) => {
  const [activeSlide, setActiveSlide] = useState(0);
  const activeSlideRef = useRef(null);
  const wrapperRef = useRef(null);
  const firstRenderRef = useRef(true);

  useEffect(() => {
    if (firstRenderRef.current) {
      //this is checking whether its the first render of the ponent. If it is, we dont want useEffect to run.
      firstRenderRef.current = false;
    } else if (activeSlideRef.current) {
      activeSlideRef.current.scrollIntoView({
        behavior: 'smooth',
        block: 'nearest',
        inline: 'nearest'
      });
    }
  }, [activeSlide]);

  const moveRight = () => {
    if (activeSlide + 1 >= children.length) {
      return children.length - 1;
    }
    return activeSlide + 1;
  };

  const moveLeft = () => {
    if (activeSlide - 1 <= 0) {
      return 0;
    }
    return activeSlide - 1;
  };

  return (
    <div id="trap" tabIndex="0">
      <button onClick={() => setActiveSlide(moveLeft)}>PREV</button>

      {children.map((child, i) => {
        return (
          <SlideLink
            key={`slideLink-${i}`}
            isActive={activeSlide === i}
            onClick={e => {
              setActiveSlide(i);
            }}
          >
            {i + 1}
          </SlideLink>
        );
      })}

      <Wrapper
        onScroll={e => {
          let { width } = wrapperRef.current.getBoundingClientRect();
          let { scrollLeft } = wrapperRef.current;

          if ((scrollLeft / width) % 1 === 0) {
            setActiveSlide(scrollLeft / width);
          }
        }}
        ref={wrapperRef}
      >
        {children.map((child, i) => {
          return (
            <Slide
              key={`slide-${i}`}
              ref={i === activeSlide ? activeSlideRef : null}
            >
              {child}
            </Slide>
          );
        })}
      </Wrapper>

      <button onClick={() => setActiveSlide(moveRight)}>NEXT</button>
    </div>
  );
};

export default BlogPostCardSlider;

It displays its children as Slides in a carousel. You can navigate the carousel by pressing the 'NEXT' or 'PREVIOUS' buttons. There is also a ponent that shows you what slide you're on (e.g.: 1 2 *3* 4 etc.). I call these the SlideLink(s). Whenever the activeSlide updates, the SlideLink will update and highlight you the new current Slide.

Lastly, you can also navigate by scrolling. And here's the problem:

Whenever somebody scrolls, I am checking what slide they're on by doing some calculations:

onScroll={e => {
          let { width } = wrapperRef.current.getBoundingClientRect();
          let { scrollLeft } = wrapperRef.current;

          if ((scrollLeft / width) % 1 === 0) {
            setActiveSlide(scrollLeft / width);
          }
        }}

...the result of this is that setActiveSlide is only called once the slide pletely in view. This results in a laggy experience : the user has scrolled to the next slide, the next slide is in view, but the calculation is not pleted yet (since it is only pleted once the Slide is 100% in view), so the active SlideLink gets updated very late in the process.

How would I solve this? Is there some way to optimistically update this?

This is a relatively simple problem, but I haven't been able to solve it. I have built the following carousel / slider:

const BlogPostCardSlider = ({ children }) => {
  const [activeSlide, setActiveSlide] = useState(0);
  const activeSlideRef = useRef(null);
  const wrapperRef = useRef(null);
  const firstRenderRef = useRef(true);

  useEffect(() => {
    if (firstRenderRef.current) {
      //this is checking whether its the first render of the ponent. If it is, we dont want useEffect to run.
      firstRenderRef.current = false;
    } else if (activeSlideRef.current) {
      activeSlideRef.current.scrollIntoView({
        behavior: 'smooth',
        block: 'nearest',
        inline: 'nearest'
      });
    }
  }, [activeSlide]);

  const moveRight = () => {
    if (activeSlide + 1 >= children.length) {
      return children.length - 1;
    }
    return activeSlide + 1;
  };

  const moveLeft = () => {
    if (activeSlide - 1 <= 0) {
      return 0;
    }
    return activeSlide - 1;
  };

  return (
    <div id="trap" tabIndex="0">
      <button onClick={() => setActiveSlide(moveLeft)}>PREV</button>

      {children.map((child, i) => {
        return (
          <SlideLink
            key={`slideLink-${i}`}
            isActive={activeSlide === i}
            onClick={e => {
              setActiveSlide(i);
            }}
          >
            {i + 1}
          </SlideLink>
        );
      })}

      <Wrapper
        onScroll={e => {
          let { width } = wrapperRef.current.getBoundingClientRect();
          let { scrollLeft } = wrapperRef.current;

          if ((scrollLeft / width) % 1 === 0) {
            setActiveSlide(scrollLeft / width);
          }
        }}
        ref={wrapperRef}
      >
        {children.map((child, i) => {
          return (
            <Slide
              key={`slide-${i}`}
              ref={i === activeSlide ? activeSlideRef : null}
            >
              {child}
            </Slide>
          );
        })}
      </Wrapper>

      <button onClick={() => setActiveSlide(moveRight)}>NEXT</button>
    </div>
  );
};

export default BlogPostCardSlider;

It displays its children as Slides in a carousel. You can navigate the carousel by pressing the 'NEXT' or 'PREVIOUS' buttons. There is also a ponent that shows you what slide you're on (e.g.: 1 2 *3* 4 etc.). I call these the SlideLink(s). Whenever the activeSlide updates, the SlideLink will update and highlight you the new current Slide.

Lastly, you can also navigate by scrolling. And here's the problem:

Whenever somebody scrolls, I am checking what slide they're on by doing some calculations:

onScroll={e => {
          let { width } = wrapperRef.current.getBoundingClientRect();
          let { scrollLeft } = wrapperRef.current;

          if ((scrollLeft / width) % 1 === 0) {
            setActiveSlide(scrollLeft / width);
          }
        }}

...the result of this is that setActiveSlide is only called once the slide pletely in view. This results in a laggy experience : the user has scrolled to the next slide, the next slide is in view, but the calculation is not pleted yet (since it is only pleted once the Slide is 100% in view), so the active SlideLink gets updated very late in the process.

How would I solve this? Is there some way to optimistically update this?

Share Improve this question edited Dec 23, 2019 at 10:18 R. Kohlisch asked Dec 18, 2019 at 14:02 R. KohlischR. Kohlisch 3,0137 gold badges33 silver badges64 bronze badges 1
  • I think it would be useful if you provide a demo here. – Arman Taherian Commented Dec 28, 2019 at 7:32
Add a ment  | 

2 Answers 2

Reset to default 3 +50

You can modify the calculations little bit so that when the slide is more than 50% in the view, set the active property to that one.

onScroll={e => {
    let { width } = wrapperRef.current.getBoundingClientRect();
    let { scrollLeft } = wrapperRef.current;
    setActiveSlide(Math.round(scrollLeft / width) + 1);
}}

Example: Carousel with width: 800px, Total slides: 3. So, scrollLeft will vary from 0 to 1600

  • From 0-400: Active slide = Math.round(scrollLeft / width) = 0th index (or first slide)
  • From 400-1200: Active slide = 1st index (or second slide) since it is more in the view
  • From 1200-1600: Active slide = 2nd index (or third slide) since it is more in the view

Hope it helps. Revert for any doubts.

At first sight I thought this should be a quick fix. Turned out a bit more was necessary. I create a working example here:

https://codesandbox.io/s/dark-field-g78zb?fontsize=14&hidenavigation=1&theme=dark

The main point is that setActiveSide should not be called during the onScroll event, but rather when the user is done with scrolling. This is achieved in the example using the onIdle react hook.

发布者:admin,转转请注明出处:http://www.yc00.com/questions/1745293516a4621002.html

相关推荐

发表回复

评论列表(0条)

  • 暂无评论

联系我们

400-800-8888

在线咨询: QQ交谈

邮件:admin@example.com

工作时间:周一至周五,9:30-18:30,节假日休息

关注微信