https://github.com/braverokmc79/react-app-animation
https://react-app-animation.netlify.app/
488.CSS 트랜지션으로 애니메이션 넣기
ChallengeItem.jsx
<div className={`challenge-item-details ${isExpanded ? 'expanded' : ''}` }> <p> <button onClick={onViewDetails}> 세부 정보보기{' '} <span className="challenge-item-details-icon">▲</span> </button> </p> {isExpanded && ( <div> <p className="challenge-item-description"> {challenge.description} </p> </div> )} </div>
index.css
.challenge-item-details-icon { display: inline-block; font-size: 0.85rem; margin-left: 0.25rem; transition: transform 0.3s ease-out; } .challenge-item-details.expanded .challenge-item-details-icon { transform: rotate(180deg); }
489.CSS 애이메이션으로 애니메이션 넣기
Header.jsx
import { useState } from 'react'; import NewChallenge from './NewChallenge.jsx'; import { Link } from 'react-router-dom'; export default function Header() { const [isCreatingNewChallenge, setIsCreatingNewChallenge] = useState(); function handleStartAddNewChallenge() { setIsCreatingNewChallenge(true); } function handleDone() { setIsCreatingNewChallenge(false); } return ( <> {isCreatingNewChallenge && <NewChallenge onDone={handleDone} />} <header id="main-header"> <h1><Link to={"/"} > 도전</Link></h1> <button onClick={handleStartAddNewChallenge} className="button"> 추가 도전 </button> </header> </> ); }
.index.css
.modal { top: 10%; border-radius: 6px; padding: 1.5rem; width: 30rem; max-width: 90%; z-index: 10; animation: slide-up-fade-in 0.3s ease-out forwards; } @keyframes slide-up-fade-in{ 0% { transform: translateY(30px); opacity: 0; } 100%{ transform: translateY(0); opacity: 1; } }
490.CSS 애이메이션 -- 프레이머 모션 설치
npm install framer-motion
1.설치: Framer Motion을 사용하려면 먼저 프로젝트에 설치해야 합니다. npm을 사용하여 Framer Motion을 설치할 수 있습니다.
npm install framer-motion
2.임포트: Framer Motion을 사용하려면, motion 컴포넌트를 임포트해야 합니다:
import { motion } from 'framer-motion';
3.사용: motion 컴포넌트는 HTML 태그나 React 컴포넌트 이름을 접두사로 사용하여 애니메이션을 적용할 수 있습니다. 예를 들어, motion.div는 애니메이션을 적용할 수 있는 div 컴포넌트를 생성합니다.
<motion.div animate={{ x: 0, y: 0, scale: 1, rotate: 0 }} />
4.애니메이션: animate 속성을 사용하여 애니메이션을 정의할 수 있습니다. 이 속성은 애니메이션의 최종 상태를 나타내는 객체를 받습니다.
5.이벤트: Framer Motion은 다양한 이벤트를 지원합니다. 예를 들어, onTapStart, onTap, onTapCancel, onHoverStart, onHoverEnd 등의 이벤트를 사용할 수 있습니다.
6.변형: useTransform 훅을 사용하여 입력 범위에 따라 출력 범위를 변형할 수 있습니다.
7.스프링 애니메이션: useSpring 훅을 사용하여 스프링 애니메이션을 생성할 수 있습니다.
493.진입애니메이션 추가
import { createPortal } from 'react-dom'; import {motion} from 'framer-motion'; export default function Modal({ title, children, onClose }) { return createPortal( <> <div className="backdrop" onClick={onClose} /> <motion.dialog initial={{opacity:0, y:-30}} animate={{opacity:1, y:0}} open className="modal"> <h2>{title}</h2> {children} </motion.dialog> </>, document.getElementById('modal') ); }
<motion.dialog initial={{opacity:0, y:-30}} animate={{opacity:1, y:0}} open className="modal"> ... </motion.dialog>: motion.dialog
컴포넌트를 사용하여 애니메이션을 적용한 모달을 생성합니다. 초기 상태(initial)에서는 투명도가 0이고, y축 위치가 -30입니다.
이후 애니메이션(animate)을 통해 투명도를 1로, y축 위치를 0으로 변경하여 모달이 부드럽게 나타나게 합니다.
494.요소가 사라지는 /삭제되는 애니메이션 넣기
src/components/Header.jsx
import { useState } from 'react'; import NewChallenge from './NewChallenge.jsx'; import { Link } from 'react-router-dom'; import { AnimatePresence } from 'framer-motion'; export default function Header() { const [isCreatingNewChallenge, setIsCreatingNewChallenge] = useState(); function handleStartAddNewChallenge() { setIsCreatingNewChallenge(true); } function handleDone() { setIsCreatingNewChallenge(false); } return ( <> <AnimatePresence> {isCreatingNewChallenge && <NewChallenge onDone={handleDone} />} </AnimatePresence> <header id="main-header"> <h1><Link to={"/"} > 도전</Link></h1> <button onClick={handleStartAddNewChallenge} className="button"> 추가 도전 </button> </header> </> ); }
AnimatePresence 컴포넌트 도입:
- 프레이머 모션의 AnimatePresence 컴포넌트를 이용하면, 요소가 사라질 때 애니메이션을 적용할 수 있습니다.
- AnimatePresence는 요소가 삭제되기 전 exit 애니메이션을 실행합니다.
- 이를 통해 모달 창이 닫힐 때 애니메이션 효과를 적용할 수 있습니다.
AnimatePresence를 임포트합니다
import { AnimatePresence } from 'framer-motion';
AnimatePresence로 요소를 감쌉니다
<AnimatePresence> {isCreatingNewChallenge && <NewChallenge onDone={handleDone} />} </AnimatePresence>
motion 컴포넌트의 exit 프로퍼티를 설정합니다
<motion.div initial={{ opacity: 0 }} animate={{ opacity: 1 }} exit={{ opacity: 0 }} > {/* 모달 내용 */} </motion.div>
결과:
- 이제 모달 창을 열고 닫을 때 애니메이션이 적용됩니다.
- 백드롭을 클릭해 모달을 닫으면 exit 애니메이션이 실행되며, 서서히 사라지는 효과를 확인할 수 있습니다.
495. 마우스 오버 애니메이션으로 튀어나오는 듯한 효과 주기
Header.jsx
import { useState } from 'react'; import NewChallenge from './NewChallenge.jsx'; import { Link } from 'react-router-dom'; import { AnimatePresence, motion } from 'framer-motion'; export default function Header() { const [isCreatingNewChallenge, setIsCreatingNewChallenge] = useState(); function handleStartAddNewChallenge() { setIsCreatingNewChallenge(true); } function handleDone() { setIsCreatingNewChallenge(false); } return ( <> <AnimatePresence> {isCreatingNewChallenge && <NewChallenge onDone={handleDone} />} </AnimatePresence> <header id="main-header"> <h1><Link to={"/"} > 도전</Link></h1> <motion.button whileHover={{scale:1.1}} transition={{type:'spring', stiffness:500}} onClick={handleStartAddNewChallenge} className="button"> 추가 도전 </motion.button> </header> </> ); }
프레이머 모션을 사용하여 버튼에 자연스럽고 튀어 나오는 애니메이션을 적용하는 방법을 요약하겠습니다:
motion 컴포넌트 가져오기:
모션 버튼으로 변경:
- 기존 버튼을 motion.button으로 바꾸고 특수 속성들을 추가합니다
<motion.button whileHover={{ scale: 1.1 }} transition={{ type: 'spring', stiffness: 500 }}> Hover me! </motion.button>
whileHover 속성 사용:
- whileHover 속성을 사용하여 커서를 올릴 때만 적용되는 애니메이션 상태를 정의합니다.
- 예: 버튼을 10% 크게 만들기 위해 scale: 1.1 설정.
스프링 애니메이션 적용:
- 기본적으로 프레이머 모션은 스프링 애니메이션을 적용하지 않습니다.
transition 속성을 사용하여 애니메이션 타입을 spring으로 설정합니다
<motion.button whileHover={{ scale: 1.1 }} transition={{ type: 'spring', stiffness: 500 }} > Hover me! </motion.button>
- stiffness 값을 조정하여 튕기는 효과를 조절할 수 있습니다. 예를 들어, stiffness: 500.
추가 조정:
- mass 등 다른 스프링 속성을 추가하여 다양한 애니메이션 효과를 실험할 수 있습니다
<motion.button whileHover={{ scale: 1.1 }} transition={{ type: 'spring', stiffness: 500, mass: 1 }} > Hover me! </motion.button>
결과:
- 버튼에 커서를 올리면 크기가 커지면서 튕겨 나오는 애니메이션 효과가 적용됩니다.
- 스프링 애니메이션 설정을 통해 더욱 자연스러운 애니메이션을 구현할 수 있습니다.
이렇게 하면 버튼이 커서에 반응하여 자연스럽게 튕겨 나오는 애니메이션을 쉽게 구현할 수 있습니다.
496. 애니메이션 상태 재사용하기
Modal.jsx
import { createPortal } from 'react-dom'; import {motion} from 'framer-motion'; export default function Modal({ title, children, onClose }) { //const hiddenAnimationState={opacity:0, y:30}; return createPortal( <> <div className="backdrop" onClick={onClose} /> <motion.dialog variants={{ hidden:{opacity:0, y:30}, visible:{opacity:1, y:0} }} initial="hidden" animate="visible" exit="hidden" open className="modal"> <h2>{title}</h2> {children} </motion.dialog> </>, document.getElementById('modal') ); }
상수로 애니메이션 상태 관리:
- initial과 exit 상태가 동일하다면, 상수로 애니메이션 상태를 정의하여 재사용할 수 있습니다:
const hiddenAnimationState = { opacity: 0, y: -50 };
모달 컴포넌트에서 상수 사용:
- initial과 exit에 각각 상수를 사용하여 중복을 줄입니다
<motion.div initial={hiddenAnimationState} animate={{ opacity: 1, y: 0 }} exit={hiddenAnimationState} > {/* 모달 내용 */} </motion.div>
variants 사용:
- variants 속성을 사용하여 애니메이션 상태를 정의하고 재사용합니다
const modalVariants = { hidden: { opacity: 0, y: -50 }, visible: { opacity: 1, y: 0 } };
variants를 이용한 모달 설정:
- initial, animate, exit에 variants 식별자를 사용하여 애니메이션 상태를 적용합니다
<motion.div variants={modalVariants} initial="hidden" animate="visible" exit="hidden" > {/* 모달 내용 */} </motion.div>
이점:
- 상수나 variants를 사용하면 수정이 필요할 때 한 곳만 변경하면 되므로 유지보수가 쉽습니다.
- variants를 사용하면 애니메이션 상태를 재사용하거나 다른 곳에서도 쉽게 적용할 수 있습니다.
이렇게 하면 모달의 애니메이션 상태를 효과적으로 관리할 수 있습니다. 결과는 이전과 동일하지만, 코드가 더 간결하고 유지보수가 용이해집니다. 다음 강의에서는 variants의 더 많은 기능과 활용법을 배울 예정입니다.
497. 중첩 애니메이션과 배리언트
배리언트 정의:
- 부모 컴포넌트에서 사용할 배리언트를 정의합니다
const modalVariants = { hidden: { opacity: 0, y: -50 }, visible: { opacity: 1, y: 0 } };
모달 컴포넌트 설정:
- variants 속성을 사용하여 모달의 initial, animate, exit 상태를 설정합니다
<motion.div variants={modalVariants} initial="hidden" animate="visible" exit="hidden" > {/* 모달 내용 */} </motion.div>
자식 컴포넌트 설정:
- 자식 컴포넌트에서 동일한 배리언트를 정의합니다
const listItemVariants = { hidden: { opacity: 0, scale: 0.8 }, visible: { opacity: 1, scale: 1 } };
자식 컴포넌트에 배리언트 적용:
- 자식 컴포넌트에서 variants 속성을 사용하여 배리언트를 설정합니다
<motion.li variants={listItemVariants} transition={{ type: 'spring', stiffness: 300 }} > {/* 리스트 아이템 내용 */} </motion.li>
exit 상태 오버라이드:
- 자식 컴포넌트에서 부모의 exit 상태를 오버라이드하여 특정 애니메이션 동작을 방지합니다
<motion.li variants={listItemVariants} transition={{ type: 'spring', stiffness: 300 }} exit={{ opacity: 1, scale: 1 }} // visible 상태의 값 복사 > {/* 리스트 아이템 내용 */} </motion.li>
애니메이션 동작 확인:
- 모달이 열리고 닫힐 때 자식 컴포넌트의 애니메이션이 부모의 배리언트 설정에 따라 자동으로 활성화됩니다.
코드 예시:
import { motion } from 'framer-motion'; const modalVariants = { hidden: { opacity: 0, y: -50 }, visible: { opacity: 1, y: 0 } }; const listItemVariants = { hidden: { opacity: 0, scale: 0.8 }, visible: { opacity: 1, scale: 1 } }; const Modal = ({ isOpen, onClose }) => ( <AnimatePresence> {isOpen && ( <motion.div className="modal" variants={modalVariants} initial="hidden" animate="visible" exit="hidden" > <form> <motion.ul> <motion.li variants={listItemVariants} transition={{ type: 'spring', stiffness: 300 }} exit={{ opacity: 1, scale: 1 }} // visible 상태의 값 복사 > {/* 이미지 또는 리스트 아이템 내용 */} </motion.li> </motion.ul> </form> </motion.div> )} </AnimatePresence> );
요약:
- 부모 컴포넌트에서 배리언트를 정의하고 이를 자식 컴포넌트에서도 동일하게 설정하면 애니메이션 상태가 자동으로 적용됩니다.
- exit 상태를 오버라이드하여 자식 컴포넌트의 애니메이션을 조절할 수 있습니다.
- 이렇게 하면 모달의 애니메이션 동작을 일관성 있게 관리할 수 있으며, 유지보수가 용이합니다
498. 리스트 애니메이션에 스태거링 효과 주기
1. 부모 컴포넌트를 motion 요소로 변환
리스트 항목의 부모 요소를 motion 요소로 변환합니다. 이 경우에는 ul 요소를 motion.ul로 바꿉니다.
import { motion } from 'framer-motion'; // 리스트 항목의 부모 요소를 motion.ul로 변환 <motion.ul> {/* 리스트 항목들 */} </motion.ul>
2.배리언트 설정
부모 컴포넌트에 배리언트를 설정하고, 자식 컴포넌트에 시차를 적용하는 애니메이션을 정의합니다.
const listVariants = { visible: { transition: { staggerChildren: 0.05, // 자식 요소 애니메이션 시작 시점의 시차를 설정 } } }; const listItemVariants = { hidden: { opacity: 0, scale: 0.8 }, visible: { opacity: 1, scale: 1, transition: { type: 'spring', stiffness: 300 } } };
3. 부모 컴포넌트에 배리언트 적용
부모 컴포넌트 (motion.ul)에 배리언트를 적용합니다.
<motion.ul variants={listVariants} initial="hidden" animate="visible"> {/* 리스트 항목들 */} </motion.ul>
4. 자식 컴포넌트에 배리언트 적용
리스트 항목을 motion.li로 변환하고, 배리언트를 적용합니다.
<motion.li variants={listItemVariants}> {/* 리스트 항목 내용 */} </motion.li>
최종 코드 예시
import { motion } from 'framer-motion'; const listVariants = { visible: { transition: { staggerChildren: 0.05, // 자식 요소 애니메이션 시작 시점의 시차를 설정 } } }; const listItemVariants = { hidden: { opacity: 0, scale: 0.8 }, visible: { opacity: 1, scale: 1, transition: { type: 'spring', stiffness: 300 } } }; const Modal = ({ isOpen, onClose }) => ( <AnimatePresence> {isOpen && ( <motion.div className="modal" variants={listVariants} initial="hidden" animate="visible" exit="hidden" > <form> <motion.ul variants={listVariants} initial="hidden" animate="visible"> <motion.li variants={listItemVariants}> {/* 이미지 또는 리스트 아이템 내용 */} </motion.li> <motion.li variants={listItemVariants}> {/* 이미지 또는 리스트 아이템 내용 */} </motion.li> {/* 추가 리스트 항목들 */} </motion.ul> </form> </motion.div> )} </AnimatePresence> );
요약
- 부모 요소를 motion.ul로 변환하고, variants 속성을 사용하여 배리언트를 설정합니다.
- 자식 요소인 리스트 항목을 motion.li로 변환하고, 배리언트를 적용합니다.
- 부모 배리언트에서 staggerChildren 속성을 사용하여 자식 요소의 애니메이션 시작 시점에 시차를 줍니다.
이렇게 하면 리스트 항목이 하나씩 차례대로 애니메이션화되어 자연스러운 시각적 효과를 제공할 수 있습니다.
499. 색상 애니메이션 추가 및 키 프레임으로 효과 주기
1. 숫자가 아닌 값으로 애니메이션 설정
애니메이션 속성에 숫자뿐만 아니라 색상 등의 다른 값도 설정할 수 있습니다.
예시: 버튼에 마우스 오버할 때 배경색을 변경
<motion.button whileHover={{ backgroundColor: '#8b11f0', scale: 1.1 }} > Hover me </motion.button>
이 코드에서는 backgroundColor 속성을 헥스 코드 값으로 설정하여 버튼의 배경색을 변경합니다.
2. 키 프레임을 사용한 애니메이션 설정
애니메이션 속성에 단일 값 대신 배열을 사용하여 키 프레임 애니메이션을 설정할 수 있습니다. 이를 통해 애니메이션의 여러 단계를 정의할 수 있습니다.
예시: 리스트 항목에 키 프레임 애니메이션 적용
const listItemVariants = { hidden: { opacity: 0, scale: 0.5 }, visible: { opacity: 1, scale: [0.8, 1.3, 1], // 키 프레임 설정 transition: { type: 'spring', stiffness: 300 } } }; <motion.li variants={listItemVariants}> {/* 리스트 항목 내용 */} </motion.li>
이 코드는 scale 속성에 키 프레임을 설정하여 애니메이션이 0.8, 1.3, 1의 단계를 거치도록 합니다
3. 스태거링(staggering) 효과 적용
리스트 항목이 하나씩 차례대로 애니메이션화되도록 설정할 수 있습니다. 이를 위해 부모 요소에 staggerChildren 옵션을 설정합니다.
예시: 스태거링 효과 적용
const listVariants = { visible: { transition: { staggerChildren: 0.05, // 자식 요소 애니메이션 시작 시점의 시차를 설정 } } }; <motion.ul variants={listVariants} initial="hidden" animate="visible"> <motion.li variants={listItemVariants}> {/* 리스트 항목 내용 */} </motion.li> <motion.li variants={listItemVariants}> {/* 리스트 항목 내용 */} </motion.li> </motion.ul>
이 코드는 리스트 항목들이 동시에 나타나지 않고 차례대로 나타나도록 합니다.
4. 배리언트와 애니메이션 속성의 재사용
부모 컴포넌트에서 설정한 애니메이션 상태가 자식 컴포넌트에 자동으로 전달됩니다. 이를 통해 애니메이션 설정을 재사용할 수 있습니다.
예시: 부모 컴포넌트에서 배리언트 설정
const modalVariants = { hidden: { opacity: 0 }, visible: { opacity: 1, transition: { staggerChildren: 0.05 } } }; <motion.div variants={modalVariants} initial="hidden" animate="visible" exit="hidden"> <motion.ul> <motion.li variants={listItemVariants}> {/* 리스트 항목 내용 */} </motion.li> </motion.ul> </motion.div>
이렇게 하면 부모 컴포넌트의 배리언트 설정이 자식 컴포넌트에도 적용되어 자연스러운 애니메이션 효과를 얻을 수 있습니다.
요약
프레이머 모션을 활용하면 단순한 숫자 값 외에도 색상, 배열 등의 다양한 값을 사용하여 애니메이션을 설정할 수 있습니다. 또한, 배리언트와 스태거링 효과를 활용하여 복잡한 애니메이션을 손쉽게 구현할 수 있습니다. 이를 통해 더욱 생동감 있고 다채로운 UI를 만들 수 있습니다.
500.명령적 접근법으로 애니메이션 구현하기
명령적 애니메이션 설정
명령적 방식으로 애니메이션을 트리거하기 위해 useAnimate 훅을 사용합니다.
1. useAnimate 훅 가져오기
import { useAnimate } from 'framer-motion';
2. useAnimate 훅 사용하기
컴포넌트 함수 내에서 useAnimate 훅을 호출하여 scope와 animate 요소를 가져옵니다.
const [scope, animate] = useAnimate();
3. 애니메이션 트리거 설정
폼이 유효하지 않을 때 애니메이션을 트리거합니다.
const handleSubmit = () => { if (!isValidForm()) { animate('input, textarea', { x: [-10, 0, 10, 0] }, { type: 'spring', duration: 0.2, stagger: 0.05 }); return; } // 유효한 폼 제출 처리 };
- 선택자: 'input, textarea'는 애니메이션을 적용할 요소를 선택합니다.
- 애니메이션 객체: { x: [-10, 0, 10, 0] }는 요소가 좌우로 흔들리도록 x 축 위치를 정의합니다.
- 설정 객체: { type: 'spring', duration: 0.2, stagger: 0.05 }는 애니메이션 타입, 지속 시간, 지연 시간을 설정합니다.
4. scope를 이용해 범위 지정
scope를 사용하여 애니메이션이 특정 범위 내에서만 작동하도록 합니다. 이 예제에서는 폼 요소에 ref를 추가합니다
return ( <form ref={scope} onSubmit={handleSubmit}> <input type="text" /> <textarea /> <button type="submit">Submit</button> </form> );
요약
- useAnimate 훅 사용: useAnimate 훅을 가져와 scope와 animate 요소를 추출합니다.
- 애니메이션 트리거 설정: animate 함수를 사용하여 특정 이벤트(예: 유효하지 않은 폼 제출)에서 애니메이션을 트리거합니다.
- 선택자와 애니메이션 객체: CSS 선택자와 애니메이션 객체를 통해 애니메이션을 적용할 요소와 속성을 정의합니다.
- scope 설정: scope를 이용해 애니메이션 적용 범위를 제한합니다.
이렇게 하면 명령적 방식으로 애니메이션을 설정하여 특정 이벤트에 반응하는 애니메이션 효과를 손쉽게 구현할 수 있습니다.
NewChallenge.jsx
import { useContext, useRef, useState } from 'react'; import { ChallengesContext } from '../store/challenges-context.jsx'; import Modal from './Modal.jsx'; import images from '../assets/images.js'; import { motion, useAnimate, stagger } from 'framer-motion'; export default function NewChallenge({ onDone }) { const title = useRef(); const description = useRef(); const deadline = useRef(); const [scope, animate] = useAnimate(); ~ function handleSubmit(event) { ~ animate( 'input, textarea', { x: [-10, 0, 10, 0] }, { type: 'spring', duration: 0.2, delay: stagger(0.05) } ); ~ return ( <Modal title="도전하기 추가!" onClose={onDone}> <form id="new-challenge" onSubmit={handleSubmit} ref={scope}> ~
501.레이아웃 변화에 애니메이션 적용하기
프레이머 모션 설정:
- motion과 AnimatePresence를 사용하여 요소가 사라지거나 나타날 때 애니메이션을 적용합니다.
- layout 속성을 사용하여 레이아웃 변화에 애니메이션을 적용합니다.
ChallengeItem 컴포넌트 수정:
- ChallengeItem 컴포넌트의 li 태그를 motion.li로 변경합니다.
- motion.li에 layout 속성을 추가하여 레이아웃 변경 시 애니메이션이 적용되도록 합니다.
ChallengeItem.jsx
import { motion } from 'framer-motion'; function ChallengeItem({ challenge, onComplete }) { return ( <motion.li layout> <h3>{challenge.title}</h3> <p>{challenge.description}</p> <button onClick={() => onComplete(challenge.id)}>완료</button> </motion.li> ); }
Challenges.jsx
import { motion, AnimatePresence } from 'framer-motion'; function Challenges({ displayedChallenges }) { return ( <AnimatePresence> {displayedChallenges.length > 0 ? ( <motion.ol key="list" initial={{ opacity: 0, y: 20 }} animate={{ opacity: 1, y: 0 }} exit={{ opacity: 0, y: -20 }} > {displayedChallenges.map(challenge => ( <ChallengeItem key={challenge.id} challenge={challenge} /> ))} </motion.ol> ) : ( <motion.p key="fallback" initial={{ opacity: 0, y: 20 }} animate={{ opacity: 1, y: 0 }} exit={{ opacity: 0, y: -20 }} > No challenges found </motion.p> )} </AnimatePresence> ); }
요약
motion과 AnimatePresence 사용:
- AnimatePresence는 요소가 DOM에서 제거될 때 애니메이션을 적용합니다.
- motion 컴포넌트에 layout 속성을 추가하여 레이아웃 변화 시 애니메이션이 적용됩니다.
애니메이션 적용:
- ChallengeItem의 li 태그를 motion.li로 변경하고 layout 속성을 추가하여 레이아웃 변화 애니메이션을 활성화합니다.
- Challenges 컴포넌트에서 motion.ol과 motion.p를 사용하여 탭 전환 시 애니메이션을 적용합니다.
부드러운 전환 구현:
- 항목이 '완료'로 표시되어 다른 탭으로 이동할 때, motion 컴포넌트의 layout 속성 덕분에 부드럽게 애니메이션이 적용됩니다.
502.여러 요소 애니메이션 조율하기
1. AnimatePresence 컴포넌트 소개
- AnimatePresence는 요소가 DOM에서 사라질 때 애니메이션을 적용할 수 있게 해주는 프레이머 모션의 컴포넌트입니다.
- Challenges.jsx 파일에서 항목을 렌더링하고, 이 파일에서 AnimatePresence를 적용해야 합니다.
2. 기본 설정
Challenges.jsx 파일에 AnimatePresence를 가져옵니다:
import { AnimatePresence, motion } from 'framer-motion';
도전 항목 리스트를 AnimatePresence로 래핑합니다:
<AnimatePresence> {displayedChallenges.map((challenge) => ( <ChallengeItem key={challenge.id} challenge={challenge} /> ))} </AnimatePresence>
개별 항목 애니메이션 설정
- ChallengeItem 컴포넌트 파일에서 motion 컴포넌트로 감싸고, exit 속성을 추가합니다
<motion.li key={challenge.id} exit={{ opacity: 0, y: -30 }} > {challenge.content} </motion.li>
리스트의 전체 애니메이션 적용
- 전체 리스트가 DOM에서 사라질 때 애니메이션을 적용하기 위해 Challenges.jsx 파일에서 리스트를 AnimatePresence로 래핑하고, motion.ol로 변경합니다:
<AnimatePresence> <motion.ol exit={{ opacity: 0, y: -30 }}> {displayedChallenges.map((challenge) => ( <ChallengeItem key={challenge.id} challenge={challenge} /> ))} </motion.ol> </AnimatePresence>
탭 전환 시 애니메이션 적용
- 리스트가 비어있을 때 나타나는 대체 문구까지 AnimatePresence로 감싸고, 리스트와 문구 각각에 key 속성을 추가합니다:
<AnimatePresence mode="wait"> {displayedChallenges.length > 0 ? ( <motion.ol key="list" exit={{ opacity: 0, y: -30 }}> {displayedChallenges.map((challenge) => ( <ChallengeItem key={challenge.id} challenge={challenge} /> ))} </motion.ol> ) : ( <motion.p key="fallback" initial={{ opacity: 0, y: 20 }} animate={{ opacity: 1, y: 0 }} exit={{ opacity: 0, y: -20 }}> No challenges found </motion.p> )} </AnimatePresence>
동작 방식 확인 및 조정
- 리스트가 비었을 때 대체 문구가 애니메이션과 함께 나타나는지 확인합니다.
- mode="wait" 속성을 설정하여 첫 번째 요소가 사라진 후 나타나는 애니메이션이 재생되도록 합니다.
7. 결론
- AnimatePresence를 사용하여 리스트 항목이 사라질 때 애니메이션을 적용하면, UX가 더욱 부드럽고 시각적으로 매력적으로 변합니다.
- 여러 항목에 AnimatePresence를 사용하면 각 항목의 애니메이션 동작 방식을 세밀하게 통제할 수 있습니다
503.레이아웃 애니메이션과 다른 애니메이션 통합하기
문제점 및 원인
- 리스트 항목을 클릭할 때 항목들이 울렁거리고 좌측 이미지가 왜곡되는 문제 발생.
- 이 문제의 원인은 ChallengeItem에 layout 속성이 적용되어 있어서, 항목의 높이 변화에 프레이머 모션이 애니메이션을 적용하기 때문.
해결 방법
layout 속성 제거:
- ChallengeItem에서 layout 속성을 제거합니다.
- 제거 후 항목들이 울렁거리는 문제는 해결되지만, 애니메이션 없이 갑자기 사라지게 됩니다.
애니메이션 적용:
- 항목이 나타날 때 명시적으로 애니메이션을 적용합니다.
애니메이션 구현
motion.div 적용:
- 세부 사항 영역을 감싸고 있는 div를 motion.div로 변경합니다.
- initial과 animate 속성을 추가해 진입 애니메이션을 설정합니다
<motion.div initial={{ height: 0, opacity: 0 }} animate={{ height: 'auto', opacity: 1 }} > {details} </motion.div>
- initial 상태는 높이를 0, 불투명도를 0으로 설정하여 아직 나타나기 전 상태를 정의합니다.
- animate 상태는 높이를 자동으로 설정하고, 불투명도를 1로 설정하여 자연스럽게 나타나도록 합니다.
exit 애니메이션 추가:
- exit 속성을 추가하여 요소가 사라질 때의 애니메이션을 설정합니다
<motion.div initial={{ height: 0, opacity: 0 }} animate={{ height: 'auto', opacity: 1 }} exit={{ height: 0, opacity: 0 }} > {details} </motion.div>
AnimatePresence로 래핑:
- AnimatePresence로 세부 사항 영역을 래핑하여 사라질 때의 애니메이션도 적용합니다
import { AnimatePresence, motion } from 'framer-motion'; <AnimatePresence> {isExpanded && ( <motion.div initial={{ height: 0, opacity: 0 }} animate={{ height: 'auto', opacity: 1 }} exit={{ height: 0, opacity: 0 }} > {details} </motion.div> )} </AnimatePresence>
최종 코드 정리
- AnimatePresence와 motion 컴포넌트를 사용하여 세부 사항 영역의 애니메이션을 부드럽게 적용합니다.
- 항목이 나타날 때와 사라질 때의 애니메이션을 명시적으로 설정하여 울렁거림과 왜곡 문제를 해결합니다.
이렇게 하면 리스트 항목의 세부 사항을 열고 닫을 때 자연스러운 애니메이션이 적용되고, 다른 항목이 왜곡되지 않으며 부드러운 동작을 유지할 수 있습니다.
504.공유된 요소레 애니메이션 적용하기
1. 항목이 없는 탭에서 항목이 있는 탭으로 이동 시 애니메이션
- Challenges.jsx 파일 수정:
- 순서 있는 리스트에 initial과 animate 속성을 추가하여, 항목이 나타날 때 애니메이션을 적용합니다.
import { AnimatePresence, motion } from 'framer-motion'; // 순서 있는 리스트 컴포넌트에 애니메이션 추가 <motion.ol initial={{ opacity: 0, y: 20 }} animate={{ opacity: 1, y: 0 }} exit={{ opacity: 0, y: 20 }} > {displayedChallenges.map(challenge => ( <ChallengeItem key={challenge.id} challenge={challenge} /> ))} </motion.ol>
2. 탭 아래 바 애니메이션 적용
Tab 컴포넌트 수정:
- 탭 하단 바에 애니메이션을 적용하기 위해 motion.div로 변경하고, layoutId 속성을 추가합니다.
- 먼저 motion을 프레이머 모션에서 가져옵니다
import { motion } from 'framer-motion';
2.바 컴포넌트를 motion.div로 변경:
- 바 요소를 motion.div로 변환하고 layoutId 속성을 추가합니다.
<motion.div className="tab-indicator" layoutId="tab-indicator" />
전체 코드 정리
Challenges.jsx:
import { AnimatePresence, motion } from 'framer-motion'; import ChallengeItem from './ChallengeItem'; function Challenges({ displayedChallenges }) { return ( <AnimatePresence> {displayedChallenges.length > 0 ? ( <motion.ol initial={{ opacity: 0, y: 20 }} animate={{ opacity: 1, y: 0 }} exit={{ opacity: 0, y: 20 }} > {displayedChallenges.map(challenge => ( <ChallengeItem key={challenge.id} challenge={challenge} /> ))} </motion.ol> ) : ( <motion.div initial={{ opacity: 0, y: 20 }} animate={{ opacity: 1, y: 0 }} exit={{ opacity: 0, y: 20 }} > No challenges found </motion.div> )} </AnimatePresence> ); }
Tab 컴포넌트:
import { motion } from 'framer-motion'; function Tab({ isActive, label }) { return ( <div className={`tab ${isActive ? 'active' : ''}`}> {label} {isActive && ( <motion.div className="tab-indicator" layoutId="tab-indicator" /> )} </div> ); }
결론
항목이 없는 탭에서 항목이 있는 탭으로 이동 시 애니메이션:
- Challenges.jsx 파일에서 순서 있는 리스트에 initial과 animate 속성을 추가하여 부드러운 진입 애니메이션을 적용했습니다.
탭 아래 바 애니메이션:
- Tab 컴포넌트에서 바를 motion.div로 변환하고 layoutId 속성을 추가하여 부드러운 애니메이션을 적용했습니다.
이 두 가지 개선을 통해 UI의 전환이 더욱 자연스럽고 매끄럽게 되었으며, 사용자 경험이 향상되었습니다.
505.키를 활용해 애니메이션 다시 트리거하기
애니메이션 다시 트리거하기: 배지 애니메이션
이 섹션에서는 배지의 숫자가 바뀔 때마다 애니메이션이 재생되도록 설정하는 방법을 다룹니다. 예를 들어, 항목을 완료 목록에서 실패 목록으로 이동시키면 두 탭의 배지가 튀어 오르는 애니메이션을 추가하는 방식입니다.
1. 배지 컴포넌트 수정
프레이머 모션에서 motion 가져오기:
- Badge 컴포넌트 파일에서 프레이머 모션에서 motion을 가져옵니다
import { motion } from 'framer-motion';
2.span을 motion.span으로 변경:
- span 요소를 motion.span으로 변경합니다.
const Badge = ({ count }) => { return ( <motion.span initial={{ scale: 1 }} animate={{ scale: [1, 1.2, 1] }} transition={{ duration: 0.3 }} > {count} </motion.span> ); };
2. ChallengeTabs 컴포넌트에서 애니메이션 재실행 설정
- key 속성 추가:
- Badge 컴포넌트가 있는 ChallengeTabs 컴포넌트에서 key 속성을 추가하여, badgeCaption 값을 key로 설정합니다.
const ChallengeTabs = ({ completedCount, failedCount }) => { return ( <div> <div className="tab"> Completed <Badge key={completedCount} count={completedCount} /> </div> <div className="tab"> Failed <Badge key={failedCount} count={failedCount} /> </div> </div> ); };
정리
- Badge 컴포넌트 수정:
- motion.span으로 변경하고 initial, animate, transition 속성을 추가하여 배지의 크기가 변하는 애니메이션을 설정합니다.
- ChallengeTabs 컴포넌트 수정:
- Badge 컴포넌트에 key 속성을 추가하여, 숫자가 변경될 때마다 애니메이션이 재생되도록 설정합니다.
이렇게 하면 항목을 이동할 때마다 배지의 숫자가 변경되면, 배지가 튀어 오르는 애니메이션이 다시 재생되어 UI가 더 생동감 있게 됩니다. 리액트에서 key 속성을 이용해 컴포넌트를 재생성하여 애니메이션을 다시 트리거하는 유용한 패턴을 익혔습니다.
506.스크롤 기반 애니메이션
스크롤 애니메이션을 활용한 패럴랙스 효과 적용하기
이번 섹션에서는 프레이머 모션을 활용하여 스크롤 애니메이션을 통해 히어로 이미지와 배경 이미지를 패럴랙스 효과로 움직이는 방법을 다룹니다
1. 프레이머 모션 설정
프레이머 모션에서 motion 가져오기:
- Welcome.jsx 파일에 motion을 가져옵니다.
import { motion, useScroll, useTransform } from 'framer-motion';
2.div와 img를 motion으로 변경:
- div와 img 요소를 motion.div와 motion.img로 변경합니다.
<motion.div> <motion.img src="hero.jpg" alt="Hero" /> <motion.img src="city.jpg" alt="City" /> </motion.div>
2. 스크롤 값 변환 설정
스크롤 값을 받아오는 useScroll 훅 사용:
const { scrollY } = useScroll();
2.스크롤 값을 애니메이션 값으로 변환하는 useTransform 훅 사용:
- 예: 도시 이미지의 불투명도와 y 축 위치를 변경하는 코드
const opacityCity = useTransform(scrollY, [0, 200, 300, 500], [1, 0.5, 0.5, 0]); const yCity = useTransform(scrollY, [0, 200], [0, -100]);
3.스타일 속성에 변환된 값 할당:
<motion.img src="city.jpg" alt="City" style={{ opacity: opacityCity, y: yCity }} />
3. 다른 요소에도 패럴랙스 효과 적용
히어로 이미지 및 텍스트에 애니메이션 추가:
- 히어로 이미지
const opacityHero = useTransform(scrollY, [0, 300, 500], [1, 1, 0]); const yHero = useTransform(scrollY, [0, 200], [0, -150]); <motion.img src="hero.jpg" alt="Hero" style={{ opacity: opacityHero, y: yHero }} />
- 텍스트
const scaleText = useTransform(scrollY, [0, 300], [1, 1.5]); const yText = useTransform(scrollY, [0, 200, 300, 500], [0, 50, 50, 300]); <motion.div style={{ scale: scaleText, y: yText }}> Welcome to our site </motion.div>
4. 전체 코드 예시
import { motion, useScroll, useTransform } from 'framer-motion'; const Welcome = () => { const { scrollY } = useScroll(); const opacityCity = useTransform(scrollY, [0, 200, 300, 500], [1, 0.5, 0.5, 0]); const yCity = useTransform(scrollY, [0, 200], [0, -100]); const opacityHero = useTransform(scrollY, [0, 300, 500], [1, 1, 0]); const yHero = useTransform(scrollY, [0, 200], [0, -150]); const scaleText = useTransform(scrollY, [0, 300], [1, 1.5]); const yText = useTransform(scrollY, [0, 200, 300, 500], [0, 50, 50, 300]); return ( <motion.div> <motion.img src="city.jpg" alt="City" style={{ opacity: opacityCity, y: yCity }} /> <motion.img src="hero.jpg" alt="Hero" style={{ opacity: opacityHero, y: yHero }} /> <motion.div style={{ scale: scaleText, y: yText }}> Welcome to our site </motion.div> </motion.div> ); }; export default Welcome;
import { Link } from "react-router-dom"; import cityImg from "../assets/city.jpg"; import heroImg from "../assets/hero.png"; import Footer from "../components/Footer"; import { motion, useScroll, useTransform } from 'framer-motion'; export default function WelcomePage() { const {scrollY} =useScroll(); const opacityCity = useTransform(scrollY, [0, 200, 300, 500], [1, 0.5, 0.5, 0]); const yCity = useTransform(scrollY, [0, 200], [0, -100]); const opacityHero = useTransform(scrollY, [0, 300, 500], [1, 1, 0]); const yHero = useTransform(scrollY, [0, 200], [0, -150]); const scaleText = useTransform(scrollY, [0, 300], [1, 1.5]); const yText = useTransform(scrollY, [0, 200, 300, 500], [0, 50, 50, 300]); return ( <> <header id="welcome-header"> <motion.div id="welcome-header-content" style={{ scale: scaleText, y: yText }} > <h1>도전할 준비가 되셨나요?</h1> <Link id="cta-link" to="/challenges"> 시작 </Link> </motion.div> <motion.img style={{opacity:opacityCity , y:yCity}} src={cityImg} alt="햇빛이 닿은 도시의 스카이라인" id="city-image" /> <motion.img src={heroImg} alt="망토를 쓴 슈퍼히어로" id="hero-image" style={{opacity:opacityHero , y:yHero}} /> </header> ~ ~ </> ); }
정리
- 프레이머 모션 사용: motion, useScroll, useTransform 훅을 사용하여 스크롤 애니메이션을 설정합니다.
- 스크롤 값 변환: useTransform을 통해 스크롤 값을 애니메이션 값으로 변환하여 패럴랙스 효과를 적용합니다.
- 스타일 속성에 애니메이션 값 할당: style 속성에 변환된 값을 할당하여 애니메이션이 자연스럽게 적용되도록 합니다.
이러한 방식으로 페이지의 각 요소가 다른 속도로 움직이도록 하여 세련된 패럴랙스 효과를 구현할 수 있습니다
댓글 ( 0)
댓글 남기기