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 공식 문서 일부분...