Home > database >  Creating a react carousel with elastic effect state getting eaten up
Creating a react carousel with elastic effect state getting eaten up

Time:12-24

`

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.

  • Related