Hero 섹션 결과물 시연

개요
이번 시간에는 저의 GTA 카피 프로젝트에 사용된 기술들을 분석해볼 텐데요. 먼저 Hero섹션에 사용된 기술들을 먼저 알아보겠습니다. 혹시나 따라 해보고 싶으신 분들은 아래 코드를 다운 받아서 하나씩 만들어가며 설명해보도록 하게요.
자료
코드
깃허브
gta-examples
chugue • Updated Sep 29, 2025
구성
위에 시연된 gif를 보시면 Hero섹션에는 GSAP기술과 CSS Mask옵션으로 쉽게 구현이 가능해요. 그래서 위에 제공된 기본 코드에서 같이 구현해 나갈텐데, 이 블로그에서는 초점을 맞추기위해서 tailwind 설명은 최대한 자제 하도록 할게요. 이번 블로그 구성을 간단히 알려드리자면,
- 마스킹 이미지 설정
- GSAP set 함수를 활용한 초기 상태 설정
- GSAP timeline과 scrollTrigger를 활용한 애니메이션 오케스트레이션
- 원형으로 확장되며 나타나는 ComingSoon 연출
들로 구성되어 있으니 참고해주세요!
1. 마스킹 이미지 설정
일단 기본코드를 받으시고
npm i
로 패키지들을 설치하고 npm run dev
로 프로젝트를 띄워보면 아래와 같은 화면을 만나실 수 있을거에요. 
애니메이션 작업을 할 때도 마찬가지이겠지만, 기본 HTML의 구성은 모두 끝내고 그 다음 부가효과를 입힌다는 생각으로 작업하시면 좋아요. 그래서 화면처럼 기본적으로
HeroSection
에 완성된 화면은 Navbar
와 1,2,3번 이미지
로 보시는 바와 같이 구성되어있어요. 그럼 현재 이 기본 코드에서 어떻게 아래와 같이 자연스럽게 배경이 사라지며 글자가 들어오는 효과를 줄 수가 있을까요?

그럼 기본 배경을 덮을 수 있는
SVG
파일과 CSS의 maskImage
속성을 사용하면 되요. maskImage
를 속성은 이미지를 마스크 레이어로 사용해서 이미지 모양대로 표시할 수 있게 해줘요. 그럼 프로젝트 폴더로 가셔서 public/images 폴더를 확인해보면 big-hero-text.svg
파일이 있을거에요. public/images/big-hero-text.svg

이 이미지를 엄청 확대한 값을 초기상태로 설정해서, 하얀부분 사이로 그림이 보이게 해줄거에요. 그리고 점점 작아지면서 글자만 남아 강조되는 느낌을 주면 되겠죠? 그럼 여기서 GSAP을 사용해서 초기상태를 설정해볼게요. GSAP설치 방법을 모르시면 이 곳을 클릭해서 설치하시고 다시 이어가면 좋을 것 같아요.
프로젝트 폴더의
components/Hero.tsx
파일 상단에 gsap
과 useGSAP
훅을 import 해주면 사용준비는 완료에요. 그리고 gsap은 클라이언트 컴포넌트에서만 사용되니 use client
지시어도 추가해주시구요.
2. gsap.set
함수를 활용한 초기 상태 설정
지금 우리가
gsap.set
함수가 필요한 이유는 마스킹 이미지를 크게 확대한 상태에서 글자 사이로 이미지가 보이게 하고, 점점 줄어들면서 글자만 남게되는 효과를 주기 위한 것이에요. 그래서 첫 상태와 나중 상태가 필요
하죠. 그리고 이 첫 상태를 설정하기 위해 gsap.set
함수를 사용해보도록 할게요. components/Hero.tsx
"use client"; import { useGSAP } from "@gsap/react"; import gsap from "gsap"; import Image from "next/image"; const Hero = () => { useGSAP(() => { gsap.set(".mask-wrapper", { maskPosition: "center 20%", maskSize: "3600% 3600%", maskRepeat: "no-repeat", maskImage: 'url("/images/big-hero-text.svg")', }); }); return ( <section className="hero-section"> <div className="size-full mask-wrapper"> {/* 기존 코드 */}
여기서
useGSAP
훅을 사용하는 이유는 리액트 전용으로 메모리 누수나 스코프 관리가 용이
하기 떄문에 리액트에서는 useGSAP을 사용하는 것이 깔끔해요. 여기서 gsap.set
함수를 사용하면서 두 가지 매개변수를 넣어줘야 되는데요. 첫 번째는 애니메이션을 적용할 대상
이고, 두 번째는 CSS 스타일 객체
에요. - 첫 번쨰 매개변수 :
‘.mask-wrapper’
첫 번째 매개변수에
.
을 붙이면 이는 mask-wrapper라는 클래스를 선택하게 되요. 만약 붙이지 않으면 HTML 태그 이름으로 인식하게 되요. 즉 <mask-wrapper>
라는 태그이름을 찾으려고 할 것이기 때문에 꼭 클래스를 가진 요소를 선택할 때는 .
을 붙여주세요. 아이디를 가진 요소를 선택하려면 앞에 #
을 붙여주면 되요.- 두 번째 매개변수
maskPosition: “center 20%”
maskSize: "3600% 3600%"
maskRepeat: “no-repeat”
maskImage: 'url("/images/big-hero-text.svg")’
CSS속성을 여기서 정의해주면 되는데,
카멜 표기법을 사용
한다는 거 인지해주시면 좋을 것 같아요. 여기서 속성을 하나씩 설명해볼게요.마스크 이미지의 위치를 설정하는 속성이에요. x와 y위치를 지정해줘야 되기 때문에 두 값이 들어가있는 것을 확인할 수 가 있죠. 즉
x는 center
로 잡고 y는 상단 20%
에 위치시키겠다는 뜻이에요. 마스크 이미지의 사이즈의 가로와 세로 사이지를 지정할 수 있죠. 글자 사이로 이미지가 보이게 끔 해야되기 떄문에 확대해서 마스크 이미지가 안보일 때까지 확대해야 했어요.
3600%
가 적당했기 때문에 이렇게 설정한거에요. 만약 백그라운드 이미지 요소보다 마스킹이미지가 작다면 이미지가 반복하게 되는데, 이미지가 작아졌을 때 반복하지 않도록 하기 위해서 필요해요.
maskImage 속성으로 public 폴더 경로를 제외한 에셋의 위치를 넣어주면 되요.
자 이제 초기 상태를 설정했으니 다음은
timeline
함수와 scrollTrigger
를 사용해서 애니메이션을 만들면 되겠네요.3. GSAP timeline과 scrollTrigger를 활용한 애니메이션 오케스트레이션
오케스트레이션이라는 단어를 쓰는게 너무 장황하단 생각을 하지만, 여러 애니메이션을 지휘하듯 다루어야 하기 떄문에 적절한 단어라고 생각이 드네요. 그리고 여기서
timeline
함수를 쓰는 이유는 여러가지 애니메이션을 언제 어디서 어떻게 나타나게 할 지 손쉽게 컨트롤할 수 있기 때문이에요! 즉, 우리가 원하는 것은 스크롤을 내리긴 하지만 그렇다고 Hero 섹션이 스크롤이 되면 안되죠. Hero섹션이 고정된 채로 우리가 원하는 애니메이션이 실행이 되어야 해요. 그럼 아래와 같이 작성해볼까요?
트리거 세팅
그럼 아까 만들었던
gsap.set
아래에 gsap.timeline
을 선언을 해주고 scrollTrigger
객체를 사용할 거에요. 물론 트리거가 타임라인을 사용하는 데 필수적인 것은 아니지만 저희가 만들고 싶은 효과는 HeroSection에서 여러 효과를 주고 싶기 때문에 필요해요. components/Hero.tsx
//import { ScrollTrigger } from "gsap/ScrollTrigger"; // 중간 코드 생략 //gsap.registerPlugin(ScrollTrigger); const Hero = () => { useGSAP(() => { gsap.set(".mask-wrapper", { // 코드 생략 }); const tl = gsap.timeline({ scrollTrigger: { trigger: ".hero-section", start: "top top", end: "+=200%" scrub: 2.5, pin: true, }, }); }); return ( // 기존 코드
먼저 스크롤 트리거를 사용하기 위해서는 scrollTrigger 플러그인을 선언해줘야 해요 하지만 애니메이션이 준비가 되어있지 않으면 어떻게 작동하는 지 확인할 수 없으니 일단 주석처리를 하도록 할게요!
그리고 위 코드처럼
gsap.timeline
함수에는 scrollTrigger 객체를 설정할 수 있는데요. 설정 하나하나씩 알아가볼게요.- 저희가 고정하고 싶은 타겟은
.hero-section
이고 이 요소의 top 부분이 viewport의 top에 도달하면 트리거를 시키므로‘top top'
을 start로 설정해요.
- 그리고 고정한다는 말은 언제까지 고정할 건데란 질문이 따라오겠죠? 그래서 end를 설정해야되는데 현재 타겟 높이의 2배를 스크롤하는 동안 고정시키겠다 라는 의미로
“+=200%”
를 설정했어요.
- scrub이라는 옵션은 true또는 숫자를 넣을 수 있는데, 우리가 마우스를 스크롤하면 바로 화면이 연동되는 것은 가끔 딱딱하게 느껴져요. 그래서 2.5초의 지연시간을 추가하면 부드럽게 스크롤 된다는 느낌이 들기 때문에
2.5초로 설정
했어요.
- 당연히 고정 효과를 사용하기 위해서는 pin이라는 옵션을
true
로 설정해줘야 완성이 되요.
자 이렇게 시작 부분은 설정이 되었네요. 이제 애니메이션을 하나하나씩 추가해보도록 할게요.
타임라인에 애니메이션 추가하기
이제
timeline
을 tl
이라는 변수로 정의했다면, 해당 tl에 메서드 체이닝 방식으로 애니메이션 효과를 붙일 수 있어요. 그럼 마스킹이미지가 작아지는 효과를 바로 붙여보도록 할게요. components/Hero.tsx
// 윗 코드 생략 const tl = gsap.timeline({ scrollTrigger: { trigger: ".hero-section", start: "top top", end: "+=200%", scrub: 2.5, pin: true, }, }); tl.to(".mask-wrapper", { maskPosition: "center 20%", maskSize: "20% 20%", }); }); // 아래 코드 생략
위 코드 처럼
tl.
변수에 to
라는 메서드를 붙일 수 있어요. to
는 기본문법중에 하나로 간단히 설명드리자면 이런 상태로 완성이 되어야 해!
라고 명시해주는 함수라고 생각하면 좋겠네요. 문법은 이곳을 클릭해서 좀 더 자세히 알아보시면 좋을 것 같아요. 그래서 maskPosition을 그대로 유지하고 maskSize를 가로세로 ‘20% 20%’
로 바꿔볼게요. 그리고 애니메이션이 지속되는 시간으로 duration: 2
를 설정해볼게요. 그럼 저장하고 페이지를 새로고침하면 아래와 같이 애니메이션이 작동할거에요.

그럴싸 해보이지만, 뭔가 살짝 부족한 느낌이 많이 들거에요. 좀 더 자연스러워 보이기 위해서 몇 가지 애니메이션을 좀 더 추가해보도록 할게요. 먼저 글자가 작아질 때 글자 사이로 보이는 이미지가 살짝 줄어들면 좀 더 이 애니메이션에 극적인 효과를 줄 수 있어요.
현재 뒷 배경이미지는 tailwind css로
scale-110
이 적용된 상태에요. 마스킹 이미지가 작아질때 이 배경 이미지가 살짝 줄어드는 느낌이 들면 몰입감이 높아지겠죠? 그럼 아래 처럼 코드를 작성해볼 까요? components/Hero.tsx :.scale-out 적용

이미지처럼
hero-bg.webp
컴포넌트에 scale-out
클래스를 추가하고 타임라인 tl변수에 .to(”scale-out”, { scale: 1, ease: “power1.inOut”})
을 기존 마스킹이미지 이전에 추가해볼게요. 그럼 scale-out
요소의 애니메이션을 원래사이즈로 되돌리면서 약간 줄어드는 효과가 적용이 될거에요. 그리고 두 개의 애니메이션이 동시에 진행이 되어야지 몰입감이 있기 때문에 ‘mask-wrapper’ 애니메이션에 ‘<’
을 세번째 인자로 넣어주시면 이전 애니메이션과 동시진행이 가능해요!그리고 마스킹 이미지
(.mask-wrapper)
의 duration:2
은 옵션을 삭제하고 완전히 스크롤에 의해서 통제되도록 상단에 scrollTrigger 플러그인 주석도 해제 한 뒤(활성화), 다시 새로고침 해볼게요. 
이미지가 줄어드는 느낌 때문에 좀 더 몰입감이 생기긴 하지만, 다른 텍스트 이미지는 비율을 유지하고 남아 있기 때문에 완성도 면에서는 조금 떨어지는 상태에요. 마스킹 이미지가 글자를 대체하면서 나타날 것이기 때문에, 기존의 텍스트 이미지는
fade-out
처리하고 하얀글자만 남을수 있도록 하얀 배경의 오버레이를 추가해볼게요!Fade-out처리와 오버레이 추가해서 글자만 남기기
그럼 텍스트 이미지인
hero-text.webp
를 사용하는 컴포넌트에 fade-out
클래스를 추가해줄게요. components/Hero.tsx :.fade-out 적용

그리고
timeline
에서 .scale-out
과 .mask-wrapper
사이에 fade-out
애니메이션을 넣을거에요. 옵션으로는 { scale: 1, ease: ‘power1.inOut”}, ‘<’)
를 추가해서 동시에 작동하면 좋겠죠?components/Hero.tsx :.overlay 적용

오버레이는
.mask-wrapper
를 relative
로 잡고 레이어를 쌓아주는 느낌으로 사용할 거에요. 그래서 div
태그에 overlay
클래스로 애니메이션 타겟으로 사용하구요. tailwind는 size-full
, absolute
, bg-white
, opacity-0
, z-10
로 설정할게요. 그리고
timeline
에는 opacity-0
이였던 오버레이가 서서히 하얀색으로 드러나게 끔 opacity: 1
으로 잡고 ‘<’
옵션으로 모두 같이 동작하면 되겠네요. 완성된 코드는 제일 아래에 둘테니 참고하시면 좋을거 같아요. 그럼 어떻게 적용이 되었나 확인해볼까요? 
이제 깔끔한게 맘에드네요. 그럼 나머지는 ComingSoon이라는 글자가 나타나는 애니메이션만 구현하면 되겠네요.
현재까지 진행된 코드 확인
components/Hero.tsx
"use client"; import { useGSAP } from "@gsap/react"; import gsap from "gsap"; import { ScrollTrigger } from "gsap/ScrollTrigger"; import Image from "next/image"; gsap.registerPlugin(ScrollTrigger); const Hero = () => { useGSAP(() => { gsap.set(".mask-wrapper", { maskPosition: "center 20%", maskSize: "3600% 3600%", maskRepeat: "no-repeat", maskImage: 'url("/images/big-hero-text.svg")', }); const tl = gsap.timeline({ scrollTrigger: { trigger: ".hero-section", start: "top top", end: "+=200%", scrub: 2.5, pin: true, }, }); tl.to(".scale-out ", { scale: 1, ease: "power1.inOut" }) .to(".fade-out", { scale: 1, ease: "power1.inOut" }, "<") .to(".overlay", { opacity: 1, ease: "power1.inOut" }, "<") .to( ".mask-wrapper", { maskPosition: "center 20%", maskSize: "20% 20%", }, "<" ); }); return ( <section className="hero-section"> <div className="size-full mask-wrapper"> <Image src="/images/hero-bg.webp" alt="hero-image" className="size-full object-cover md:scale-110 h-full scale-out" fill /> <Image src="/images/hero-text.webp" alt="hero-text" className="absolute h-full top-0 object-cover md:scale-110 fade-out" fill /> <Image src="/images/watch-trailer.png" alt="watch-trailer" className="absolute w-48 bottom-5 left-1/2 -translate-x-1/2 " width={100} height={100} /> <div className="overlay size-full absolute bg-white opacity-0 z-10" /> </div> </section> ); }; export default Hero;
4.원형으로 나타나는 Coming Soon 연출
제일 위에 첨부된 gif이미지를 보시면, 마지막에 원형으로 Coming May 26th 2026가 드러나는데, 텍스트 이미지는 자연스럽게 통합이 되는 듯한 연출이 되는데요. 아래 제공된 코드를 먼저 복사해서 붙여넣고, 애니메이션을 구현해보도록 할게요.
ComingSoon 컴포넌트 코드
components/ComingSoon.tsx
const ComingSoon = () => { return ( <section className="coming-soon-section"> <div className="flex-col flex gap-20 "> <Image src="/images/logo.webp" alt="logo" className="entrance-logo" width={300} height={300} /> <div className="text-wrapper"> <h3 className="gradient-text"> Coming <br /> May 26 <br /> 2026 </h3> </div> <div className="flex-center gap-10 "> <Image src="/images/ps-logo.svg" className="w-20 " alt="" width={300} height={300} /> <Image src="/images/x-logo.svg" className="w-40 " alt="" width={300} height={300} /> </div> </div> </section> ); }; export default ComingSoon;
ComingSoon 전용 css 코드
이 코드를
app/global.css
에 붙여 넣어주세요!app/global.css
/* 상단 기존 코드 */ .coming-soon-section { @apply absolute inset-0 overflow-hidden z-50 opacity-100 flex justify-center; margin-top: 0vh; mask-image: radial-gradient(circle at 50% 100vh, black 0%, transparent 0%); mask-repeat: no-repeat; mask-size: 100% 100%; .entrance-logo { @apply w-79 h-auto mt-[16.3vh] object-contain self-center; } .gradient-text { @apply text-[8rem] font-round-bold !font-extrabold leading-[7.5rem] inset-0 uppercase text-center text-transparent; letter-spacing: -3px; background-clip: text; background-image: linear-gradient(to right, #fc63b1, #ff6583, #ff8619); } } }
일단 제공된 코드 구성의 핵심을 설명드리자면,
.coming-soon-section
섹션을 기존 완성한 애니메이션 위에 레이어에absolute
를 적용해서 하나 더 쌓을 것이구요. 로고가 스포트 라이트 되면서 서서히 들어나는 효과를 주기위해서mask-image
속성을 활용했어요.
.entrance-logo
에서는 이전 애니메이션의 점점 드러나는 텍스트와 최종 멈추는 지역에서 자연스럽게 겹치게 해야되는데,w-
속성과mt-
속성으로 크기와 위치 미세조정이 필요했어요.
.gradient-text
는background-clip-text
속성을 사용하고 배경에 핑크, 빨강, 주황으로 그라데이션이 표현될 수 있도록 했어요.
그럼 코드를 Hero 컴포넌트에 임포트를 해주세요.
components/Hero.tsx

Hero.tsx 컴포넌트에
mask-wrapper
div요소 아래에 추가해주세요. ComingSoon
으로 레이어를 더 쌓기 위해서에요. 애니메이션 적용 (mask-image를 활용해 스포트라이트 연출)
이제 GSAP으로 애니메이션을 적용해주면 되겠죠? 그럼
.coming-soon-section
CSS를 먼저 한 번 살펴볼까요? coming-soon-section 선택자의 CSS와 GSAP 코드

스포트라이트가 원형으로 나타나서 글씨를 드러내는 효과를 위해서 CSS로
mask-image
속성으로 초기값을 지정했어요. 여기서 radial—gradient
는 원형 그라데이션을 50%
0vh
지정했는데요. 이는 원의 중심이 뷰포트 최하단에 위치시켜라
가 되기 때문에, 보이지는 않지만 반원의 그라데이션이 자리잡고 있었을거에요. 그리고 mask-image에서는 black 속성이 보여지는 영역
이 되기 때문에, 현재의 black 0%
로 아무것도 보이지 않게 초기값을 설정한 것이죠. 그래서
GSAP
의 to
메소드로 완료상태를 정의해 주었는데요. 보이는 영역 black 50%
를 가진 원형 그라데이션이 중심이 화면 최상단50% 0vh
에 위치시켜라가 되는 것인데요. 그럼 스크롤에 따라 스포트라이트가 아래에서 부터 위로 조명을 비추는 것 처럼 글자가 드러나는 효과를 줄 수가 있어요. components/Hero.tsx
// 상단 기존 코드 .to( ".mask-wrapper", { maskPosition: "center 20%", maskSize: "18% 18%", }, "<" ) .to(".coming-soon-section", { duration: 2, maskImage: "radial-gradient(circle at 50% 0vh, black 50%, transparent 100%)", ease: "power1.inOut", }) // 하단 기존 코드
그럼 이 애니메이션을 이전 애니메이션이 끝나는 시점에 위 코드 블록 처럼 붙여주면 자연스럽게 연결이 되겠죠?
마지막 fade-out 애니메이션 (다중 객체 애니메이션 적용하기)
그리고 다음 섹션으로 넘어가기 전에 진행된 애니메이션을 모드 가려주는 애니메이션이 원래 존재 했는데요. 그냥 모든게 사라지는 효과를 넣을때, 여러 타겟을 선택하는 문법을 사용해서
opacity: 0
을 지정해주면 한 번에 처리 할 수 있어요. components/Hero.tsx
// 상단 기존 코드 .to(".coming-soon-section", { duration: 2, maskImage: "radial-gradient(circle at 50% 0vh, black 50%, transparent 100%)", ease: "power1.inOut", }) .to(".coming-soon-section, .mask-wrapper", { opacity: 0, duration: 2, ease: "power1.inOut", }); // 하단 기존 코드
한 번에 여러 요소를 선택하기 위해서는 타겟을 넣은 곳에
“.coming-soon-section, .mask-wrapper”
처럼 쉼표를 사용해서 두 개의 클래스를 넣어주면 동시에 효과를 적용할 수 있어요. 그럼 마지막 결과를 한 번 보실까요? 
이제 이번 블로그의 결과가 완성이 되었네요! 깔끔합니다.
결론
정리해보자면, 이번 Hero섹션의 주로 사용했던 GSAP 기술은
timeline
이라고 할 수 있겠는데요. 그리고 CSS의 gradient
또한 주요하게 사용된 섹션이라고 생각합니다. GSAP의 여러가지 메소드들이 많이 있는데, 그것을 따로 공부하기 보다는 이렇게 유명한 사이트를 공식문서만 참고하고 카피하는 연습을 해보면 실력이 많이 늘지 않을까 생각합니다. 이상 글 읽어주셔서 감사합니다!