framer-motion과 transform 중앙 정렬 (문제 해결)

Devinix·2024년 4월 19일
0

[문제 해결]

목록 보기
19/29
post-thumbnail

개요

React를 이용하여 전화 번호 복사 붙여넣기 기능을 구현하고 있었다. 유저가 복사 아이콘을 누르면 작성자의 전화 번호를 복사하고, 복사됨을 알리는 UI의 애니메이션을 구현하였다.

코드를 살펴보도록 하자
(Chakra UI 라이브러리를 이용하였다.)

import {
  Box,
  Flex,
  Icon,
  Text,
  Button,
  useDisclosure,
  chakra,
} from '@chakra-ui/react';
import { AnimatePresence, motion } from 'framer-motion';
import { useState } from 'react';
import { CopyNumberIcon, HeartIcon, ReportIcon } from '@/assets/icons';
import Modal from '@/components/Modal/Modal';

interface MapGardenDetailBottomSectionProps {
  garden: GardenDetail;
}

const CopyBox = chakra(motion.div);

const MapGardenDetailBottomSection = ({
  garden,
}: MapGardenDetailBottomSectionProps) => {
  //...생략
  // 복사된 상태 (CopyBox 렌더링 용 상태)
  const [copied, setCopied] = useState(false);
  
  // 복사 기능 함수
  const handleCopyClipBoard = async (phoneNumber: string) => {
    try {
      await navigator.clipboard.writeText(phoneNumber);
      setCopied(true);

      // 2초간 UI를 보여준다.
      setTimeout(() => {
        setCopied(false);
      }, 2000);
    } catch (e) {
      alert('복사에 실패하였습니다');
    }
  };

  return (
		  // ... 생략
          <Flex
            padding="18px 20px"
            border="1px solid"
            borderColor="gray.200"
            borderRadius="9px"
            justifyContent="space-between"
          >
            <Text fontWeight="semiBold">{garden.contact}</Text>
            <Icon
              w="24px"
              h="24px"
              cursor="pointer"
              as={CopyNumberIcon}
              // 클릭 시 전화번호(garden.contact) 복사
              onClick={() => handleCopyClipBoard(garden.contact)}
            />
          </Flex>
        </Box>

        <AnimatePresence>
          {copied && (
            <CopyBox
              pos="absolute"
              top="260px"
              // 좌우 중앙 정렬
              left="50%"
              transform="translateX(-50%)"
              w="195px"
              h="48px"
              display="flex"
              alignItems="center"
              justifyContent="center"
              bgColor="green.500"
              color="white"
              fontSize="15px"
              fontWeight="medium"
              borderRadius="9px"
              initial={{ opacity: 0, y: -20 }}
              animate={{ opacity: 1, y: 0 }}
              exit={{ opacity: 0, y: 20 }}
            >
              복사 되었습니다.
            </CopyBox>
          )}
        </AnimatePresence>
	//... 생략
  );
};

export default MapGardenDetailBottomSection;

문제 상황

left: "50%"
transform: "translateX(-50%)"

위와 같이 left와 transform을 CopyBox의 props로 전달하여 좌우 중앙정렬 하고 있었으나, framer-motion의 애니메이션을 추가하고 난 후 CopyBox의 위치가 올바르게 조정되지 않는 이슈가 발생했다.

원인

framer-motion을 사용할 때 x와 y 속성으로 애니메이션을 설정하면, 내부적으로 transform: translateX()와 transform: translateY()가 적용되는 것이 원인이었다. 이 경우, 기존의 CSS transform: translateX(-50%)와 충돌하여 CopyBox의 중앙 정렬이 제대로 이루어지지 않았던 것이다.

해결 과정

원인을 파악하고 나니 해결 과정은 매우 간단했다. 애니메이션 초기(initial)에 x:"50%"을 명시적으로 추가해서 transform 속성이 올바르게 적용되도록 조정하니 다시 중앙 정렬이 된 모습을 볼 수 있었다.

        <AnimatePresence>
          {copied && (
            <CopyBox
              pos="absolute"
              top="260px"
              left="50%"
              // transform:"translateX(-50%)" 제거
              w="195px"
              h="48px"
              display="flex"
              alignItems="center"
              justifyContent="center"
              bgColor="green.500"
              color="white"
              fontSize="15px"
              fontWeight="medium"
              borderRadius="9px"
              
              // 초기값에 x:"-50%" 설정
              initial={{ opacity: 0, x:"-50%", y: -20 }}
              animate={{ opacity: 1, y: 0 }}
              exit={{ opacity: 0, y: 20 }}
            >
              복사 되었습니다.
            </CopyBox>
          )}
        </AnimatePresence>

결론

코드의 수정은 매우 간단했는데 한참 헤맸다.. framer-motion의 x와 y좌표의 이동 애니메이션은 transform을 이용해 이루어지는데 이를 모르고 있어서 마주쳤던 이슈였다. x, y, z 말고도 여러 다른 transform을 이용한 애니메이션을 구현할 때 한번 쯤 생각해 볼 만한 이슈인 것 같다.

frmaer-motion 공식 문서 일부분...

profile
프론트엔드 개발

0개의 댓글