상세 컨텐츠

본문 제목

React(리액트) 티키타카 32 (motion) 애니메이션

React

by e7e 2025. 3. 26. 09:13

본문

웹 프로그램을 하다 보면 , 당근 빼 놓을 수 없는게 자잘한 애니메이션이당.

개발자의 더 큰 재미를 위해서도, 고객의 더 큰 만족을 위해서도...

마치 인생에 재미가 필요하듯, 화면엔 오버스럽지 않은 애니메이션이 필요하당.

 

이걸 편하게 해주는 모듈이 바로 motion이당. 아래 링크에 잠시 방문하고 오장. 

https://motion.dev/docs

 

여기 설명은 당연 그 뿌리를 위 링크에 두고 있당. 긴 말은 사치당. 달려보장

 

먼저 vite로 react / javascript 프로젝트를 아무폴더에나 만들었다 치장.

 

추가 필요 모듈 설치 ( tailwindcss도 설치해서 자주 써 보도록 하장. 정말 좋당)

npm i   motion          
npm i  tailwindcss   @tailwindcss/vite

 

tailwindcss 설정법을 안드로메다에 두고 다니는 사람들이 있어 여기 적는당.

vite.config.js

import { defineConfig } from "vite";
import react from "@vitejs/plugin-react";
import tailwindcss from "@tailwindcss/vite";
// https://vite.dev/config/
export default defineConfig({
plugins: [react(), tailwindcss()],
});

 

index.css

@import "tailwindcss";

만약 vscode에서 Tailwind CSS IntelliSense 플러그인이 잘 동작하지 않는다면

플러그인을  Disable -> Restart Extensions 클릭 -> Enable 하거낭

vscode를 재 구동 시키도록 하장. (요건 확실히 쪼메 마니 불편한 부분이당.)

 

 

본론으로 들어가장. 아래 틀을 눈으로 흡수하여 머리에서 소화시키장.

자주 사용되는 기본 틀

initial={{ rotate: 0, scale: 0, color: "gray" }}
animate={{
rotate: 360,
scale: 1,
color: "white",
transition: { duration: 1 },
}}
whileHover={{ scale: 1.2 }}
whileTap={{ scale: 0.9 }}

 

소화시킨 내용을 실행되는 모습과 매칭 시키장.(요게 기본적용)

BasicMotion.jsx

// eslint-disable-next-line no-unused-vars
import { motion } from "motion/react";
function BasicMotion() {
return (
<motion.div
className="w-20 h-15 bg-pink-400 rounded-b-lg justify-center text-2xl flex items-center "
initial={{ rotate: 0, scale: 0, color: "gray" }}
animate={{
rotate: 360,
scale: 1,
color: "white",
transition: { duration: 1 },
}}
whileHover={{ scale: 1.2 }}
whileTap={{ scale: 0.9 }}
onHoverStart={() => {
console.log("Hover Started");
}}
>
호산나
</motion.div>
);
}
export default BasicMotion;

 

아래 소스에선 layout이란 글자가 핵심이당. (일단 담아두장)

ToggleButton.jsx

// eslint-disable-next-line no-unused-vars
import { motion } from "motion/react";
import { useState } from "react";
function ToggleButton({ title }) {
const [isOn, setIsOn] = useState(true);
const toggleSwitch = () => setIsOn(!isOn);
return (
<button
className="flex p-0 cursor-pointer w-[100px] h-[50px] bg-[#cba0ee] rounded-full"
style={{
justifyContent: "flex-" + (isOn ? "start" : "end"),
}}
onClick={toggleSwitch}
>
<motion.div
className="w-[50px] h-[50px] bg-[#9911ff] rounded-full text-xs flex justify-center items-center text-white font-extrabold"
layout
transition={{
type: "spring",
duration: 0.5,
bounce: 0.8,
}}
>
{title}
</motion.div>
</button>
);
}
export default ToggleButton;

 

아래에선 layoutId <AnimatePresence> 가 핵심이당.

TabAni.jsx

// eslint-disable-next-line no-unused-vars
import { AnimatePresence, motion } from "motion/react";
import { useState } from "react";
const sample = [
{ icon: "🎃", label: "호박" },
{ icon: "❤", label: "만남" },
{ icon: "📆", label: "달력" },
];
const [hobak, love, cal] = sample;
const tabs = [hobak, love, cal];
function TabAni() {
const [selectedTab, setSelectedTab] = useState(tabs[0]);
return (
<div className="w-[480px] h-[60vh] max-h-[360px] rounded-3xl bg-violet-400 overflow-hidden flex flex-col shadow-lg shadow-blue-400">
<nav className="bg-[#e15d24] p-0 pt-5 pb-5 rounded rounded-br-none rounded-bl-none border-b-[#e5f6b6] border-b-1">
<ul className="list-none px-2 m-0 flex w-full">
{tabs.map((item) => (
<motion.li
key={item.label}
initial={false}
animate={{
backgroundColor:
item === selectedTab ? "#F3EE66FF" : "#B8490D00",
}}
className="select-none flex justify-between items-center list-none px-10 mx-0 font-bold text-sm rounded-md w-full relative "
onClick={() => setSelectedTab(item)}
>
{`${item.icon} ${item.label}`}
{item === selectedTab ? (
<motion.div
className="absolute bottom-[-2] l-0 r-0 h-[3px]"
layoutId="underline"
id="underline"
/>
) : null}
</motion.li>
))}
</ul>
</nav>
<main className="flex justify-center items-center flex-1">
<AnimatePresence mode="wait">
<motion.div
key={selectedTab ? selectedTab.label : "empty"}
initial={{ y: 15, opacity: 0 }}
animate={{ y: 0, opacity: 1 }}
exit={{ y: -10, opacity: 0 }}
transition={{ duration: 0.3 }}
className="text-9xl"
>
{selectedTab ? selectedTab.icon : "😋"}
</motion.div>
</AnimatePresence>
</main>
</div>
);
}
export default TabAni;

 

모두 적용하여 보장.

App.jsx

import BasicMotion from "./BasicMotion";
import TabAni from "./TabAni";
import ToggleButton from "./ToggleButton";
function App() {
return (
<div className="flex flex-row justify-center items-center mt-5">
<div className="mr-8">
<h1 className="text-3xl bg-slate-600 text-white rounded-2xl px-5 py-5 mt-5 mb-5">
쭈니 Motion 띠이작
</h1>
<div className="flex justify-center items-center mb-5">
<BasicMotion />
</div>
<div className="flex flex-col gap-4 flex-1">
<ToggleButton title={"지혀니"} />
<ToggleButton title={"영시니"} />
<ToggleButton title={"세이니"} />
<ToggleButton title={"누구니"} />
</div>
</div>
<div className="flex justify-center items-center mt-30">
<TabAni />
</div>
</div>
);
}
export default App;

결과를 잘 확인하여... 꼬옥 소스와 연결시켜 이해하장.

 

 

 

 


 

제목이 무제라서 무죄일 수 없듯이

 

의도가 없었다 해서 무죄일 수 Up 뜻이

 

제 목이 사슴목이니 무죄라 우기는 건 우~간다당

 

무제로 무죄를 주장하고 싶은 

 

그 마음만은 크게  와 닿는다.

 

난 특별한 의도가  없었지만

 

금수저는 거기에 특별히 의도를 넣었다.

 

내 잘못이다.

 

 

https://www.youtube.com/watch?v=9kaCAbIXuyg

관련글 더보기