공들여 다듬은 움직임이 어떤 사용자에게는 멀미와 두통이 되기도 합니다. 우리에게 즐거운 미끄러짐이 균형 감각이 예민한 이에게는 흔들리는 배에 탄 듯한 어지럼을 안기지요. 그런 사람을 밀어내지 않으면서 반응만은 전하는 방법을 CSS로 만져 보겠습니다. 나쁜 예와 접근성을 지킨 예를 나란히 놓고 비교합니다.

86.1 움직임 줄이기 신호 읽기

많은 기기에는 움직임을 줄여 달라는 설정이 있고, CSS는 그 신호를 읽을 수 있습니다. 가장 무딘 방식은 모든 애니메이션과 전환을 통째로 끄는 것입니다.


@media (prefers-reduced-motion: reduce) {
* {
animation: none !important;
transition: none !important;
}
}


급할 때 쓸 안전망은 되지만, 이러면 성공을 알리는 확인이나 처리 중이라는 표시처럼 꼭 필요한 상태 피드백까지 통째로 사라집니다. 어지럼을 부르는 것은 대개 크고 요란한 움직임이지, 잔잔하게 흐려지는 작은 변화가 아닌데 말이지요. 그래서 실제로는 전부 끄기보다 큰 움직임만 골라 부드럽게 줄이는 편이 낫습니다.

86.2 없애지 말고 부드럽게 바꾸기

화면을 크게 가로지르는 움직임이 어지럼의 주범입니다. 아래 대화창은 아래에서 40px를 미끄러져 올라옵니다.


.dialog {
transform: translateY(40px);
transition: transform .3s ease;
}
.dialog.open {
transform: translateY(0);
}


움직임을 줄이려는 사용자에게는 이 미끄러짐을 걷어 내고, 자리에서 조용히 밝아지는 크로스페이드로 바꿔 줍니다.


@media (prefers-reduced-motion: reduce) {
.dialog {
transform: none;
opacity: 0;
transition: opacity .2s ease;
}
.dialog.open {
opacity: 1;
}
}


슬라이드는 사라졌지만 대화창이 나타난다는 사실은 그대로 전해집니다. 반응을 없애는 대신 어지럽지 않은 형태로 갈아 끼운 것입니다.

86.3 꼭 필요한 모션은 크기만 줄이기

처리 중임을 알리는 회전 표시는 함부로 없앨 수 없습니다. 없애는 대신 속도를 늦춰 자극을 덜어 줍니다.


@keyframes spin {
to { transform: rotate(360deg); }
}
.spinner {
animation: spin .8s linear infinite;
}
@media (prefers-reduced-motion: reduce) {
.spinner {
animation-duration: 1.6s;
}
}


회전은 남겨 지금 처리 중이라는 정보를 지키되, 두 배로 느리게 돌려 눈을 덜 피로하게 합니다. 이렇게 꼭 필요한 움직임과 그저 흥을 돋우는 곁들이 움직임을 갈라, 앞의 것은 순한 형태로라도 남기고 뒤의 것은 과감히 걷어 냅니다. 정보를 담은 모션은 지우기보다 순하게 만드는 쪽으로 갑니다.

86.4 포커스 링은 언제나 유지

키보드로 화면을 다니는 사용자에게 포커스 링은 길잡이입니다. 움직임을 줄일 때 전환은 뗄 수 있어도 포커스 링 자체는 절대 지우면 안 됩니다.


.btn {
transition: box-shadow .15s ease;
}
.btn:focus-visible {
outline: 2px solid #2563eb;
outline-offset: 2px;
}
@media (prefers-reduced-motion: reduce) {
.btn {
transition: none;
}
}


움직임 줄이기가 켜지면 부드럽게 번지던 전환만 사라지고, 또렷한 외곽선은 그대로 남습니다. 포커스 링은 움직임이 아니라 위치를 알리는 표시이니, 움직임을 줄인다고 함께 지워서는 안 되지요. 마우스를 안 쓰는 사람도 지금 어디에 있는지 놓치지 않습니다.

86.5 저절로 도는 반복은 멈추기

사용자가 시작하지 않았는데 끝없이 도는 움직임은 특히 거슬립니다. 아래 흐르는 띠는 스스로 무한히 반복합니다.


@keyframes marquee {
from { transform: translateX(0); }
to { transform: translateX(-50%); }
}
.ticker {
animation: marquee 12s linear infinite;
}


움직임을 줄이려는 사용자에게는 이 반복을 아예 멈춰 세웁니다. 스스로 넘어가는 배너나 캐러셀, 멈추지 않고 반복되는 짧은 영상도 같은 방식으로 정지시키고요.


@media (prefers-reduced-motion: reduce) {
.ticker {
animation: none;
}
}


글을 읽으려는 사용자의 눈길을 자꾸 앗아가던 움직임이 조용해집니다. 저절로 움직이는 것에는 저절로 멈출 길을 함께 주어야 합니다.

86.6 어지럼을 부르는 큰 이동 피하기

몸의 균형 감각이 예민한 사용자에게는 크게 확대되거나 멀리 이동하는 움직임이 멀미를 부릅니다. 아래 호버는 크게 부풀며 위로 솟구칩니다.


.hero {
transition: transform .5s ease;
}
.hero:hover {
transform: scale(1.4) translateY(-30px);
}


같은 강조라도 큰 이동 대신 잔잔한 밝기 변화로 두면 어지럽지 않습니다. 손이 스칠 때마다 화면이 요동치지 않아야 마음 놓고 화면을 누빌 수 있습니다.


.hero {
transition: filter .3s ease;
}
.hero:hover {
filter: brightness(1.05);
}


손을 올리면 살짝 밝아질 뿐, 화면이 커지거나 튀어 오르지 않습니다. 시차 효과나 확대 이동 같은 큰 움직임은 어지럼의 방아쇠라는 것을 늘 염두에 둡니다.

86.7 움직임 하나에만 뜻을 싣지 않기

어떤 정보를 오직 흔들림으로만 전하면, 그 움직임을 줄인 사용자는 그 뜻을 영영 받지 못합니다. 아래 입력칸은 오류를 좌우로 떠는 것으로만 알립니다.


@keyframes shake {
0%, 100% { transform: translateX(0); }
25% { transform: translateX(-6px); }
75% { transform: translateX(6px); }
}
.field.error {
animation: shake .3s ease;
}


같은 오류를 흔들림에 더해 색과 글로도 함께 알립니다. 움직임을 줄인 사용자에게도 빨간 테두리와 안내 문구가 그대로 남아 뜻이 전해집니다.


.field.error {
animation: shake .3s ease;
border-color: #dc2626;
}
.field.error .msg {
display: block;
color: #dc2626;
}


움직임은 뜻을 전하는 여러 통로 가운데 하나일 뿐입니다. 색과 글이라는 다른 통로를 함께 열어 두어야, 흔들림을 못 보는 사용자도 소외되지 않습니다.

86.8 정리

공들여 만든 움직임도 누군가에게는 고통이 될 수 있습니다. 움직임 줄이기 신호를 읽어 큰 이동은 크로스페이드로 갈아 끼우고, 꼭 필요한 모션은 순하게 줄이고, 포커스 링은 반드시 남기고, 저절로 도는 반복은 멈출 길을 주는 것. 이 배려는 처음 설계할 때 두 갈래 길을 함께 그려 두어야 끝까지 온전히 남습니다.