Search code examples
javascriptreactjsframer-motion

How to Make a Framer Motion Slider With Drag and Move on Button Press - ReactJS


I'm new a new developer and I'm wondering how I can implement moving the translateX on button press without breaking the constraint of the slider on drag. The drag I got, but it's just implementing the buttons is what's confusing me.

Slider Image:

The code is a mess because I am going crazy. I just want it to move it's X by some persentage of Pixels everytime the button is press and then the button will disable when at the end of the slider. The exist condition there, I don't need, it's just to show what I tried to do before with the handleRightBtn and handleLeftBtn functions, I don't think I need them because I want it to move by adding the persentage of Pixels to translateX each time, but the other way is plausible. I think you can get the logic of what I'm trying to do but I just can't implement it, can someone please help and show me some guidance

import { useState, useEffect, useRef } from "react";

// *Design Imports*
import javaScriptLogo from "../../assets/images/Javascript-shield.png";
import HTMLAndCSSLogo from "../../assets/images/pngfind.com-HTML-CSS.png";
import nodeLogo from "../../assets/images/Node.js_logo.svg.png";
import expressLogo from "../../assets/images/express-logo.png";
import ReactLogo from "../../assets/images/pngwing.com-react-logo.png";
import reduxLogo from "../../assets/images/pngwing.com-Redux.png";
import postgresLogo from "../../assets/images/pngwing.com-PostgreSql.png";
import mySqlLogo from "../../assets/images/pngwing.com-MySQL.png";
import awsLogo from "../../assets/images/aws.png";
import javaLogo from "../../assets/images/pngwing.com-java.png";
import mongoDbLogo from "../../assets/images/pngwing.com-mongodb.png";
import gitLogo from "../../assets/images/pngegg-git.png";
import gitHubLogo from "../../assets/images/Github_logo.png";
import dockerLogo from "../../assets/images/pngwing.com-Docker.png";
import {
  IoIosArrowDroprightCircle,
  IoIosArrowDropleftCircle,
} from "react-icons/io";
import "./skillsSlider.css";

import { motion, AnimatePresence, useMotionValue } from "framer-motion";

const SkillsSlider = () => {
  const [width, setWidth] = useState(0);
  const carouselRef = useRef(null);
  const [rightBtnClicked, setRightBtnClicked] = useState(false);
  const [leftBtnClicked, setLeftBtnClicked] = useState(false);

  const [index, setIndex] = useState(0);

  const [direction, setDirection] = useState(0);
  const [btnClicked, setBtnClicked] = useState(false);

  const [disableLeft, setDisableLeft] = useState(false);
  const [disableRight, setDisableRight] = useState(false);

  useEffect(() => {
    carouselRef.current.style.transform =
      "translateX(0px) translateY(0px) translateZ(0px)";
    // console.log(carouselRef.current.style.scrollWidth);
    carouselRef !== null &&
      setWidth(
        carouselRef.current.scrollWidth - carouselRef.current.offsetWidth
      );
    console.log(
      carouselRef.current.scrollWidth - carouselRef.current.offsetWidth
    );
  }, []);

  let prev;
  let after;
  // useEffect(() => {
  const handleLeftBtn = () => {
    setLeftBtnClicked(true);
    prev = direction;
    console.log("prev" + prev);
    setDirection(direction - 1);
    after = prev - 1;
    console.log("direc" + after);
  };

  const handleRightBtn = () => {
    setRightBtnClicked(true);
    prev = direction;
    console.log("prev" + prev);
    setDirection(direction + 1);
    after = prev + 1;
    console.log("direc" + after);
  };

  // const x = useMotionValue(0);

  const slide = {
    moveRight: {
        x: "-1453px",
        // opacity: 0,
    },
    center: {
      x: 0,
      // opacity: 0.7,
    },
    moveLeft: {
        x: "1453px",
        // opacity: 0,
    },
    exit: (direction) => {
      return {
        x: after > prev ? "-727px" : "727px",
        // opacity: 0,
      };
    },
  };

  useEffect(() => {
    let x = carouselRef.current.style.transform;
    if ((x = "translateX(0px) translateY(0px) translateZ(0px)")) {
      setDisableLeft(true);
    } else if ((x = "translateX(-1658px) translateY(0px) translateZ(0px)")) {
      setDisableRight(true);
    } else {
      setDisableLeft(false);
      setDisableRight(false);
    }
  }, [leftBtnClicked, rightBtnClicked]);

  return (
    <>
      <div className="btnsContainer">
        <button
          disabled={disableLeft ? true : false}
          className="actionLeftContainer"
          onClick={() => handleLeftBtn()}
        >
          <IoIosArrowDropleftCircle />
        </button>
        <button
          disabled={disableRight ? true : false}
          className="actionRightContainer"
          onClick={() => handleRightBtn()}
        >
          <IoIosArrowDroprightCircle />
        </button>
      </div>
      <div className="sliderContainer">
        <AnimatePresence
          initial={false}
          custom={direction}
        >
          <motion.div
            variants={slide}
            animate={
              rightBtnClicked
                ? "moveRight"
                : leftBtnClicked
                ? "moveLeft"
                : undefined
            }
            initial="center"
            exit="exit"
            onAnimationEnd={() => {
              setLeftBtnClicked(false);
              setRightBtnClicked(false);
              console.log(rightBtnClicked);
            }}
            custom={direction}
            key={btnClicked}
            drag="x"
            dragConstraints={{ right: 0, left: -width }}
            className="slider"
            ref={carouselRef}
          >
            <div>
              <img
                loading="lazy"
                src={javaScriptLogo}
                alt="Javascript-shield.png"
              />
            </div>
            <div>
              <img
                loading="lazy"
                src={HTMLAndCSSLogo}
                alt="pngfind.com-HTML-CSS.png"
              />
            </div>
            <div>
              <img loading="lazy" src={nodeLogo} alt="Node.js_logo.svg.png" />
            </div>
            <div>
              <img loading="lazy" src={expressLogo} alt="express-logo.png" />
            </div>
            <div>
              <img
                loading="lazy"
                src={ReactLogo}
                alt="pngwing.com-react-logo.png"
              />
            </div>
            <div>
              <img loading="lazy" src={reduxLogo} alt="pngwing.com-Redux.png" />
            </div>
            <div>
              <img
                loading="lazy"
                src={postgresLogo}
                alt="pngwing.com-PostgreSql.png"
              />
            </div>
            <div>
              <img loading="lazy" src={mySqlLogo} alt="pngwing.com-MySQL.png" />
            </div>
            <div>
              <img loading="lazy" src={awsLogo} alt="aws.png" />
            </div>
            <div>
              <img loading="lazy" src={javaLogo} alt="pngwing.com-java.png" />
            </div>
            <div>
              <img
                loading="lazy"
                src={mongoDbLogo}
                alt="pngwing.com-mongodb.png"
              />
            </div>
            <div>
              <img loading="lazy" src={gitLogo} alt="pngegg-git.png" />
            </div>
            <div>
              <img loading="lazy" src={gitHubLogo} alt="Github_logo.png" />
            </div>
            <div>
              <img
                loading="lazy"
                src={dockerLogo}
                alt="pngwing.com-Docker.png"
              />
            </div>
          </motion.div>
        </AnimatePresence>
      </div>
    </>
  );
};

export default SkillsSlider;


Solution

  • Finally, here is how I resolved my problems...! Please, anyone, let me know a better solution if you have any ideas.

    import { useState, useEffect, useRef } from "react";
    
    // *Design Imports*
    import javaScriptLogo from "../../assets/images/Javascript-shield.png";
    import HTMLAndCSSLogo from "../../assets/images/pngfind.com-HTML-CSS.png";
    import nodeLogo from "../../assets/images/Node.js_logo.svg.png";
    import expressLogo from "../../assets/images/express-logo.png";
    import ReactLogo from "../../assets/images/pngwing.com-react-logo.png";
    import reduxLogo from "../../assets/images/pngwing.com-Redux.png";
    import postgresLogo from "../../assets/images/pngwing.com-PostgreSql.png";
    import mySqlLogo from "../../assets/images/pngwing.com-MySQL.png";
    import awsLogo from "../../assets/images/aws.png";
    import javaLogo from "../../assets/images/pngwing.com-java.png";
    import mongoDbLogo from "../../assets/images/pngwing.com-mongodb.png";
    import gitLogo from "../../assets/images/pngegg-git.png";
    import gitHubLogo from "../../assets/images/Github_logo.png";
    import dockerLogo from "../../assets/images/pngwing.com-Docker.png";
    import figmaLogo from "../../assets/images/pngwing.com-figma.png";
    import {
      IoIosArrowDroprightCircle,
      IoIosArrowDropleftCircle,
    } from "react-icons/io";
    import "./skillsSlider.css";
    
    import { motion, useMotionValue } from "framer-motion";
    
    const SkillsSlider = () => {
      const [width, setWidth] = useState(0);
      const [btnClicked, setBtnClicked] = useState(false);
      const [responsiveSlider, setResponsiveSlider] = useState(false);
      const carouselRef = useRef(null);
    
      const position = useMotionValue(0);
    
      useEffect(() => {
        carouselRef !== null &&
          setWidth(
            carouselRef.current.scrollWidth - carouselRef.current.offsetWidth
          );
      }, [responsiveSlider]);
    
      // useEffect(() => {
      //   console.log(position.get(), btnClicked);
      // }, [position, btnClicked]);
    
      return (
        <>
          <div className="btnsContainer">
            <button
              disabled={btnClicked ? true : false}
              className="actionLeftContainer"
              onClick={() => {
                position.getPrevious() >= -941
                  ? position.set(0)
                  : position.set(position.get() + 941);
                setBtnClicked(true);
                setTimeout(() => {
                  setBtnClicked(false);
                }, 1000);
              }}
            >
              <IoIosArrowDropleftCircle />
            </button>
            <button
              disabled={btnClicked ? true : false}
              className="actionRightContainer"
              onClick={() => {
                position.getPrevious() <= -941
                  ? position.set(-1882)
                  : position.set(position.get() - 941);
                setBtnClicked(true);
                setTimeout(() => {
                  setBtnClicked(false);
                }, 1000);
              }}
            >
              <IoIosArrowDroprightCircle />
            </button>
          </div>
          <div className="sliderContainer">
            <motion.div
              drag="x"
              dragMomentum={false}
              dragConstraints={{ right: 0, left: -width }}
              // Sets new position of the slider.
              onDragEnd={(e, { offset }) => {
                position.set(position.get() + offset.x);
                // console.log("Drag End", offset);
                // console.log(position.get(), btnClicked);
                setResponsiveSlider(!responsiveSlider);
              }}
              animate={btnClicked && { x: position.get() }}
              transition={
                btnClicked && {
                  duration: 1,
                  type: "spring",
                  stiffness: 45,
                }
              }
              className="slider"
              ref={carouselRef}
            >
              <div>
                <img
                  loading="lazy"
                  src={javaScriptLogo}
                  alt="Javascript-shield.png"
                />
              </div>
              <div>
                <img
                  loading="lazy"
                  src={HTMLAndCSSLogo}
                  alt="pngfind.com-HTML-CSS.png"
                />
              </div>
              <div>
                <img loading="lazy" src={nodeLogo} alt="Node.js_logo.svg.png" />
              </div>
              <div>
                <img loading="lazy" src={expressLogo} alt="express-logo.png" />
              </div>
              <div>
                <img
                  loading="lazy"
                  src={ReactLogo}
                  alt="pngwing.com-react-logo.png"
                />
              </div>
              <div>
                <img loading="lazy" src={reduxLogo} alt="pngwing.com-Redux.png" />
              </div>
              <div>
                <img
                  loading="lazy"
                  src={postgresLogo}
                  alt="pngwing.com-PostgreSql.png"
                />
              </div>
              <div>
                <img loading="lazy" src={mySqlLogo} alt="pngwing.com-MySQL.png" />
              </div>
              <div>
                <img loading="lazy" src={awsLogo} alt="aws.png" />
              </div>
              <div>
                <img loading="lazy" src={javaLogo} alt="pngwing.com-java.png" />
              </div>
              <div>
                <img
                  loading="lazy"
                  src={mongoDbLogo}
                  alt="pngwing.com-mongodb.png"
                />
              </div>
              <div>
                <img loading="lazy" src={gitLogo} alt="pngegg-git.png" />
              </div>
              <div>
                <img loading="lazy" src={gitHubLogo} alt="Github_logo.png" />
              </div>
              <div>
                <img loading="lazy" src={dockerLogo} alt="pngwing.com-Docker.png" />
              </div>
              <div>
                <img loading="lazy" src={figmaLogo} alt="pngwing.com-figma.png" />
              </div>
            </motion.div>
          </div>
        </>
      );
    };
    
    export default SkillsSlider;