상세 컨텐츠

본문 제목

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

관련글 더보기