로딩 스피너 깜빡임의 두 끝 — 앞쪽은 CSS, 뒤쪽은 JS
로딩 스피너의 깜빡임은 양 끝에서 따로 생기는데 CSS는 한쪽만 잡는다.
언제: 스피너가 한 프레임 번쩍하고 사라지거나, 떴다가 하드컷으로 끊길 때. 앞쪽인지 뒤쪽인지부터 판별한다.
| 증상 | 어느 쪽 | 처방 |
|---|---|---|
| 응답이 너무 빨라 스피너가 뜨기 전이나 직후 사라진다 | 앞쪽(leading) | CSS — animation-delay/transition-delay |
| 스피너가 떴는데 너무 짧게 보이고 하드컷된다 | 뒤쪽(trailing) | JS — useMinimumLoading |
절차
- 앞쪽이면 지연을 준다. 응답이 지연보다 빨리 오면 한 프레임도 안 보인다 — 스피너를 통째로 건너뛴다.
- 뒤쪽이면 unmount를
max(응답 시각, 최소 노출 시간)으로 민다. 한 번 뜬 스피너가 최소 노출 시간만큼은 보이게 된다.
확인: 응답을 지연보다 빠르게 만들어 본다 — 스피너가 한 프레임도 안 뜨면 앞쪽이 잡힌 것이다. 응답을 최소 노출 시간보다 빠르게 만들어 본다 — 스피너가 그 시간만큼 유지되면 뒤쪽이 잡힌 것이다.
왜 갈리나: CSS는 시작만 통제한다. 스피너가 보이는 구간은 [뜬 시각, unmount 시각]인데, 지연은 앞쪽을 미루고 지속 시간은 애니메이션 길이일 뿐 요소가 DOM에 남는 시간이 아니다. 뒤쪽 끝은 응답이 언제 오느냐가 정하고 그건 CSS가 모르는 값이다. 그래서 “최소 몇 ms는 보인다”를 CSS만으로는 보장할 수 없다.
함정
- 억제력은 지연에서만 나온다 —
@starting-style단독(지연 0)으론 앞쪽을 못 막는다. - 이미 떴다는 사실은 못 되돌린다. 뒤쪽은 CSS가 가진 두 값(지연·지속 시간)으로 잡을 수 없고 사라지는 시각 자체를 미뤄야 한다 — CSS 밖, JS 타이밍이다.
곁가지 — @starting-style은 같은 뿌리의 반대편이다. transition은 두 상태 사이를 보간하는데, 진입에는 옮겨갈 이전 상태가 없고1 퇴장에는 요소 자체가 이미 사라진다. @starting-style은 앞쪽의 출발값을 대신 정해주고(진입 fade-in), useMinimumLoading은 뒤쪽의 unmount를 미룬다. 뿌리는 같아도 손대는 지점이 달라 별개 결정이다.
Footnotes
-
CSS transition은 요소의 첫 스타일 적용이나
display: none→ 표시 전환에서는 기본적으로 발동하지 않는다.@starting-style이 “무엇에서 출발할지”를 정의해 그걸 가능하게 한다. (@starting-style — MDN) ↩