GODRICH
#css애니메이션#버튼#로딩#전환효과#스크롤#대시보드#실전ui#디자인트렌드

이미지 갤러리 CSS 9종 — 메이슨리부터 라이트박스까지

이미지 갤러리 CSS는 사진 여러 장을 그리드나 워터폴 모양으로 배치하고 호버·클릭에 반응하게 만드는 레이아웃 기법입니다. 핀터레스트에 저장해 둔 그 갤러리처럼 예쁘지만, 막상 코드로 옮기려면 그리드 수식과 크롭 처리에서 막히기 쉽습니다.

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

아홉 개는 인기 순서가 아니라 갤러리 하나를 실제로 만들 때 부딪히는 순서로 놓았습니다. 먼저 칸을 똑같이 나누는 가장 기본적인 css 그리드 레이아웃부터 시작해(01), 사진 비율이 제각각이라는 현실적인 문제를 풀고(02), 화면 폭에 맞춰 칸 수가 스스로 바뀌는 반응형을 더하고(03), 높이가 다른 사진을 빈틈없이 쌓는 워터폴을 두 가지 방식으로 비교하고(04~05), 인스타그램처럼 정사각형만 고집하거나(06) 대표 사진 하나를 키우는 편집 레이아웃을 얹고(07), 마지막으로 캡션과 클릭 확대라는 상호작용을 붙입니다(08~09). 두 장을 겹쳐 놓고 손잡이로 비교하는 배치는 이미지 비교 슬라이더 UI에, 칸 자체를 끌어 넓히는 쪽은 화면 분할 패널에 따로 모아 두었습니다.

번호 이름 무엇이 움직이나 JS
01 2단 균등 그리드 타일 translateY·scale 없음
02 3단 그리드 · 비율 통일 사진 scale, 프레임 translateY 없음
03 자동 반응형 그리드 타일 opacity·scale 진입 없음
04 멀티 컬럼 워터폴 타일 translateY 없음
05 그리드 워터폴 타일 scale 없음
06 정사각 그리드 고정 사진 scale, 프레임 translateY 없음
07 첫 장 강조 배치 타일 scale 없음
08 호버 캡션 오버레이 캡션 translateY·opacity 없음
09 클릭 시 라이트박스 패널 scale·opacity 없음

012단 균등 그리드

무엇이 움직이나

사진 네 장을 2×2 칸에 똑같은 크기로 나눠 놓고, 커서가 올라간 칸만 살짝 떠오르며 그림자가 짙어져 지금 보고 있는 사진이 어디인지 알려줍니다.

2단 균등 그리드
새 창에서 열기

01 · 2단 균등 그리드 — 측정 누적면적 3.564% · 움직인 프레임 15/23GODRICH 원작 · zip 에 SCSS·React 포함 · 비밀번호 보기

grid-template-columns:repeat(2,1fr)gaptranslateY hover

코드에서 볼 곳

포트폴리오 섬네일이나 블로그 글 속 사진 나열처럼 가장 단순한 갤러리에 씁니다.

.gallery {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: $sp-2;
}
.tile {
  transition: transform $duration $easing, box-shadow $duration $easing;
}
.tile:hover, .tile:focus-visible {
  transform: translateY(-4px) scale(1.03);
  box-shadow: $shadow-raised;
}

023단 그리드 · 비율 통일

무엇이 움직이나

세로 사진·가로 사진·정사각 사진이 섞여 있어도 각 이미지에 aspect-ratio: 4/3과 object-fit: cover를 걸면 모든 칸이 같은 크기로 맞춰지고, 호버하면 프레임이 살짝 떠오르며 안쪽 사진도 함께 확대됩니다.

3단 그리드 · 비율 통일
새 창에서 열기

02 · 3단 그리드 · 비율 통일 — 측정 누적면적 3.778% · 움직인 프레임 23/23GODRICH 원작 · zip 에 SCSS·React 포함 · 비밀번호 보기

aspect-ratio:4/3object-fit:coveroverflow:hidden

코드에서 볼 곳

휴대폰으로 찍어 비율이 제각각인 사진을 올리는 갤러리에 맞습니다.

.cell {
  aspect-ratio: 4 / 3;
  overflow: hidden;
}
.photo {
  width: 100%; height: 100%;
  object-fit: cover;
  transform: scale(1);
  transition: transform $duration $easing;
}
.cell:hover .photo, .cell:focus-within .photo {
  transform: scale(1.12);
}

03자동 반응형 그리드

무엇이 움직이나

grid-template-columns: repeat(auto-fit, minmax(56px, 1fr)) 한 줄만 걸어 두면 미디어쿼리 없이도 화면 폭에 맞춰 한 줄에 들어가는 칸 수가 저절로 바뀌고, 새 타일은 옅게 나타나며 자리를 잡습니다.

자동 반응형 그리드
새 창에서 열기

03 · 자동 반응형 그리드 — 측정 누적면적 23.517% · 움직인 프레임 23/23GODRICH 원작 · zip 에 SCSS·React 포함 · 비밀번호 보기

repeat(auto-fit,minmax())그리드 자동 배치opacity+scale 진입

코드에서 볼 곳

카테고리마다 사진 수가 다른 상품 갤러리에 씁니다.

.gallery {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(56px, 1fr));
  gap: $sp-2;
}
.tile {
  opacity: .35;
  transform: scale(.88);
  transition: transform $duration $easing, opacity $duration $easing;
}
.tile.is-visible {
  opacity: 1;
  transform: scale(1);
}

04멀티 컬럼 워터폴

무엇이 움직이나

사진 다섯 장에 columns: 2만 걸면 자바스크립트 없이도 높이가 제각각인 사진이 컬럼을 따라 빈틈없이 쌓이는 워터폴 레이아웃이 되고, break-inside: avoid로 사진이 컬럼 경계에서 반으로 잘리는 걸 막습니다. 핀터레스트 스타일 무드보드에 어울립니다.

멀티 컬럼 워터폴
새 창에서 열기

04 · 멀티 컬럼 워터폴 — 측정 누적면적 3.547% · 움직인 프레임 23/23GODRICH 원작 · zip 에 SCSS·React 포함 · 비밀번호 보기

columns:2break-inside:avoidcolumn-gap

코드에서 볼 곳

.gallery {
  columns: 2;
  column-gap: $sp-2;
}
.tile {
  break-inside: avoid;
  margin: 0 0 $sp-2;
  transition: transform $duration $easing;
}
.tile:hover, .tile:focus-visible {
  transform: translateY(-3px);
}

05그리드 워터폴 (읽는 순서 유지)

무엇이 움직이나

grid-auto-rows를 4px 같은 아주 작은 단위로 쪼개고 사진마다 자기 높이에 맞는 grid-row-end: span N을 미리 계산해 주면, 컬럼 방식과 달리 사진이 위→아래가 아니라 왼쪽→오른쪽 읽는 순서 그대로 배치되는 워터폴이 됩니다.

그리드 워터폴 (읽는 순서 유지)
새 창에서 열기

05 · 그리드 워터폴 (읽는 순서 유지) — 측정 누적면적 2.089% · 움직인 프레임 23/23GODRICH 원작 · zip 에 SCSS·React 포함 · 비밀번호 보기

grid-auto-rows:4pxgrid-row-end:span N읽는 순서 유지

코드에서 볼 곳

발행 순서가 중요한 뉴스·포트폴리오 갤러리에 맞습니다.

.gallery {
  display: grid;
  grid-auto-flow: row dense;
  grid-template-columns: repeat(2, 1fr);
  grid-auto-rows: 4px;
  column-gap: $sp-2;
}
.tile--1 { grid-row-end: span 10; }
.tile--2 { grid-row-end: span 13; }
.tile--3 { grid-row-end: span 9; }

06정사각 그리드 고정

무엇이 움직이나

모든 타일에 aspect-ratio: 1 / 1과 object-fit: cover를 걸어 원본 사진 비율과 상관없이 인스타그램 피드처럼 완벽한 정사각형만 나열되게 하고, 호버하면 타일이 살짝 떠오르면서 안쪽 사진도 함께 확대됩니다.

정사각 그리드 고정
새 창에서 열기

06 · 정사각 그리드 고정 — 측정 누적면적 3.329% · 움직인 프레임 23/23GODRICH 원작 · zip 에 SCSS·React 포함 · 비밀번호 보기

aspect-ratio:1/1object-fit:coverscale hover

코드에서 볼 곳

.cell {
  aspect-ratio: 1 / 1;
  overflow: hidden;
}
.photo {
  width: 100%; height: 100%;
  object-fit: cover;
  transform: scale(1);
  transition: transform $duration $easing;
}
.cell:hover .photo {
  transform: scale(1.1);
}

07첫 장 강조 배치

무엇이 움직이나

첫 번째 사진에만 grid-column: span 2와 grid-row: span 2를 줘서 나머지보다 네 배 크게 띄우고, 그 옆으로 작은 사진들이 채워지는 잡지 레이아웃을 순수 CSS 선택자 하나로 만듭니다.

첫 장 강조 배치
새 창에서 열기

07 · 첫 장 강조 배치 — 측정 누적면적 7.026% · 움직인 프레임 21/23GODRICH 원작 · zip 에 SCSS·React 포함 · 비밀번호 보기

:nth-of-typegrid-column: 1 / 3grid-row: 1 / 3

코드에서 볼 곳

대표 사진 하나를 강조하는 블로그 헤더 갤러리에 씁니다.

.gallery {
  display: grid;
  grid-template-columns: repeat(3, 56px);
  grid-template-rows: repeat(2, 56px);
  gap: $sp-2;
}
.tile:first-child {
  grid-column: 1 / 3;
  grid-row: 1 / 3;
}

08호버 캡션 오버레이

무엇이 움직이나

평소엔 사진 아래쪽에 살짝 가려져 있던 반투명 띠가 호버하면 translateY로 완전히 올라오면서 제목과 위치 같은 캡션 글자가 함께 드러납니다.

호버 캡션 오버레이
새 창에서 열기

08 · 호버 캡션 오버레이 — 측정 누적면적 4.308% · 움직인 프레임 18/23GODRICH 원작 · zip 에 SCSS·React 포함 · 비밀번호 보기

translateY hover반투명 그러데이션opacity

코드에서 볼 곳

여행 사진마다 장소 이름을 붙이는 갤러리에 맞습니다.

.caption {
  position: absolute; left: 0; right: 0; bottom: 0;
  transform: translateY(64%);
  opacity: 0;
  transition: transform $duration $easing, opacity $duration $easing;
}
.tile:hover .caption, .tile:focus-within .caption {
  transform: translateY(0);
  opacity: 1;
}

09클릭 시 라이트박스 (JS 없이)

무엇이 움직이나

타일을 클릭하면 popovertarget 속성 하나로 원본 크기 사진이 담긴 팝오버가 뜨고, ::backdrop이 배경을 어둡게 덮는 동안 @starting-style이 사진을 작게·투명하게 시작해 커지며 나타나게 합니다.

클릭 시 라이트박스 (JS 없이)
새 창에서 열기

09 · 클릭 시 라이트박스 (JS 없이) — 측정 누적면적 11.947% · 움직인 프레임 8/23GODRICH 원작 · zip 에 SCSS·React 포함 · 비밀번호 보기

popovertarget::backdrop@starting-style

코드에서 볼 곳

<button popovertarget="lb-01">과 <div id="lb-01" popover>만 짝지으면 열고 닫는 로직 전부를 브라우저가 대신합니다.

.lightbox {
  opacity: 0;
  transform: scale(.8);
  transition: opacity $duration $easing, transform $duration $easing,
    overlay $duration $easing allow-discrete, display $duration $easing allow-discrete;
  @starting-style { opacity: 0; transform: scale(.8); }
}
.lightbox:popover-open { opacity: 1; transform: scale(1); }
.lightbox::backdrop { background: rgba($stage-ink, .7); opacity: 0; }
.lightbox:popover-open::backdrop { opacity: 1; }

어디서 깨지는가 — 함정 하나

css 갤러리, 어디서 깨지나요?

05를 처음 렌더링했을 때 두 번째 컬럼 위쪽에 예상치 못한 빈 칸이 생겼습니다. grid-auto-flow: row(dense 없이)로 두면 브라우저는 span이 큰 타일 뒤에 빈 칸을 그냥 남겨 두고 다음 줄로 넘어가 버리기 때문입니다. dense를 붙이자 그 구멍은 메워졌지만, dense는 DOM 순서보다 빈 칸 채우기를 우선하기 때문에 스팬 조합에 따라 타일이 실제 DOM 순서와 다른 위치에 그려질 수 있습니다. 04번(멀티 컬럼)이라면 애초에 순서가 위→아래·왼쪽 컬럼부터라 이런 문제가 없지만, 05번(그리드 행 스팬)은 dense를 켜는 순간 이 위험을 떠안게 된다는 뜻입니다. 칸 안에서 사진이 마우스를 따라 기울기까지 하는 연출은 마우스를 따라오는 3D 회전 장면 쪽에서 다뤘습니다. 발행 날짜처럼 순서 자체가 의미 있는 갤러리라면 dense를 빼고 빈 칸을 감수하거나, 압축 파일 비밀번호 24p2nyh2과 함께 들어 있는 vanilla 폴더의 span 값을 직접 조정해 빈 칸이 생기지 않게 미리 계산해야 합니다.

접근성

챙긴 것

prefers-reduced-motion: reduce를 켠 화면에서는 아홉 개 모두 전환·루프만 꺼지고 결과 상태는 그대로 남도록 맞췄습니다. 01·02·06은 확대·이동이 순간적으로 적용되고, 03은 타일이 전환 없이 바로 제자리(불투명·원래 크기)로 나타나고, 04·05는 떠오름 없이 바로 제자리에 멈추고, 07은 강조 크기가 즉시 적용되고, 08은 캡션이 늘 펼쳐진 채로 고정되고, 09는 라이트박스가 전환 없이 바로 열리고 닫힙니다. 09의 클릭 트리거는 <button> 요소라 마우스뿐 아니라 키보드 Tab·Enter로도 열리고, popovertargetaction="hide" 닫기 버튼도 같은 방식으로 접근합니다. 나머지 여덟 개도 호버 전용 동작을 :focus-visible이나 :focus-within에도 걸어 두어 키보드 사용자가 마우스 없이도 같은 결과를 봅니다. CSS 그리드 메이슨리 표준화 논의는 MDN 문서에서, Popover API 지원 범위는 MDN Popover API에서 확인할 수 있습니다. 카드형 호버 효과가 더 궁금하면 이전에 올린 카드 호버 모음과 글래스모피즘 모음도 함께 보면 좋습니다.

FAQ

09 라이트박스는 Popover API를 지원하지 않는 브라우저에서 어떻게 됩니까?

popovertarget 속성을 브라우저가 모르면 그 속성은 그냥 무시되어 버튼이 평범한 버튼으로 남고 클릭해도 아무 일이 일어나지 않습니다. 레이아웃이 깨지거나 에러 없이 기능만 조용히 빠지는 안전한 방식이고, 최신 Chrome·Edge·Safari·Firefox는 이미 지원합니다.

이미지 갤러리 만들기에서 04와 05 중 어떤 워터폴을 써야 합니까?

사진이 위에서 아래로 읽혀도 상관없다면(무드보드·갤러리) columns 방식(04)이 코드가 짧고 관리하기 쉽습니다. 최신순 정렬처럼 왼쪽 위부터 순서대로 읽혀야 한다면 그리드 방식(05)을 쓰고, 함정 문단에서 다룬 dense의 순서 어긋남 위험을 감안해야 합니다. 정렬 순서를 사람이 직접 끌어 바꾸는 갤러리는 포트폴리오 갤러리 UI에서 따로 다뤘습니다.

React 판에서 03 자동 반응형 그리드는 칸 수를 어떻게 계산합니까?

AutoFitGallery 컴포넌트는 칸 수를 계산하지 않습니다. grid-template-columns: repeat(auto-fit, minmax())가 컨테이너 폭만 보고 브라우저 안에서 스스로 계산하므로, 리사이즈 이벤트를 듣거나 상태를 두지 않아도 창 크기가 바뀌면 자동으로 다시 배치됩니다.

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

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