AI 채팅 CSS 로딩 애니메이션 9종
ChatGPT나 Claude 같은 AI 채팅창에서 답이 오기를 기다릴 때 뜨는 점 세 개, 깜빡이는 커서, 서서히 나타나는 글자는 전부 CSS 로딩 애니메이션입니다. div 하나로 끝나는 아홉 가지를 코드와 함께 모았습니다.
자동 재생 · 눌러서 해당 항목으로 · 전부 한 zip
- 01 생각중 점 3개
- 02 Thinking… 텍스트 쉬머
- 03 스트리밍 커서 깜빡임
- 04 메시지 버블 등장
- 05 스크롤 가장자리 페이드
- 06 도구 호출 단계 표시
- 07 단어 단위 페이드 인
- 08 전송→정지 버튼 모핑
- 09 정적 상태 뱃지 대체
아홉 가지는 인기 순이 아니라 AI 채팅창에서 실제로 벌어지는 순서를 따릅니다. 먼저 답을 기다리는 동안 보이는 대기 신호(01~03)를 지나, 답이 도착하고 대화가 쌓이는 순간(04~05)으로, 그다음은 AI가 도구를 쓰며 일하는 과정과 글자가 이어지는 방식(06~07)을 거쳐, 마지막은 사용자가 생성을 멈추는 조작과 움직임을 줄인 사람을 위한 대체 표시(08~09)로 끝납니다.
01생각중 점 3개
메시지를 보내고 AI가 답을 준비하는 동안, 빈 말풍선 자리에 작은 점 세 개가 순서대로 커졌다 작아지며 사람이 타자를 치는 듯한 자연스러운 대기 신호를 보여줍니다.
@keyframes td-bounce {
0%, 60%, 100% { transform: translateY(0); opacity: .35; }
30% { transform: translateY(-6px); opacity: 1; }
}
.td-bubble.is-loading .td-dot,
.td-bubble.is-demo .td-dot {
animation: td-bounce $duration $easing infinite;
}
.td-bubble.is-demo .td-dot:nth-child(2) { animation-delay: 160ms; }
.td-bubble.is-demo .td-dot:nth-child(3) { animation-delay: 320ms; }
02Thinking 텍스트 쉬머
검색이나 요약처럼 시간이 걸리는 작업에서는 점 대신 상태 문구를 보여주고, 그 글자 위로 밝은 빛줄기가 지나가게 해 아직 진행 중이라는 걸 알립니다.
.sh-label {
background-image: linear-gradient(
90deg,
rgba(23, 20, 26, .3) 0%,
rgba(23, 20, 26, .3) 42%,
$color 50%,
rgba(23, 20, 26, .3) 58%,
rgba(23, 20, 26, .3) 100%
);
background-size: 220% 100%;
-webkit-background-clip: text;
background-clip: text;
color: rgba(23, 20, 26, .3);
}
03스트리밍 커서 깜빡임
답변 글자가 실시간으로 이어질 때는 문장 끝에 짧은 커서를 붙여 opacity로 깜빡이게 하면, 지금 이 자리에 다음 글자가 계속 도착하고 있다는 걸 보여줄 수 있습니다.
.cr-caret {
display: inline-block;
width: $sp-2;
height: 1em;
margin-left: $sp-1;
vertical-align: -0.2em;
background: $color;
}
@keyframes cr-blink {
0%, 49% { opacity: 1; }
50%, 100% { opacity: .25; }
}
.cr-line.is-demo .cr-caret,
.cr-line.is-demo .cr-tail {
animation: cr-blink $duration $easing infinite;
}
04메시지 버블 등장
답변이 완성되면 빈 자리에 있던 말풍선이 살짝 커졌다 제자리로 돌아오며 나타나, 답이 막 도착했다는 걸 눈에 띄게 알립니다.
@keyframes bp-pop {
0% { transform: scale(.82); opacity: 0; }
15% { transform: scale(1.05); opacity: 1; }
25% { transform: scale(1); opacity: 1; }
70% { transform: scale(1); opacity: 1; }
85% { transform: scale(.9); opacity: 0; }
100% { transform: scale(.82); opacity: 0; }
}
.bp-bubble.is-demo {
animation: bp-pop $duration $easing infinite;
}
05스크롤 가장자리 페이드
메시지가 쌓여 채팅창에 스크롤이 생기면, 위아래 끝을 옅게 지워 글자를 자르지 않고도 아직 더 볼 내용이 있다는 걸 알려줍니다.
.sf-viewport {
overflow: hidden;
// 가장자리 페이드 — 이 마스크는 고정. 움직이는 건 안의 트랙뿐
-webkit-mask-image: linear-gradient(to bottom, transparent 0, #000 $sp-8, #000 calc(100% - #{$sp-8}), transparent 100%);
mask-image: linear-gradient(to bottom, transparent 0, #000 $sp-8, #000 calc(100% - #{$sp-8}), transparent 100%);
padding: $sp-4 0;
}
움직이는 건 마스크가 아니라 그 안의 목록입니다. 같은 메시지 목록을 두 벌 이어 붙이고 translateY(-50%)로 반씩 밀면, 절반 지점에서 다음 벌이 똑같은 자리에 오기 때문에 이어 붙인 자국 없이 계속 흐르는 것처럼 보입니다.
06도구 호출 단계 표시
AI가 검색하거나 문서를 읽는 것처럼 도구를 순서대로 쓸 때는 단계를 세로로 늘어놓고, 끝난 단계마다 원이 체크로 바뀌며 연결선이 아래까지 채워지게 해 지금 어디까지 진행됐는지 보여줍니다.
.ts-line {
grid-column: 1;
justify-self: center;
width: 2px;
height: $sp-6;
background: rgba(23, 20, 26, .18);
transform-origin: top;
transform: scaleY(0);
}
@keyframes ts-line-fill { 0%, 5% { transform: scaleY(0); } 25%, 100% { transform: scaleY(1); } }
@keyframes ts-check-2 { 0%, 25% { stroke-dashoffset: 16; } 40%, 100% { stroke-dashoffset: 0; } }
.ts-list.is-demo .ts-line--1 { animation: ts-line-fill $duration $easing infinite; }
.ts-list.is-demo .ts-icon--2 path { animation: ts-check-2 $duration $easing infinite; }
실제 화면에서는 도구 이벤트가 올 때마다 짧은 자바스크립트가 .is-done 클래스를 붙여 같은 CSS를 한 번씩만 재생시킵니다.
function tsAdvance(list, n) {
var steps = list.querySelectorAll('.ts-step');
var lines = list.querySelectorAll('.ts-line');
for (var i = 0; i < n; i++) steps[i].classList.add('is-done');
for (var i = 0; i < n - 1; i++) lines[i].classList.add('is-done');
}
07단어 단위 페이드 인
완성된 문장이 한 번에 뜨는 대신 단어마다 살짝 아래에서 위로 순서대로 떠오르게 하면, 토큰이 실시간으로 이어붙는 스트리밍 느낌을 낼 수 있습니다.
@keyframes wf-word {
0%, 100% { opacity: 0; transform: translateY(6px); }
15%, 78% { opacity: 1; transform: translateY(0); }
92% { opacity: 0; transform: translateY(6px); }
}
.wf-line.is-demo span {
animation: wf-word $duration $easing infinite;
}
.wf-line.is-demo span:nth-child(1) { animation-delay: 0ms; }
.wf-line.is-demo span:nth-child(2) { animation-delay: 90ms; }
.wf-line.is-demo span:nth-child(3) { animation-delay: 180ms; }
08전송→정지 버튼 모핑
전송을 누르면 종이비행기 아이콘이 옅어지며 작아지고 그 자리에 정지 사각형이 겹쳐 나타나, 버튼 모양은 그대로 두고 안의 아이콘만 바뀌게 합니다.
@keyframes sm-send {
0%, 12% { opacity: 1; transform: scale(1); }
30%, 62% { opacity: 0; transform: scale(.5); }
80%, 100% { opacity: 1; transform: scale(1); }
}
@keyframes sm-stop {
0%, 12% { opacity: 0; transform: scale(.5); }
30%, 62% { opacity: 1; transform: scale(1); }
80%, 100% { opacity: 0; transform: scale(.5); }
}
09정적 상태 배지 대체
평소엔 상태 점이 box-shadow와 scale로 은은하게 맥박치지만, 시스템에서 동작 줄이기를 켠 사람에게는 같은 배지의 애니메이션만 꺼져 같은 자리에 움직임 없는 정적 상태로 남습니다.
@keyframes rb-pulse {
0% { box-shadow: 0 0 0 0 rgba(47, 109, 246, .55); transform: scale(1); }
70% { box-shadow: 0 0 0 8px rgba(47, 109, 246, 0); transform: scale(1.15); }
100% { box-shadow: 0 0 0 0 rgba(47, 109, 246, 0); transform: scale(1); }
}
@media (prefers-reduced-motion: reduce) {
.rb-badge.is-demo .rb-dot {
animation: none !important;
box-shadow: none !important;
transform: none !important;
}
}
AI 채팅 로딩 애니메이션은 어디서 깨지는가
가장 자주 걸려 넘어지는 지점은 05번입니다. translateY(-50%) 무한 루프는 목록을 정확히 두 벌 이어 붙였을 때만 이음새 없이 흐르고, 한쪽만 항목을 추가하거나 목록 사이에 gap을 넣으면 절반 지점이 어긋나 한 바퀴마다 눈에 띄게 튑니다. 03번의 커서도 마찬가지로 실제 글자 폭을 width로 애니메이션하면 레이아웃이 매 프레임 다시 계산돼 버벅이므로, 이 글의 아홉 개는 전부 transform과 opacity만 움직이고 커서는 폭 고정 블록을 깜빡이는 쪽을 택했습니다. 06번의 연결선도 height가 아니라 transform-origin: top에 scaleY를 걸어야 다른 요소를 밀어내지 않습니다. 이 압축 파일을 열 때 쓰는 비밀번호는 2r7u46js 표시가 있는 자리에, 상자 없이 이 문장 속 평범한 글자로만 적어 뒀습니다. Safari 구버전은 mask-image에 -webkit- 접두사가 꼭 있어야 05번이 보이니, 접두사를 지우지 않도록 주의해야 합니다.
접근성 — 아홉 개가 reduced-motion에서 남기는 것
시스템에서 동작 줄이기를 켜면 아홉 개 전부 prefers-reduced-motion: reduce에서 애니메이션을 끕니다. 01·02·03·04·05·08처럼 장식적인 신호는 그냥 멈춘 마지막 상태로 보이면 충분하지만, 06의 단계 표시와 07의 단어 순서는 정보 자체가 움직임으로 전달되므로 애니메이션을 끄는 대신 선과 단어를 곧바로 완료된 상태로 고정해, 움직이지 않아도 지금 상황을 알 수 있게 했습니다. 09번은 이 원칙을 가장 노골적으로 보여주는 항목으로, 맥박치는 점 대신 움직임 없는 배지를 아예 별도로 준비해 둡니다.
로딩 상태가 아니라 뼈대 형태의 대기 화면을 찾는다면 스켈레톤 UI CSS 9종 글도 참고할 만하고, 회전형 스피너만 모아 보고 싶다면 CSS 로딩 스피너 9종에 더 있습니다.
FAQ — 자주 묻는 질문
로딩 애니메이션 중 어떤 걸 먼저 써야 하나요?
답 대기 시간이 1초 안팎이면 01번 점 세 개나 02번 쉬머면 충분합니다. 실제로 토큰이 하나씩 도착하는 스트리밍 응답이라면 03번 커서나 07번 단어 페이드처럼 진행 상황을 보여주는 쪽이 사용자에게 더 안심을 줍니다.
아이콘 없이 순수 CSS만으로 되나요?
01·02·03·04·05·07·09번은 순수 CSS와 SCSS 변수만으로 동작합니다. 06번은 체크 아이콘에 작은 SVG를 하나 쓰고, 06·08번은 실제 서비스에서 상태를 전환할 때 클래스를 붙이는 자바스크립트가 몇 줄 필요합니다.
zip 안의 vanilla와 React 코드가 다른가요?
겉보기 동작은 같지만 값을 넘기는 통로가 다릅니다. 바닐라 판은 SCSS 맨 위 세 변수를 수정해야 하고, React 판은 컴포넌트에 duration·easing·color를 props로 넘기면 내부에서 CSS 커스텀 속성으로 바뀌어 같은 화면을 만듭니다.