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
2 Answers
Reset to default 3 +50You 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条)