Skip to content

Commit

Permalink
feat: 애니메이션 motion으로 변경
Browse files Browse the repository at this point in the history
  • Loading branch information
2hanbyeol1 committed Feb 12, 2025
1 parent 893cb58 commit 1d4b739
Show file tree
Hide file tree
Showing 14 changed files with 154 additions and 160 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
<img src="https://img.shields.io/badge/next.js-000000?style=for-the-badge&logo=next.js&logoColor=white"/>
<img src="https://img.shields.io/badge/typescript-3178C6?style=for-the-badge&logo=typescript&logoColor=white"/>
<img src="https://img.shields.io/badge/tailwind-06B6D4?style=for-the-badge&logo=tailwind-css&logoColor=white"/>
<img src="https://img.shields.io/badge/motion-000000?style=for-the-badge&logo=framer&logoColor=white"/>
<img src="https://img.shields.io/badge/zustand-F3DF49?style=for-the-badge&logo=zustand&logoColor=white"/>
<!-- <img src="https://img.shields.io/badge/TANSTACK QUERY-FF4154?style=for-the-badge&logo=react-query&logoColor=white"/> -->

Expand Down
69 changes: 69 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
"lint": "next lint"
},
"dependencies": {
"motion": "^12.4.2",
"next": "15.1.5",
"react": "^19.0.0",
"react-dom": "^19.0.0",
Expand Down
38 changes: 14 additions & 24 deletions src/app/_components/Header/HeaderProgress.tsx
Original file line number Diff line number Diff line change
@@ -1,33 +1,23 @@
'use client';
import useScrollProgress from '@/hooks/useScrollProgress';
import useThrottle from '@/hooks/useThrottle';
import { useEffect } from 'react';
import { motion, useScroll } from 'motion/react';

function HeaderProgress() {
const { progress, handleScroll } = useScrollProgress();
const throttleOnScroll = useThrottle(handleScroll, 30);

useEffect(() => {
handleScroll();
window.addEventListener('scroll', () => {
throttleOnScroll();
});
return () => {
window.removeEventListener('scroll', () => {});
};
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
const { scrollYProgress } = useScroll();

return (
<>
<div
style={{ width: `${progress}%` }}
className="z-10 h-1 bg-primary2 px-2 max-tablet:hidden"
></div>
<div
style={{ height: `${progress}%` }}
className="z-10 w-1 bg-primary2 py-2 tablet:hidden"
></div>
<motion.div
style={{
scaleX: scrollYProgress,
}}
className="z-10 h-1 origin-left bg-primary2 max-tablet:hidden"
></motion.div>
<motion.div
style={{
scaleY: scrollYProgress,
}}
className="z-10 w-1 origin-top bg-primary2 tablet:hidden"
></motion.div>
</>
);
}
Expand Down
33 changes: 21 additions & 12 deletions src/app/_components/SlideInView/SlideInView.tsx
Original file line number Diff line number Diff line change
@@ -1,21 +1,30 @@
'use client';
import useIntersection from '@/hooks/useIntersection';
import { motion } from 'motion/react';
import { ComponentProps } from 'react';

function SlideInView({ children, className = '' }: ComponentProps<'div'>) {
const onIntersect = (entry: IntersectionObserverEntry | undefined) => {
entry?.target.classList.add('animate-opacity');
};

const { setTarget } = useIntersection({
onIntersect,
options: { rootMargin: '0px 0px -10% 0px', threshold: 0 },
});
interface SlideInViewProps {
delay?: number;
}

function SlideInView({
children,
delay = 0,
}: SlideInViewProps & ComponentProps<'div'>) {
return (
<div ref={setTarget} className={`opacity-0 ${className}`}>
<motion.div
initial={{ opacity: 0, translateY: 30 }}
whileInView={{
opacity: 1,
translateY: 0,
transition: {
duration: 1,
delay,
},
}}
viewport={{ once: true, amount: 0.9 }}
>
{children}
</div>
</motion.div>
);
}

Expand Down
3 changes: 2 additions & 1 deletion src/app/_sections/IntroSection/IntroSection.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import SectionWatcher from '../SectionWatcher';
import IntroText from './IntroText';
import WaveIcon from './WaveIcon';

function IntroSection() {
return (
Expand All @@ -8,7 +9,7 @@ function IntroSection() {
className="flex h-screen w-full items-center justify-center bg-gradient-to-b from-primary4 via-primary4 via-70% to-primary4/0"
>
<div className="mx-8 flex w-full max-w-[52rem] items-center justify-between max-tablet:flex-col max-tablet:gap-8">
<div className="origin-bottom-right animate-wiggle text-9xl">👋</div>
<WaveIcon />
<IntroText />
</div>
</SectionWatcher>
Expand Down
15 changes: 15 additions & 0 deletions src/app/_sections/IntroSection/WaveIcon.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
'use client';
import { motion } from 'motion/react';

function WaveIcon() {
return (
<motion.div
initial={{ opacity: 0, scale: 0 }}
animate={{ opacity: 1, scale: 1 }}
transition={{ type: 'spring', visualDuration: 0.6, bounce: 0.6 }}
>
<div className="origin-bottom-right animate-wiggle text-9xl">👋</div>
</motion.div>
);
}
export default WaveIcon;
28 changes: 18 additions & 10 deletions src/app/_sections/SectionWatcher.tsx
Original file line number Diff line number Diff line change
@@ -1,30 +1,38 @@
'use client';
import useIntersection from '@/hooks/useIntersection';
import useActiveIndexStore, {
TSectionId,
} from '@/stores/useActiveSectionIdStore';
import { motion } from 'motion/react';
import { ComponentProps } from 'react';

function SectionWatcher({
sectionId,
children,
className,
}: { sectionId: TSectionId } & ComponentProps<'section'>) {
const setActiveIndex = useActiveIndexStore((state) => state.setActiveIndex);
const activate = useActiveIndexStore((state) => state.activate);
const deactivate = useActiveIndexStore((state) => state.deactivate);

const onIntersect = () => {
setActiveIndex(sectionId);
const onViewportEnter = () => {
console.log(sectionId + 'entered');
activate(sectionId);
};

const { setTarget } = useIntersection({
onIntersect,
options: { rootMargin: '-58% 0px -40%', threshold: 0 },
});
const onViewportLeave = () => {
console.log(sectionId + 'leaved');
deactivate(sectionId);
};

return (
<section id={sectionId} ref={setTarget} className={className}>
<motion.section
id={sectionId}
className={className}
onViewportEnter={onViewportEnter}
onViewportLeave={onViewportLeave}
viewport={{ margin: '0px 0px -10%' }}
>
{children}
</section>
</motion.section>
);
}
export default SectionWatcher;
2 changes: 1 addition & 1 deletion src/app/_sections/SkillsSection/SkillsSection.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ function SkillsSection() {
{SKILL_CONTENTS.map((skill, idx) => (
<SlideInView
key={`ski-${skill.title}`}
className={idx % 2 === 0 ? '' : 'animate-delay'}
delay={idx % 2 === 0 ? 0 : 0.5}
>
<Skill skill={skill} />
</SlideInView>
Expand Down
19 changes: 0 additions & 19 deletions src/app/globals.css
Original file line number Diff line number Diff line change
Expand Up @@ -5,22 +5,3 @@
* {
@apply leading-none text-dark;
}

@keyframes opacity {
from {
opacity: 0;
transform: translateY(30px);
}
to {
opacity: 1;
transform: translateY(0);
}
}

.animate-opacity {
animation: 0.7s opacity ease-in-out forwards;
}

.animate-delay {
animation-delay: 0.5s;
}
50 changes: 0 additions & 50 deletions src/hooks/useIntersection.ts

This file was deleted.

20 changes: 0 additions & 20 deletions src/hooks/useScrollProgress.ts

This file was deleted.

Loading

0 comments on commit 1d4b739

Please sign in to comment.