화면이 순간이동하듯 툭 바뀌면 사용자는 방금 어디에 있었는지를 놓칩니다. 앞 화면이 물러나고 새 화면이 들어서는 짧은 움직임을 CSS로 어떻게 이어 주는지, 클래스 하나를 토글하며 페이드와 슬라이드, 아코디언과 모달까지 코드로 보여드리겠습니다.
80.1 페이드 인: 스르르 나타나기
가장 기본은 요소가 흐릿하게 나타나는 페이드입니다. @keyframes로 투명에서 불투명으로, 살짝 아래에서 제자리로 올라오게 하면 등장에 무게가 실립니다.
@keyframes fade-in {
from {
opacity: 0;
transform: translateY(8px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
.view {
animation: fade-in 0.3s ease both;
}
화면에 .view가 붙는 순간 아래에서 살짝 떠오르며 또렷해집니다. 끝의 both는 시작 전과 끝난 뒤의 상태를 키프레임에 붙들어 두어 깜빡임을 막습니다.
80.2 슬라이드 인: 방향에 뜻을 담기
앞으로 나아갈 때는 오른쪽에서, 되돌아갈 때는 왼쪽에서 미끄러져 들어오게 하면 방향 자체가 안팎을 말해 줍니다. translateX의 부호만 바꾸면 됩니다.
@keyframes slide-from-right {
from {
transform: translateX(100%);
}
to {
transform: translateX(0);
}
}
@keyframes slide-from-left {
from {
transform: translateX(-100%);
}
to {
transform: translateX(0);
}
}
.enter-forward {
animation: slide-from-right 0.28s ease both;
}
.enter-back {
animation: slide-from-left 0.28s ease both;
}
들어갈 때와 나올 때가 반대 방향으로 움직이니, 사용자는 자기가 안으로 들어가는지 밖으로 나오는지를 몸으로 느낍니다. 이 앞뒤 대칭을 제품 전체에서 약속처럼 지키는 것이 중요합니다.
80.3 아코디언: max-height로 여닫기
내용의 높이는 미리 알 수 없으니 height는 애니메이션이 안 됩니다. 대신 넉넉히 큰 max-height를 0과 큰 값 사이에서 transition하면 펼침과 접힘이 부드러워집니다.
.panel {
max-height: 0;
overflow: hidden;
transition: max-height 0.3s ease;
}
.panel.open {
max-height: 400px;
}
마크업은 여닫는 버튼과 내용만 있으면 됩니다.
<button class="acc-head">배송 안내</button>
<div class="panel">
<p>오후 2시 이전 주문은 당일 발송됩니다.</p>
</div>
open 클래스가 붙으면 접혀 있던 내용이 아래로 밀려 나옵니다. 실제 내용이 잘리지 않도록 max-height는 예상 높이보다 여유 있게 잡아 둡니다.
80.4 탭 크로스페이드: opacity와 visibility
탭 내용을 겹쳐 두고 활성 탭만 보이게 하면 부드럽게 교차됩니다. opacity만으로는 숨긴 패널이 클릭을 가로채니 visibility를 함께 다뤄야 합니다.
.tab-panel {
opacity: 0;
visibility: hidden;
transition: opacity 0.25s ease, visibility 0.25s;
}
.tab-panel.active {
opacity: 1;
visibility: visible;
}
탭을 누르면 이전 패널이 흐려지며 물러나고 새 패널이 또렷해집니다. visibility가 페이드가 끝난 뒤에 꺼지므로, 사라지는 동안에도 화면이 끊기지 않고 이어집니다.
80.5 모달: 배경 페이드 + 패널 스케일
겹쳐 뜨는 창은 두 겹으로 움직입니다. 뒤를 덮는 막은 불투명도로 스르르 짙어지고, 앞의 패널은 살짝 작은 크기에서 제 크기로 부풀어 오르며 시선을 당깁니다.
.overlay {
opacity: 0;
visibility: hidden;
transition: opacity 0.25s ease, visibility 0.25s;
}
.overlay .modal {
transform: scale(0.92);
transition: transform 0.25s ease;
}
.overlay.open {
opacity: 1;
visibility: visible;
}
.overlay.open .modal {
transform: scale(1);
}
open 하나가 막과 패널을 동시에 깨웁니다. 어두운 막이 배경을 덮는 사이 패널이 톡 부풀어 올라, 창이 어디에서 왔는지가 그 움직임에 담깁니다.
80.6 transition-delay로 순서 주기
여러 요소가 한꺼번에 나타나면 화면이 덜컥합니다. 같은 등장에 transition-delay를 조금씩 늘려 주면 위에서부터 차례로 떠오릅니다.
.stagger li {
opacity: 0;
transform: translateY(10px);
transition: opacity 0.3s ease, transform 0.3s ease;
}
.stagger.show li {
opacity: 1;
transform: translateY(0);
}
.stagger.show li:nth-child(1) {
transition-delay: 0.05s;
}
.stagger.show li:nth-child(2) {
transition-delay: 0.1s;
}
.stagger.show li:nth-child(3) {
transition-delay: 0.15s;
}
show 클래스가 붙으면 항목들이 0.05초 간격으로 잇따라 올라옵니다. 같은 값이 한꺼번에 켜져도 딜레이가 서로 어긋나 있어 시선이 위에서 아래로 자연스럽게 흐릅니다.
80.7 움직임을 줄이는 사용자 배려하기
화면 전체가 미끄러지는 큰 움직임은 어지럼을 부르기 쉽습니다. 움직임 줄이기를 켠 사용자에게는 슬라이드와 스케일을 걷어 내고, 상태 전환만은 담백한 페이드로 남깁니다.
@media (prefers-reduced-motion: reduce) {
.view,
.enter-forward,
.enter-back {
animation: fade-in 0.2s ease both;
}
.overlay .modal,
.stagger li {
transform: none;
transition: opacity 0.2s ease;
}
.stagger.show li {
transition-delay: 0s;
}
}
슬라이드는 제자리 페이드로 바뀌고, 패널의 부풀어 오름과 항목의 순차 등장도 자리 이동 없이 흐려졌다 나타나는 정도로 잦아듭니다. 방향의 뜻은 옅어져도 화면이 바뀌었다는 사실만은 분명히 남습니다.
80.8 정리
전환은 사라지기 쉬운 화면 사이의 길을 눈에 보이게 이어 주는 일입니다. 등장엔 페이드와 슬라이드를, 여닫음엔 max-height 전환을, 겹치는 탭과 모달엔 opacity와 visibility를 짝지어 쓰고, 여럿이 나타날 땐 transition-delay로 순서를 줍니다. 상태는 대개 .open이나 .active 같은 클래스 토글 하나로 갈리며, 무엇을 넣든 prefers-reduced-motion 대체를 함께 두는 것을 잊지 않습니다.