GODRICH

히어로 섹션 디자인 패턴 9종 — JS 라이브러리 없이

히어로 섹션 디자인은 사이트를 열자마자 나타나는 첫 화면의 구도를 짜는 일입니다. 여기 아홉 가지는 스플릿·센터 스택·글래스 카드·슬라이더 같은 배치 패턴을 HTML 과 CSS 만으로 짜 둔 것이라, JS 라이브러리 없이 붙여 넣으면 그대로 서는 첫 화면이 됩니다.

자동 재생 · 눌러서 해당 항목으로 · 전부 한 zip

아홉 개를 놓은 순서는 유행이 아니라 방문자가 첫 화면에서 겪는 순서입니다. 눈이 먼저 가는 좌우 갈림(01 스플릿)과 한가운데 쌓임(02 센터 스택)으로 시작해, 문구 한 줄로 승부하는 구도(03)까지가 글자가 주인공인 셋입니다. 다음 셋은 바닥과 소품 차례입니다 — 사진 한 장 없이 색 덩어리와 유리로 배경을 만들고(04), 제품을 확대하며 기능을 짚고(05), 숫자 세 칸으로 신뢰를 세웁니다(06). 마지막 셋은 첫 화면이 다음으로 넘어가는 방식입니다. 스크롤과 함께 접히며 본문으로 이어지고(07), 여러 제품을 같은 칸에서 번갈아 보여 주고(08), 로고 하나만 남겨 기다리게 합니다(09). 아홉 개는 잉크 #17141a · 크림 #fdf7ec · 바이올렛 #5b4bd6 · 옐로 #ffd23f 값을 함께 쓰기에 두세 개를 한 페이지에 섞어 놓아도 색이 싸우지 않습니다. 이웃 글 히어로 섹션 애니메이션 9종이 첫 화면 부품의 움직임을 다뤘다면 이번 편은 화면 구도, 곧 무엇을 어디에 놓을지가 주제입니다.

01스플릿 제품 히어로

왼쪽에 머리말·제목·버튼 둘, 오른쪽에 제품 도형 세 장이 층으로 서고, 세 장이 0.12초 간격으로 아래에서 올라와 자리 잡은 뒤 맨 아래 스크롤 안내가 계속 튑니다. 층이 올라오는 거리는 --rise 변수 하나(22px)에 두었고 좁은 화면에서는 14px 로 줄어듭니다. 제품 소개 랜딩의 첫 화면에 씁니다.

translateYanimation-fill-mode:focus-visible
// animation 단축은 delay 를 0 으로 리셋하므로 낱개 속성으로 쓴다.
// 지연이 걸린 층은 animation-fill-mode: backwards 가 없으면 0프레임에 완성 상태가 한 번 번쩍인다
.hs.is-demo .hs__card {
  animation-name: hsRise;
  animation-duration: $duration;
  animation-timing-function: $easing;
  animation-iteration-count: infinite;
  animation-fill-mode: backwards;
}
.hs.is-demo .hs__card--b { animation-delay: .12s; }
.hs.is-demo .hs__card--a { animation-delay: .24s; }

@keyframes hsRise {
  0%        { transform: translateY(var(--rise)); }
  18%, 60%  { transform: translateY(0); }
  88%, 100% { transform: translateY(var(--rise)); }
}

02센터 스택 배지 히어로

한가운데 한 축에 배지·제목·설명·버튼 쌍·신뢰 표시 넷이 쌓이는 구도입니다. 배지 위로 빛줄기가 한 번 지나가고(70% 지점까지), 신뢰 표시 넷이 0.14초 간격으로 7px 들렸다 앉습니다. 출시 안내나 가입 유도 랜딩의 첫 화면에 씁니다.

translateXanimation-delay:focus-visible
.hb__sheen {
  position: absolute;
  left: 0;
  top: 0;
  width: 40%;
  height: 100%;
  background: linear-gradient(100deg, rgba(255, 255, 255, 0) 0%, rgba(255, 255, 255, .62) 50%, rgba(255, 255, 255, 0) 100%);
  transform: translateX(-160%);
}

// 빛줄기는 알약 밖(오른쪽)에서 멈춰 있다가 다음 바퀴에 왼쪽으로 되돌아간다 —
// overflow: hidden 이 닫고 있으므로 되감기가 화면에 보이지 않는다
@keyframes hbSheen {
  0%        { transform: translateX(-160%); }
  70%, 100% { transform: translateX(320%); }
}

03단어별 등장 헤드라인

제목이 통째로 나타나지 않고 낱말 하나씩, 각 낱말의 상자 안에서 아래로부터 올라옵니다. 가장 중요한 낱말 뒤에서는 형광 띠가 왼쪽부터 차올라 그 낱말 뒤를 채우는데, 띠의 기본값은 다 찬 상태라 애니메이션이 꺼진 화면에서도 강조가 남습니다. 한 문장으로 승부하는 브랜드 첫 화면에 씁니다.

scaleXtransform-originanimation-fill-mode
// 낱말 하나가 제 상자 안에서 올라온다 — 상자가 넘치는 부분을 잘라 줘야
// "아래에서 솟는" 것으로 보인다. 아랫여백은 받침이 잘리지 않게 4px 만 벌렸다 되접는다
.hk__word {
  position: relative;
  z-index: 1;
  overflow: hidden;
  display: inline-block;
  padding-bottom: $sp-1;
  margin-bottom: -$sp-1;
  vertical-align: bottom;
}
.hk__wi { display: inline-block; }

// 띠는 낱말이 다 선 뒤(0.55s)에 차올랐다가 낱말이 내려가기 전에 물러난다
@keyframes hkBand {
  0%        { transform: scaleX(0); }
  12%, 50%  { transform: scaleX(1); }
  60%, 100% { transform: scaleX(0); }
}

04그라디언트 글래스 히어로

사진 한 장 없이 색 덩어리 세 개가 서로 다른 위상으로 느리게 흐르는 바닥 위에 반투명 유리 카드가 떠 있고, 빛이 카드 표면을 대각선으로 한 번 쓸고 지나갑니다. 덩어리는 blur 필터가 아니라 radial-gradient 라 가장자리가 이미 부드럽고, 배경이 화면 전체를 움직인 덕에 누적면적이 34.261% 로 아홉 중 가장 넓습니다. 이미지 소재가 없는 서비스 소개 첫 화면에 씁니다.

backdrop-filterradial-gradientclip-path
.hg__blob--a {
  left: -64px;
  top: -104px;
  background: radial-gradient(circle at 50% 50%, $color, rgba($color, 0) 70%);
}

// 유리 카드 — 뒤의 덩어리를 backdrop-filter 로 흐려 받는다
.hg__card {
  position: relative;
  z-index: 1;
  box-sizing: border-box;
  width: 100%;
  padding: clamp(12px, 3.2vw, 20px);
  border: 1px solid rgba(255, 255, 255, .28);
  border-radius: $r-card;
  background: rgba(255, 255, 255, .10);
  backdrop-filter: blur(12px) saturate(140%);
  -webkit-backdrop-filter: blur(12px) saturate(140%);
}

05제품 확대 호출점 히어로

제품 그림이 루프 내내 천천히 커졌다 제자리로 돌아오는 동안(52% 지점에 scale 1.12) 호출점 둘이 부풀었다 터지고, 터진 자리에 설명 쪽지가 한 프레임에 딱 바뀌어 나타납니다. 쪽지를 반투명하게 띄우지 않고 steps(1, end) 컷으로 잘랐기에 유령 장이 없습니다. 기능을 짚어 보여 주는 제품 상세 첫 화면에 씁니다.

scalesteps(1, end)transform-origin
// 쪽지는 반투명 유령을 만들지 않는다 — steps(1, end) 로 한 프레임에 컷
.hz.is-demo .hz__note {
  visibility: visible;
  animation-duration: $duration;
  animation-timing-function: steps(1, end);
  animation-iteration-count: infinite;
  transition: none;
}

@keyframes hzZoom {
  0%   { transform: scale(1); }
  52%  { transform: scale(1.12); }
  88%, 100% { transform: scale(1); }
}

@keyframes hzBurst1 {
  0%, 4%    { transform: scale(1);   opacity: .9; }
  26%       { transform: scale(2.8); opacity: 0; }
  100%      { transform: scale(2.8); opacity: 0; }
}

06통계 카운터 히어로

숫자 세 칸이 슬롯머신처럼 자릿수를 굴러 올리고, 굴림이 멈추면 각 숫자 아래 밑줄이 왼쪽부터 차오릅니다. 스트립은 steps(6, end) 로 굴러 매 순간 온전한 숫자만 보이고 tabular-nums 로 자릿수 폭이 흔들리지 않습니다. 실적·규모를 앞세우는 회사 소개 첫 화면에 씁니다.

steps(6, end)font-variant-numerictranslateY
.hc__win {
  display: block;
  overflow: hidden;
  height: $hc-cell;
}
.hc__strip {
  display: block;
  transform: translateY($hc-cell * -6);              // 일곱 칸 중 마지막 = 확정된 숫자
}

.hc.is-demo .hc__strip {
  animation-name: hcRoll;
  animation-duration: $duration;
  animation-timing-function: steps(6, end);
  animation-iteration-count: infinite;
  animation-fill-mode: backwards;
  animation-delay: var(--d);
}

@keyframes hcRoll {
  0%        { transform: translateY(0); }
  60%       { transform: translateY($hc-cell * -6); }
  100%      { transform: translateY($hc-cell * -6); }
}

07스크롤로 접히는 히어로

스크롤을 내리면 히어로가 제자리에 붙어 있는 채로 작아지고(scale .86) 모서리가 둥글어지면서 본문 카드가 그 위로 올라와 덮습니다. 스크롤 상자에 이름 붙은 타임라인을 지정하고 키프레임을 animation-timeline 으로 연결했기에 스크롤 양과 화면이 1:1 로 맞습니다. 첫 화면에서 본문으로 넘어가는 이음새에 씁니다.

scroll-timeline-nameanimation-timelineposition: sticky
// ⚠ 타임라인에 **이름**을 붙인다. `scroll(nearest)` 는 `overflow: hidden` 인 조상도
// 스크롤포트로 잡아, 진행률이 영원히 0 인 타임라인에 묶이는 일이 있다 (260 02·03·04 실측).
// position: relative 는 본문 카드의 offsetTop 기준을 이 상자로 못 박기 위한 것
.hf__scroller {
  --h: clamp(148px, 58vh, 176px);
  position: relative;
  height: var(--h);
  overflow-y: auto;
  overscroll-behavior: contain;
  border-radius: $r-card;
  background: $hr-ink;
  scroll-timeline-name: --hfscroll;
  scroll-timeline-axis: y;
  scrollbar-width: none;
  &::-webkit-scrollbar { display: none; }
  &:focus-visible { outline: 2px solid $color; outline-offset: 2px; }
}

08스플릿 슬라이더 히어로

슬라이드 세 장이 같은 격자 칸에 포개져 있고, 왼쪽 글자 면은 왼쪽에서·오른쪽 그림 면은 오른쪽에서 서로 반대 방향으로 닦이듯 드러나 다음 장으로 넘어갑니다. z 순서는 steps(1, end) 컷으로만 바뀌고 위프는 이징 애니메이션이 맡아, 교차 구간에 반투명 유령이 생기지 않습니다. 여러 제품·시즌을 번갈아 보여 주는 첫 화면에 씁니다.

clip-pathgrid-areaaria-current
.hl__slide {
  grid-area: 1 / 1;
  z-index: 1;
  display: grid;
  grid-template-columns: 1fr 38%;
}

// ── 실제 동작 ① 위프 — 왼쪽 면은 왼쪽에서, 오른쪽 면은 오른쪽에서 반대로 닦인다 ──
// opacity 크로스페이드는 쓰지 않는다: 교차 구간 프레임이 전부 반투명 유령이 된다 (237·266 실측)
.hl__pane {
  box-sizing: border-box;
  transition: clip-path $dur-enter $easing;
}
.hl__pane--copy { clip-path: inset(0 100% 0 0); }
.hl__pane--art  { clip-path: inset(0 0 0 100%); }

// 맨 위(3) → 바로 아래(2) → 바닥(1). 바닥일 때만 숨은 상태로 되돌린다 — 위 두 장이 덮고 있어 안 보인다
@keyframes hlZ {
  0%,      33.32% { z-index: 3; }
  33.33%,  66.65% { z-index: 2; }
  66.66%,  100%   { z-index: 1; }
}

09미니멀 로고 히어로

가운데 로고 표시와 문구 한 줄, 버튼 하나, 그 아래 안내 한 줄만 남기는 구도입니다. 로고 둘레에서 고리 두 겹이 반 바퀴 어긋난 위상으로 퍼졌다 사라지며, 퍼짐은 88% 안에 끝나 되돌아오는 구간이 눈에 띄지 않습니다. 브랜드 티저·준비 중 안내 페이지에 씁니다.

scaleopacity:focus-visible
// 정적 상태 — 루프가 없어도 고리 두 겹이 그대로 보인다(.is-demo 를 뗀 화면이 비면 안 된다)
.hm__ring {
  position: absolute;
  inset: 0;
  border: 3px solid $color;   // 2px 는 480×300 축소본(격자 카드)에서 고리가 거의 안 보였다
  border-radius: 50%;
  scale: 1;
  opacity: .34;
}
.hm__ring--b { scale: 1.34; opacity: .18; }

@keyframes hmRing {
  0%             { scale: .58; opacity: .62; }
  88%            { scale: 1.9;  opacity: 0; }
  88.01%, 100%   { scale: .58; opacity: 0; }
}

어디서 깨지는가 — 함정

처음 무너진 것은 07 의 스크롤 연결이었습니다. 가까운 스크롤 상자를 스스로 고르는 scroll(nearest) 방식을 먼저 썼는데, 내용을 자르기만 하는 overflow: hidden 상자도 브라우저는 스크롤포트로 집어들어, 진행률이 영원히 0 인 타임라인에 접힘 애니메이션이 묶였습니다. 스크롤을 아무리 내려도 히어로가 줄어들지 않는 화면이 되었습니다. 타임라인에 이름(scroll-timeline-name: --hfscroll)을 붙이고 히어로와 카드에서 animation-timeline 으로 그 이름을 명시해 연결하니 비로소 스크롤 양이 화면에 닿았습니다. 이징을 등속(linear)으로 둔 것도 같은 이유로, 스프링 곡선을 얹으면 손가락이 멈춘 뒤에도 화면이 홀로 흔들립니다. 키프레임은 한 방향으로만 쓰고 채움 모드를 both 로 묶어, 끝까지 내렸다가 다시 올려도 중간값에서 튀어 올라가지 않게 했습니다. 붙어 있는 히어로의 여유도 같은 자리에서 걸렸습니다 — 부모에 padding-bottom 을 줘 봐야 그 영역은 sticky 범위 밖이라 소용이 없어, 대신 덮어 올라올 본문 카드의 높이로 여유를 잡았습니다. 덧붙여 스크롤 구동 애니메이션은 크롬·엣지 115 와 사파리 26.0 부터 이미 돌고 파이어폭스만 아직 기본값이 꺼져 있어(2026-09-12 caniuse 기준 전역 87.22%), 지원이 없는 브라우저에서는 접힘 없이 히어로가 서 있고 카드가 위로 지나갑니다 — 정적 상태를 먼저 쓴 것이 이런 갈림길의 보험이었습니다.

두 번째는 08 의 점 인디케이터입니다. 점 채움을 장이 바뀌는 0% 지점에서 컷했더니, 새 장이 아직 닦이는 중인데 점만 먼저 옮겨 가 있었습니다. 경계를 슬라이드 교체 지점인 33.33% 에 맞추자 이번에는 세 점이 전부 빈 장이 한 프레임 잡혔습니다. 컷과 z 순서 키프레임의 경계가 같은 지점에 겹치면 그 프레임에서 두 상태가 딱 만나 버리는 것입니다. 컷을 18% 와 51.33% 로 옮겨 프레임 사이에 떨어뜨리니 점과 장이 같은 속도로 움직입니다 — 24프레임 캡처는 4.1667% 눈금이라 이 두 지점은 프레임에 걸치지 않습니다. 채움을 부드럽게 보간하면 반쯤 찬 점이 어중간하게 남기 마련이라, 채움도 컷으로 잘라 눈금 사이에 떨어뜨렸습니다.

세 번째는 대표 장면입니다. 썸네일로 쓰이는 한 장은 앞 장과 가장 크게 달라진 장으로 뽑히는데, 이 규칙은 작은 부품에게 불리합니다. 03 의 형광 띠는 85×15px 짜리 부품이라 프레임당 변화가 0.25% 인데, 낱말이 상자에서 솟는 등장은 프레임당 1.05~1.21% 를 움직입니다. 띠를 아무리 키워도 등장을 이길 수 없어, 대표 장면은 낱말 세 개가 다 선 그림으로 두고 띠는 움직임을 보여 주는 gif 에서만 보이게 했습니다. 키프레임 배분도 이에 맞춰 등장을 여러 프레임에 걸고 띠의 채움은 낱말이 다 선 뒤(0.55초)로 미뤄, 두 관심이 한 장에서 겹치게 했습니다. 같은 까닭에 05 의 쪽지도 컷 하나로만 세우지 않고 확대·터짐 같은 작은 움직임을 곁들였습니다 — 컷만 남으면 움직인 프레임 수가 모자라 캡처에서 죽은 그림처럼 보입니다.

마지막은 멈춘 화면입니다. 06 의 숫자 스트립을 굴림 애니메이션이 떼어진 상태에서 첫 칸에 서 있게 두면, 자동 재생이 꺼진 순간 확정값이 빈칸처럼 보입니다. 스트립의 기본 위치를 일곱 칸 중 마지막 칸, 곧 최종 숫자로 못 박아(translateY($hc-cell * -6)) 정적 상태에서도 값이 그대로 읽히게 했습니다. 04 에서는 밝은 색 덩어리 둘이 글자가 앉는 왼쪽 위를 침범해 미스트 보조 글자의 대비를 먹는 것을 보고, 둘을 아래로 내려(오른쪽 아래 모서리와 바닥 오른쪽 절반) 글자 자리의 바닥을 잔잔한 잉크로 지켰습니다. 미스트가 잉크 바닥 위에서 8.65:1 을 내려면 바닥이 잔잔해야 하므로, 움직이는 배경 위에서는 글자 주변을 지키는 일이 곧 대비 지키기입니다. 이렇게 깨진 자리를 고친 아홉 벌의 완성 코드는 압축 파일 안에 들어 있고, 열어 보는 데 필요한 비밀번호는 u6yhwkzq 입니다.

접근성

색은 짐작이 아니라 WCAG 2.1 상대휘도 공식으로 직접 계산했고, 통과한 조합만 무대에 올렸습니다. 맨 아래 세 줄이 이 팔레트에서 쓰지 않기로 한 조합입니다.

실측 대비 본문 기준
흰 글자 #ffffff 잉크 #17141a 18.24:1 통과
옐로 #ffd23f 잉크 #17141a 12.63:1 통과
라일락 #9d93ff 잉크 #17141a 6.99:1 통과
미스트 #b6b0c0 잉크 #17141a 8.65:1 통과
잉크 #17141a 크림 #fdf7ec 17.10:1 통과
스톤 #56505e 크림 #fdf7ec 7.28:1 통과
바이올렛 #5b4bd6 크림 #fdf7ec 5.75:1 통과
흰 글자 #ffffff 바이올렛 #5b4bd6 6.14:1 통과
흰 글자 #ffffff 그래스 #0f7a52 5.35:1 통과
잉크 #17141a 오렌지 #ff4d1f 5.50:1 통과
흰 글자 #ffffff 오렌지 #ff4d1f 3.32:1 미달 — 쓰지 않는다
흰 글자 #ffffff 옐로 #ffd23f 1.44:1 미달 — 쓰지 않는다
바이올렛 #5b4bd6 잉크 #17141a 2.97:1 미달 — 쓰지 않는다

움직임을 줄여 달라는 설정(prefers-reduced-motion: reduce)에서는 아홉 개 모두 움직임만 끄고 상태와 정보를 남깁니다. 01 은 도형 스택과 스크롤 안내가 올라온 채로 서고, 02 는 빛줄기가 사라진 자리에 배지와 신뢰 표시가 그대로 앉아 있으며, 03 은 낱말이 다 선 상태에서 형광 띠만 가득 차 있게 둡니다. 04 는 덩어리와 빛줄기가 멈춰도 유리 카드와 글자가 남고, 05 는 확대와 터짐이 꺼진 대신 설명 쪽지 둘이 열린 채로 보입니다. 06 은 숫자가 최종값에 박힌 채 굴림이 멈추고 밑줄은 차 있는 상태로 서며, 07 은 접힘 없이 히어로가 서 있고 카드가 그 위를 지나갑니다. 08 은 지금 장이 온전히 보이고 누른 점의 aria-current 가 채움으로 남으며, 09 는 고리 두 겹이 정해진 크기와 옅기로 고정됩니다.

구조 쪽 장치는 이렇습니다. 데모마다 제목 단계를 하나로만 두었고 읽는 순서도 글자가 먼저·그림이 나중이라 화면 낭독기가 문구부터 만납니다. 버튼은 아홉 개 모두 :focus-visible 로 포커스 링을 무대 위에서 보이는 색으로 따로 지정했고, 02 의 버튼 쌍과 08 의 점 셋은 화살표 키로 옮겨 다닐 수 있습니다. 05 의 호출점은 aria-expandedaria-controls 로 쪽지의 열림 상태를 알리고, 06 의 숫자는 굴러가는 스트립 전체를 aria-hidden 으로 감싼 뒤 값을 aria-label 한 줄로 읽히게 했습니다. 07 의 스크롤 상자는 tabindexrole="region" 으로 키보드로도 접근하게 두었고, 09 의 안내 문장은 role="status" 로 눌렀을 때 한 번만 읽힙니다. 접는 방식의 근거는 MDN 의 scroll-timeline-name 문서backdrop-filter 문서, 대비 기준은 W3C 의 최소 명도 대비 해설을 따랐습니다. 첫 화면 소재를 더 둘러보려면 히어로 모음으로, 이 사이트가 코드를 어떻게 검증하는지는 소개에 적어 두었습니다.

FAQ

히어로 섹션 높이는 화면에 꽉 채워야 하나요?

꽉 채우는 것이 답이 아닐 때가 많습니다. 휴대폰 브라우저는 주소창 높이가 들쭉날쭉해 100vh 고정 화면은 아래가 잘리거나 위가 뜨고, 첫 화면의 임무는 전부 보여 주는 것이 아니라 다음 동작으로 데려가는 것입니다. 이 아홉 개는 내용 높이에 맞추고(데모 무대는 480×300), 본문과의 이음새가 걱정되면 07 처럼 스크롤로 접히는 구도로 잇습니다.

이 편과 히어로 애니메이션 9종 편은 어떻게 나눠서 쓰나요?

애니메이션 편이 첫 화면 부품의 움직임 효과를 모았다면 이번 편은 화면 전체의 구도를 모았습니다. 실제로는 짝입니다 — 예컨대 03 의 낱말 등장 구도에 그 글의 낱말 이징을 얹고, 07 의 접힘에 스크롤 효과를 더할 수 있습니다. 구도를 먼저 고르고 애니메이션 9종에서 움직임을 골라 얹는 순서를 권합니다.

사진 소재가 없는데 첫 화면을 채울 수 있나요?

됩니다. 04 는 사진 한 장 없이 radial-gradient 색 덩어리 세 개와 유리 카드 하나로 배경을 만들고, 05 는 도형 조합으로 제품 그림을 흉내 냈습니다. 외부 요청이 0건이라 로딩도 가볍고 색만 바꾸면 브랜드에 맞춰집니다. 첫 화면의 역할은 관심을 얻는 것이지 사진 감상이 아니므로 도형과 색으로도 충분히 설 수 있습니다.

압축 비밀번호를 입력하세요

비밀번호는 이 글 본문 안에 있습니다. 읽다 보면 나옵니다.