`
import "./SlideShow.css";
import { useState } from "react";
import { Button } from "@mui/material";
function SlideShow() {
const [dist, setDist] = useState(0);
console.log(dist);
const delay = (t) => new Promise((resolve) => setTimeout(resolve, t));
const n = 6;
return (
<div className="container">
<div className="left"></div>
<div className="slideshow">
<div className="view">
<div
className="images"
style={{ transform: `translate(-${dist}px,0)` }}
>
<div className="img">a</div>
<div className="img">b</div>
<div className="img">c</div>
<div className="img">d</div>
<div className="img">e</div>
<div className="img">f</div>
<div className="img">g</div>
</div>
</div>
<div className="buttons">
<Button
onClick={() => {
if (dist / 500 == 0) setDist(n * 500);
else {
setDist((d) => d - 550);
delay(600).then(() => setDist((d) => d 50));
}
}}
>
PREV
</Button>
<Button
onClick={() => {
if (dist / 500 >= n) setDist(0);
else {
setDist((d) => d 550);
delay(600).then(() => setDist((d) => d - 50));
}
}}
>
NEXT
</Button>
</div>
</div>
<div className="right"></div>
</div>
);
}
export default SlideShow;
`
CSS
.container {
width: 100vw;
height: 100vh;
display: flex;
flex-direction: row;
justify-content: center;
align-items: center;
}
.left {
flex-grow: 1;
background-color: white;
height: 100%;
z-index: 2;
}
.right {
flex-grow: 1;
background-color: white;
height: 100%;
z-index: 2;
}
.view {
width: 500px;
height: 500px;
background-color: beige;
box-shadow: 0 3px 6px rgba(0, 0, 0, 0.16), 0 3px 6px rgba(0, 0, 0, 0.23);
border-radius: 2px;
overflow-x: hidden;
}
.slideshow {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
}
body {
overflow-x: hidden;
position: relative;
}
.img {
min-width: 500px;
height: 500px;
display: grid;
place-content: center;
border: 0px solid red;
}
.images {
position: absolute;
display: flex;
flex-direction: row;
width: 100vw;
height: 500px;
transition: 750ms;
}
Basically Im trying to make a carousel but with an elastic effect. So itll move to the right 550 px and then after a timeout It'll move 50 px back. After learning how to properly use past state to update new state
It works perfectly but the issue is if you move right really fast and it resets to 0px (crosses last slide it loops back to first one) the state gets messed up and skips over some setState statements. I'm guessing it can solved using promises but I dont have enough experience any help will be appreciated.
Also can a similar effect be recreated using just css or a react library?
CodePudding user response:
It seems that the occasional mismatch of dist
happens because the change of carousel position is not synced with the elastic effect set by a timer.
Perhaps one approach to solve this could be separating the implement of carousel position and elastic effect with different properties, so they are less likely to have conflicts.
The following example uses translateX
to control the carousel position and left
to control the elastic effect. useEffect
is used to listen to changes of carousel position and set timer for the elastic effect, also throttle the "bounce back" if the changes happen too often.
Live demo of below example: stackblitz
The example also refactored the structure a bit so that the component takes some props, including an array images
for display, a sizeX
value represents the distance to move with each image, also bounce
and delay
to be specified.
The event is changed to handle the index
of images instead of actual dist
, hopefully making it easier to maintain.
Example:
const SlideShow = ({ images, sizeX, bounce, delay }) => {
const [imageIndex, setImageIndex] = useState(0);
const [elastic, setElastic] = useState("none");
useEffect(() => {
const bounceBack = setTimeout(() => {
setElastic("none");
}, delay);
return () => clearTimeout(bounceBack);
}, [imageIndex, delay]);
return (
<div className="container">
<div className="left"></div>
<div className="slideshow">
<div className="view">
<div
className="images"
style={{
transform: `translateX(-${imageIndex * sizeX}px)`,
left:
elastic === "left"
? `-${bounce}px`
: elastic === "right"
? `${bounce}px`
: "0",
}}
>
{images.map((item) => (
<div className="img" key={item}>
{item}
</div>
))}
</div>
</div>
<div className="buttons">
<button
onClick={() => {
setElastic(imageIndex === 0 ? "left" : "right");
setImageIndex((prev) =>
prev === 0 ? images.length - 1 : prev - 1
);
}}
>
PREV
</button>
<button
onClick={() => {
setElastic(imageIndex === images.length - 1 ? "right" : "left");
setImageIndex((prev) =>
prev === images.length - 1 ? 0 : prev 1
);
}}
>
NEXT
</button>
</div>
</div>
<div className="right"></div>
</div>
);
};
export default SlideShow;
For a solution with library perhaps consider to explore libraries such as framer-motion which has built-in options for customizable spring bounce.