react framer-motion 부드러운 아코디언 메뉴

해적왕·2022년 8월 13일
2
post-thumbnail

https://codesandbox.io/s/framer-motion-2-animating-shared-layouts-1cpd0?from-embed=&file=/src/App.js:1037-1044

공식문서를 참고했지만 내 눈에 쉽게 변형했다. 가장 먼저 공식문서를 보는 습관을 길러야겠다.

AnimateSharedLayout
여러 구성 요소 간에 레이아웃 변경을 애니메이션화합니다.
이 전에는 링크를 클릭했을 때 변경하는 요소만 알고 있었는데 레이아웃 변경 시도 가능했다.

import styled, { keyframes } from "styled-components";
import { motion, AnimateSharedLayout, AnimatePresence } from "framer-motion";

const items = [
  {
    id: "1",
    title: "",
    subtitle: "",
    img:"",
  },
  {
    id: "2",
    title: "",
    subtitle: "",
    img:"",
  },
  {
    id: "3",
    title: "",
    subtitle: "",
    img:"",
  }
]

const Accordion = () => {
  return (
    <AnimateSharedLayout>
      <Container>
        <motion.ul layout transition={{ duration: 0.3, ease: [0.43, 0.13, 0.23, 0.96] }}>
          {items.map(item => (
            <Item key={item} item={item} />
          ))}
        </motion.ul>
        <h4>
          iepppop.velog
        </h4>
      </Container>
    </AnimateSharedLayout>
  )
}
export default Accordion;

const Container = styled(motion.div)`
    margin:200px auto 0 auto;
    width:50%;
    text-align:center;

    ul{
      width: 300px;
      background:#fff;
      margin:0 auto;
    }
`

<motion.div layout />
motion.divlayout은 레이아웃 변경 사항을 자동으로 애니메이션화 한다.

솔직히 이렇게 읽어도 뭔 소린지 몰랐다.그러나 역시 몇 시간 동안 해보니까 감이 왔다 ㅎ 말 그대로 자동으로 애니메이션 효과를 넣어준다는 것.

그래서 전체 ul부분과 Item.js 타이틀과 내용에 넣었다.

Item.js

import { motion, AnimatePresence, layout } from 'framer-motion';
import { useState } from 'react';
import styled from 'styled-components';

const Item = ({ item }) => {
    const [isOpen, setIsOpen] = useState(false);
    const toggleOpen = () => setIsOpen(!isOpen);

    return (
        <>
            <ItemWrap onClick={toggleOpen} layout transition={{ duration: 0.3, ease: [0.43, 0.13, 0.23, 0.96] }} key={item}>
                <motion.h1>{item.title}</motion.h1>
                <Img><img src={item.img} alt={item.title} /></Img>
            </ItemWrap>
            <AnimatePresence>
                {isOpen && (
                    <SubWrap>
                        <motion.h5 layout
                            initial={{ y: -10, opacity: 0 }}
                            animate={{ y: 0, opacity: 1 }}
                            exit={{ opacity: 0 }}
                            transition={{ duration: 0.3, ease: [0.43, 0.13, 0.23, 0.96] }}
                        >{item.subtitle}</motion.h5>
                    </SubWrap>
                )}
            </AnimatePresence>
        </>
    )
}
export default Item;

const ItemWrap = styled(motion.li)`
    cursor: pointer;
    display:flex;
    align-items:center;
    justify-content:space-between;
    border-top:1px solid #eee;
    border-bottom:1px solid #eee;
    margin-top:-1px;
    background: white;
    overflow: hidden;

    h1{
        padding:22px 30px 21px 30px;
        font-size:15px;
        z-index:1;
        opacity:0.9;
    }
`

const Img = styled(motion.div)`
    width:40px;
    height:40px;
    overflow: hidden;
    border-radius:50%;
    margin:0 30px 0 0;
    display:flex;
    justify-content:center;
    align-items:center;

    img{
        width:32px;
        height:32px;
        border-radius:50%;
        vertical-align:bottom;
    }
`

const SubWrap = styled(motion.div)`
    font-size:15px;
    height:auto;
    width:100%;
    display:flex;
    flex-direction: column;
    background: white;

    h5{
        padding:20px 30px;
        font-weight:500;
        line-height:150%;
        text-align:left;
    }

    :last-child{
        border-radius:0 0 20px 20px;
    }

`

효과가 들어가야 할 제목 부분과 하단 설명 부분에도 layout을 넣어줬다.

이건 초반에 만든 건데 보면 확실히 뚝뚝 끊기는 걸 볼 수 있다.

잔 버벅거림으로 인해 이거만 5시간 잡고 있었던 듯 하다. styled-components에 (motion.div)를 제대로 안넣고 layout만 넣고 있으니 바뀌지를 않았을 뿐.... ㅠ 바보아냐?

profile
프론트엔드

0개의 댓글