
강의 목표
CSS Animation에 대해 학습하고 실습을 진행합니다.
강의 내용 정리
강의시간에 학습한 내용을 정리합니다.
실습 - Tailwind 이동 애니메이션
구현 팁
- 애니메이션에 해당하는 svg코드를 찾기
- svg요소를 회전시킴
- 회전은 tailwind spin animation을 사용
내 접근방식 및 코드
- hero highlight 애니메이션의 구성부터 살펴봄 → 선으로 된 두개의 원이 시계/반시계로 회전하는 방식
- svg태그는 복붙하여 사용하고 컨테이너에 담은다음 애니메이션을 설정하기로 함
HeroHighlightAnimation.tsx
function HighlightImage() {
return (
<>
<svg
viewBox="0 0 1026 1026"
fill="none"
aria-hidden="true"
className="absolute inset-0 h-full w-full animate-spin-slow"
>
<path
d="M1025 513c0 282.77-229.23 512-512 512S1 795.77 1 513 230.23 1 513 1s512 229.23 512 512Z"
stroke="#D4D4D4"
strokeOpacity="0.7"
></path>
<path
d="M513 1025C230.23 1025 1 795.77 1 513"
stroke="url(#:S1:-gradient-1)"
strokeLinecap="round"
></path>
<defs>
<linearGradient
id=":S1:-gradient-1"
x1="1"
y1="513"
x2="1"
y2="1025"
gradientUnits="userSpaceOnUse"
>
<stop stop-color="#06b6d4"></stop>
<stop offset="1" stopColor="#06b6d4" stopOpacity="0"></stop>
</linearGradient>
</defs>
</svg>
</>
);
}
function HighlightImageReverse() {
return (
<>
<svg
viewBox="0 0 1026 1026"
fill="none"
aria-hidden="true"
className="absolute inset-0 h-full w-full animate-spin-reverse-slower"
>
<path
d="M913 513c0 220.914-179.086 400-400 400S113 733.914 113 513s179.086-400 400-400 400 179.086 400 400Z"
stroke="#D4D4D4"
strokeOpacity="0.7"
></path>
<path
d="M913 513c0 220.914-179.086 400-400 400"
stroke="url(#:S1:-gradient-2)"
strokeLinecap="round"
></path>
<defs>
<linearGradient
id=":S1:-gradient-2"
x1="913"
y1="513"
x2="913"
y2="913"
gradientUnits="userSpaceOnUse"
>
<stop stop-color="#06b6d4"></stop>
<stop offset="1" stopColor="#06b6d4" stopOpacity="0"></stop>
</linearGradient>
</defs>
</svg>
</>
);
}
function HighlightImageContainer() {
return (
<>
<div className="absolute left-1/2 top-4 h-[1026px] w-[1026px] -translate-x-1/3 stroke-gray-300/70 [mask-image:linear-gradient(to_bottom,white_20%,transparent_75%)] sm:top-16 sm:-translate-x-1/2 lg:ml-12 xl:ml-0">
<HighlightImage />
<HighlightImageReverse />
</div>
</>
);
}
const HeroHighlightAnimation = () => {
return (
<>
<HighlightImageContainer />
</>
);
};
export default HeroHighlightAnimation;
tailwind.config.ts
import type { Config } from "tailwindcss";
import { nextui } from "@nextui-org/react";
const config: Config = {
content: [
"./pages/**/*.{js,ts,jsx,tsx,mdx}",
"./components/**/*.{js,ts,jsx,tsx,mdx}",
"./app/**/*.{js,ts,jsx,tsx,mdx}",
"./node_modules/@nextui-org/theme/dist/**/*.{js,ts,jsx,tsx}",
],
theme: {
extend: {
colors: {
codeit_purple: "#6500c3",
},
animation: {
"spin-reverse-slower": "spin-reverse 6s linear infinite",
"spin-slow": "spin 4s linear infinite",
},
keyframes: {
"spin": {
to: {
transform: "rotate(1turn)",
},
},
"spin-reverse": {
to: {
transform: "rotate(-1turn)",
},
},
},
},
},
darkMode: "class",
plugins: [nextui()],
};
export default config;
정답 코드
function AnimateSpin() {
return (
<svg
viewBox="0 0 558 558"
width="558"
height="558"
fill="none"
aria-hidden="true"
className="animate-spin"
>
<defs>
<linearGradient
id=":Rddbqnla:"
x1="79"
y1="16"
x2="105"
y2="237"
gradientUnits="userSpaceOnUse"
>
<stop stop-color="#13B5C8"></stop>
<stop offset="1" stop-color="#13B5C8" stop-opacity="0"></stop>
</linearGradient>
</defs>
<path
opacity=".2"
d="M1 279C1 125.465 125.465 1 279 1s278 124.465 278 278-124.465 278-278 278S1 432.535 1 279Z"
stroke="#13B5C8"
></path>
<path
d="M1 279C1 125.465 125.465 1 279 1"
stroke="url(#:Rddbqnla:)"
stroke-linecap="round"
></path>
</svg>
);
}
export default AnimateSpin;
실습 - 스크롤 애니메이션
목표
https://codesandbox.io/p/sandbox/popup-modal-ycouuu?file=%2Fsrc%2FApp.tsx
codesandbox.io
구현 팁
- 100vh로 높이를 설정하여 여러개의 페이지를 생성
- react-intersection-observer를 설치
- 요소를 감지하면 log를 찍는 방식으로 일단 확인
- 이후 log를 찍는 로직을 애니메이션 로직으로 변경하여 완성
내 코드
"use client";
import { useState } from "react";
import { useInView } from "react-intersection-observer";
const Page = ({ title }: { title: string }) => {
const { ref, inView } = useInView({ triggerOnce: true, threshold: 0.5 });
const [isVisible, setIsVisible] = useState(false);
if (inView && !isVisible) {
setIsVisible(true);
}
return (
<div
ref={ref}
className={`h-[100vh] flex flex-col justify-center items-center mt-2 w-full transition-all duration-300 ${
isVisible ? "opacity-100 translate-y-0" : "opacity-0 translate-y-10"
}`}
>
<h2 className="text-[5vw] font-bold">{title}</h2>
</div>
);
};
const ScrollAnimation = () => {
const PAGE_TITLES = ["Don't", "you", "just", "hate", "popups?"];
return (
<>
{PAGE_TITLES.map((title, index) => (
<Page key={index} title={title} />
))}
</>
);
};
export default ScrollAnimation;
정답 코드
import { useInView } from "react-intersection-observer";
function Title({ children }: { children: React.ReactNode }) {
const { ref, inView } = useInView({
triggerOnce: true,
rootMargin: '-100px 0px',
});
const styleClass = inView ? "opacity-100 translate-y-0" : "opacity-0 translate-y-20"
return (
<div
ref={ref}
className={`transition-all ${styleClass}`}
>
<h2 className="text-6xl ">{children}</h2>
</div>
);
}
function Page({ children }: { children: React.ReactNode }) {
return (
<div className="flex justify-center items-center h-screen">{children}</div>
);
}
const PAGE_TITLES = ["Don't", "you", "just", "hate", "popups?"];
function ReactObserver() {
return (
<div className="">
{PAGE_TITLES.map((title) => (
<Page key={title}>
<Title>{title}</Title>
</Page>
))}
</div>
);
}
export default ReactObserver;
실습 - 페이지 스크롤 인디케이터
목표
https://codesandbox.io/p/sandbox/framer-motion-usescroll-xwdxbt?from-embed=
codesandbox.io
내 접근방식 및 코드
"use client";
import { useEffect, useState } from "react";
import { useSpring, animated } from "react-spring";
const LoremIpsum = () => {
return (
<>
<article>
<p>
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aliquam ac
rhoncus quam.
</p>
<p>
Fringilla quam urna. Cras turpis elit, euismod eget ligula quis,
imperdiet sagittis justo. In viverra fermentum ex ac vestibulum.
Aliquam eleifend nunc a luctus porta. Mauris laoreet augue ut felis
blandit, at iaculis odio ultrices. Nulla facilisi. Vestibulum cursus
ipsum tellus, eu tincidunt neque tincidunt a.
</p>
<h2>Sub-header</h2>
<p>
In eget sodales arcu, consectetur efficitur metus. Duis efficitur
tincidunt odio, sit amet laoreet massa fringilla eu.
</p>
<p>
Pellentesque id lacus pulvinar elit pulvinar pretium ac non urna.
Mauris id mauris vel arcu commodo venenatis. Aliquam eu risus arcu.
Proin sit amet lacus mollis, semper massa ut, rutrum mi.
</p>
<p>Sed sem nisi, luctus consequat ligula in, congue sodales nisl.</p>
<p>
Vestibulum bibendum at erat sit amet pulvinar. Pellentesque pharetra
leo vitae tristique rutrum. Donec ut volutpat ante, ut suscipit leo.
</p>
<h2>Sub-header</h2>
<p>
Maecenas quis elementum nulla, in lacinia nisl. Ut rutrum fringilla
aliquet. Pellentesque auctor vehicula malesuada. Aliquam id feugiat
sem, sit amet tempor nulla. Quisque fermentum felis faucibus, vehicula
metus ac, interdum nibh. Curabitur vitae convallis ligula. Integer ac
enim vel felis pharetra laoreet. Interdum et malesuada fames ac ante
ipsum primis in faucibus. Pellentesque hendrerit ac augue quis
pretium.
</p>
<p>
Morbi ut scelerisque nibh. Integer auctor, massa non dictum tristique,
elit metus efficitur elit, ac pretium sapien nisl nec ante. In et ex
ultricies, mollis mi in, euismod dolor.
</p>
<p>Quisque convallis ligula non magna efficitur tincidunt.</p>
<p>
Pellentesque id lacus pulvinar elit pulvinar pretium ac non urna.
Mauris id mauris vel arcu commodo venenatis. Aliquam eu risus arcu.
Proin sit amet lacus mollis, semper massa ut, rutrum mi.
</p>
<p>Sed sem nisi, luctus consequat ligula in, congue sodales nisl.</p>
<p>
Vestibulum bibendum at erat sit amet pulvinar. Pellentesque pharetra
leo vitae tristique rutrum. Donec ut volutpat ante, ut suscipit leo.
</p>
<h2>Sub-header</h2>
<p>
Maecenas quis elementum nulla, in lacinia nisl. Ut rutrum fringilla
aliquet. Pellentesque auctor vehicula malesuada. Aliquam id feugiat
sem, sit amet tempor nulla. Quisque fermentum felis faucibus, vehicula
metus ac, interdum nibh. Curabitur vitae convallis ligula. Integer ac
enim vel felis pharetra laoreet. Interdum et malesuada fames ac ante
ipsum primis in faucibus. Pellentesque hendrerit ac augue quis
pretium.
</p>
<p>
Morbi ut scelerisque nibh. Integer auctor, massa non dictum tristique,
elit metus efficitur elit, ac pretium sapien nisl nec ante. In et ex
ultricies, mollis mi in, euismod dolor.
</p>
<p>Quisque convallis ligula non magna efficitur tincidunt.</p>
</article>
</>
);
};
const PageScrollIndicator = () => {
const [scrollProgress, setScrollProgress] = useState(0);
const handleScroll = () => {
const scrollTop = window.scrollY;
const docHeight =
document.documentElement.scrollHeight - window.innerHeight;
const progress = (scrollTop / docHeight) * 100;
setScrollProgress(progress);
};
useEffect(() => {
window.addEventListener("scroll", handleScroll);
return () => window.removeEventListener("scroll", handleScroll);
}, []);
const springProps = useSpring({
width: `${scrollProgress}%`,
config: { tension: 220, friction: 20 },
});
return (
<>
<div className="fixed top-0 left-0 w-full h-1 bg-gray-200 z-50">
<animated.div style={springProps} className="h-full bg-blue-500" />
</div>
<div>
<LoremIpsum />
<LoremIpsum />
<LoremIpsum />
<LoremIpsum />
<LoremIpsum />
</div>
</>
);
};
export default PageScrollIndicator;
정답 코드
import { useScroll, animated } from "@react-spring/web";
function LoremIpsum() {
return (
<div>
<article>
<p>
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aliquam ac
rhoncus quam.
</p>
<p>
Fringilla quam urna. Cras turpis elit, euismod eget ligula quis,
imperdiet sagittis justo. In viverra fermentum ex ac vestibulum.
Aliquam eleifend nunc a luctus porta. Mauris laoreet augue ut felis
blandit, at iaculis odio ultrices. Nulla facilisi. Vestibulum cursus
ipsum tellus, eu tincidunt neque tincidunt a.
</p>
<h2>Sub-header</h2>
<p>
In eget sodales arcu, consectetur efficitur metus. Duis efficitur
tincidunt odio, sit amet laoreet massa fringilla eu.
</p>
<p>
Pellentesque id lacus pulvinar elit pulvinar pretium ac non urna.
Mauris id mauris vel arcu commodo venenatis. Aliquam eu risus arcu.
Proin sit amet lacus mollis, semper massa ut, rutrum mi.
</p>
<p>Sed sem nisi, luctus consequat ligula in, congue sodales nisl.</p>
<p>
Vestibulum bibendum at erat sit amet pulvinar. Pellentesque pharetra
leo vitae tristique rutrum. Donec ut volutpat ante, ut suscipit leo.
</p>
<h2>Sub-header</h2>
<p>
Maecenas quis elementum nulla, in lacinia nisl. Ut rutrum fringilla
aliquet. Pellentesque auctor vehicula malesuada. Aliquam id feugiat
sem, sit amet tempor nulla. Quisque fermentum felis faucibus, vehicula
metus ac, interdum nibh. Curabitur vitae convallis ligula. Integer ac
enim vel felis pharetra laoreet. Interdum et malesuada fames ac ante
ipsum primis in faucibus. Pellentesque hendrerit ac augue quis
pretium.
</p>
<p>
Morbi ut scelerisque nibh. Integer auctor, massa non dictum tristique,
elit metus efficitur elit, ac pretium sapien nisl nec ante. In et ex
ultricies, mollis mi in, euismod dolor.
</p>
<p>Quisque convallis ligula non magna efficitur tincidunt.</p>
<p>
Pellentesque id lacus pulvinar elit pulvinar pretium ac non urna.
Mauris id mauris vel arcu commodo venenatis. Aliquam eu risus arcu.
Proin sit amet lacus mollis, semper massa ut, rutrum mi.
</p>
<p>Sed sem nisi, luctus consequat ligula in, congue sodales nisl.</p>
<p>
Vestibulum bibendum at erat sit amet pulvinar. Pellentesque pharetra
leo vitae tristique rutrum. Donec ut volutpat ante, ut suscipit leo.
</p>
<h2>Sub-header</h2>
<p>
Maecenas quis elementum nulla, in lacinia nisl. Ut rutrum fringilla
aliquet. Pellentesque auctor vehicula malesuada. Aliquam id feugiat
sem, sit amet tempor nulla. Quisque fermentum felis faucibus, vehicula
metus ac, interdum nibh. Curabitur vitae convallis ligula. Integer ac
enim vel felis pharetra laoreet. Interdum et malesuada fames ac ante
ipsum primis in faucibus. Pellentesque hendrerit ac augue quis
pretium.
</p>
<p>
Morbi ut scelerisque nibh. Integer auctor, massa non dictum tristique,
elit metus efficitur elit, ac pretium sapien nisl nec ante. In et ex
ultricies, mollis mi in, euismod dolor.
</p>
<p>Quisque convallis ligula non magna efficitur tincidunt.</p>
</article>
</div>
);
}
function ScrollIndicator() {
const { scrollYProgress } = useScroll({ config: { duration: 20 , easing: num => num} });
return (
<div>
<animated.div
className="progress-bar"
style={{ scaleX: scrollYProgress }}
/>
<h1>
<code>useScroll</code> demo
</h1>
<LoremIpsum />
</div>
);
}
export default ScrollIndicator;
반응형
'교육 > 코드잇 스프린트 : 단기심화 5기' 카테고리의 다른 글
| [ 코드잇 스프린트 ] 교육기간 6일차 TIL (3) | 2024.11.15 |
|---|---|
| [ 코드잇 스프린트 ] 교육기간 5일차 TIL (1) | 2024.11.14 |
| [ 코드잇 스프린트 ] 교육기간 3일차 TIL (0) | 2024.11.12 |
| [ 코드잇 스프린트 ] 교육기간 2일차 TIL (0) | 2024.11.08 |
| [ 코드잇 스프린트 ] 교육기간 1일차 TIL (1) | 2024.11.07 |
