프레이머 모션이 포함된 애니메이션 회전목마

React 애플리케이션에서 Framer 모션으로 Carousel 컴포넌트를 만들고 싶습니다.

전제 조건:


  • Javascript(ES6) 지식
  • HTML 및 CSS에 대한 지식
  • 리액트 기초지식

  • React 애플리케이션 생성 및 Npm 모듈 설치



    bash 또는 터미널에서 아래 명령을 실행합니다.

    npx create-react-app carousel
    



    cd carousel && npm install  framer-motion @popmotion/popcorn
    


    앱 구성 요소 업데이트




    import React from "react";
    import { useState } from "react";
    import { motion, AnimateSharedLayout } from "framer-motion";
    import Carousel from "./Carousel";
    import "./styles.css";
    
    const Pagination = ({ currentPage, setPage }) => {
      // Wrap all the pagination Indicators
      // with AnimateSharedPresence
      // so we can detect when Indicators
      // with a layoutId are removed/added
      return (
        <AnimateSharedLayout>
          <div className="Indicators">
            {pages.map((page) => (
              <Indicator
                key={page}
                onClick={() => setPage(page)}
                isSelected={page === currentPage}
              />
            ))}
          </div>
        </AnimateSharedLayout>
      );
    };
    
    const Indicator = ({ isSelected, onClick }) => {
      return (
        <div className="Indicator-container" onClick={onClick}>
          <div className="Indicator">
            {isSelected && (
              // By setting layoutId, when this component
              // is removed and a new one is added elsewhere,
              // the new component will animate out from the old one.
              <motion.div className="Indicator-highlight"
                          layoutId="highlight" />
            )}
          </div>
        </div>
      );
    };
    
    const pages = [0, 1, 2, 3, 4];
    
    const App = () => {
      /* We keep track of the pagination direction as well as
       * current page, this way we can dynamically generate different
       * animations depending on the direction of travel */
      const [[currentPage, direction], setCurrentPage] = useState([0, 0]);
    
      function setPage(newPage, newDirection) {
        if (!newDirection) newDirection = newPage - currentPage;
        setCurrentPage([newPage, newDirection]);
      }
    
      return (
        <>
          <Carousel
            currentPage={currentPage}
            direction={direction}
            setPage={setPage}
          />
          <Pagination currentPage={currentPage}
                      setPage={setPage} />
        </>
      );
    };
    
    export default App;
    


    캐러셀 컴포넌트 생성



    앱의 src 폴더에 Carousel.js를 추가하고 아래와 같이 코드를 업데이트합니다.

    import React from "react";
    import { useRef } from "react";
    import { motion, AnimatePresence } from "framer-motion";
    import { wrap } from "@popmotion/popcorn";
    
    // Variants in framer-motion define visual states
    // that a rendered motion component can be in at
    // any given time.
    
    const xOffset = 100;
    const variants = {
      enter: (direction) => ({
        x: direction > 0 ? xOffset : -xOffset,
        opacity: 0
      }),
      active: {
        x: 0,
        opacity: 1,
        transition: { delay: 0.2 }
      },
      exit: (direction) => ({
        x: direction > 0 ? -xOffset : xOffset,
        opacity: 0
      })
    };
    
    const pages = [0, 1, 2, 3, 4];
    
    const Carousel = ({ currentPage, setPage, direction }) => {
      /* Add and remove pages from the array to checkout
         how the gestures and pagination animations are
         fully data and layout-driven. */
      const hasPaginated = useRef(false);
    
      function detectPaginationGesture(e, { offset }) {
        if (hasPaginated.current) return;
        let newPage = currentPage;
        const threshold = xOffset / 2;
    
        if (offset.x < -threshold) {
          // If user is dragging left, go forward a page
          newPage = currentPage + 1;
        } else if (offset.x > threshold) {
          // Else if the user is dragging right,
          // go backwards a page
          newPage = currentPage - 1;
        }
    
        if (newPage !== currentPage) {
          hasPaginated.current = true;
          // Wrap the page index to within the
          // permitted page range
          newPage = wrap(0, pages.length, newPage);
          setPage(newPage, offset.x < 0 ? 1 : -1);
        }
      }
    
      return (
        <div className="slider-container">
          <AnimatePresence
            // This will be used for components to resolve
            // exit variants. It's necessary as removed
            // components won't rerender with
            // the latest state (as they've been removed)
            custom={direction}>
            <motion.div
              key={currentPage}
              className="slide"
              data-page={currentPage}
              variants={variants}
              initial="enter"
              animate="active"
              exit="exit"
              drag="x"
              onDrag={detectPaginationGesture}
              onDragStart={() => (hasPaginated.current = false)}
              onDragEnd={() => (hasPaginated.current = true)}
              // Snap the component back to the center
              // if it hasn't paginated
              dragConstraints={{ left: 0, right: 0, top: 0, bottom: 0 }}
              // This will be used for components to resolve all
              // other variants, in this case initial and animate.
              custom={direction}
            />
          </AnimatePresence>
        </div>
      );
    };
    
    export default Carousel;
    


    스타일링



    캐러셀의 간단한 스타일로 App.css를 업데이트합니다.

    body {
      display: flex;
      justify-content: center;
      align-items: center;
      min-height: 100vh;
      overflow: hidden;
      background: #09a960;
    }
    
    * {
      box-sizing: border-box;
    }
    
    .App {
      font-family: sans-serif;
      text-align: center;
    }
    
    .slider-container {
      position: relative;
      width: 600px;
      height: 600px;
    }
    
    .slide {
      border-radius: 5px;
      background: white;
      position: absolute;
      top: 0;
      left: 0;
      bottom: 0;
      right: 0;
    }
    
    /* position of indicator container */
    .Indicators {
      display: flex;
      justify-content: center;
      margin-top: 30px;
    }
    
    .Indicator-container {
      padding: 20px;
      cursor: pointer;
    }
    
    .Indicator {
      width: 10px;
      height: 10px;
      background: #fcfcfc;
      border-radius: 50%;
      position: relative;
    }
    
    .Indicator-highlight {
      top: -2px;
      left: -2px;
      background: #09f;
      border-radius: 50%;
      width: 14px;
      height: 14px;
      position: absolute;
    }
    


    앱 실행




    npm start
    


    localhost:3000에서 간단한 애니메이션 Carousel을 볼 수 있습니다.

    감사

    좋은 웹페이지 즐겨찾기