웹 페이지에 접근할 때, 긴 로딩 시간을 해결할 가장 좋은 방법은 시간을 단축하는 것이지만 그럴 수 없는 경우 디자인적으로 개선 할 수 있습니다.
사용자 경험을 개선하기 위한 웹사이트와 애플리케이션에서 가장 일반적인 시스템 피드백 형태 중 하나는 대기 애니메이션 진행 표시기 입니다.
Designing for the appearance of speed
[참고: 원글] https://juneuprising.medium.com/designing-for-the-appearance-of-speed-aaabc7f568c2
Designing for the Appearance of Speed
Interface design for an impatient world
juneuprising.medium.com
"느린 사이트는 유저의 감정적 차원을 극적으로 줄였다"
2013년 Luke Wroblewski 화면 경험에서 로딩 스피너를 사용하는 것의 단점에 대해 처음으로 논의했고, 그가 "스켈레톤 화면" 이라고 부르는 것을 지지했습니다.
같은 글의 저자 Bill Chung 은 아래와 같이 스켈레톤 디자인에 대해 연구를 하고 글을 작성하였습니다.
[참고] https://uxdesign.cc/what-you-should-know-about-skeleton-screens-a820c45a571a
Everything you need to know about skeleton screens
How do we know that skeleton screens actually work?
uxdesign.cc
위의 글에서 말하는 "어느 스켈레톤 디자인이 로딩 시간이 더 짧게 느껴졌는가" 은 아래와 같이 말합니다.
- 정적 vs 애니메이션 스켈레톤 👉 애니메이션 스켈레톤
- 펄싱 애니메시연 vs 웨이브 애니메이션 👉 웨이브 애니메이션
- 빠른 파동 vs 느리고 안정적인 파동 👉 느리고 안정적인 파동
- 왼쪽에서 오른쪽으로의 파동 애니매이션 vs 오른쪽에서 왼쪽으로의 파동 애니메이션 👉 왼쪽에서 오른쪽으로의 파동 애니매이션
스켈레톤 화면 이란?
스켈레톤 화면은 화면을 로딩하는 시간 동안 유저에게 나타나는 와이어프레임 형태의 화면입니다.
페이스북, 링크드인, 구글 드라이브, 유튜브 등에서 사용하는 것을 볼 수 있는데요.
로딩 중인 화면을 스켈레톤 말고도 스피너, 자체 디자인 된 로딩 애미메이션 등등으로 사용하는 것을 봤을 텐데요.
대부분 많이 사용하는 스피너UI 와 스켈레톤UI 가 어느 경우에 사용해야 하는지, 각각의 장단점은 무엇인지 확실히 알고 쓰임새에 맞게 사용하면 더욱 사용자경험 개선에 좋을 것 같아 공부해봤습니다.
스피너(Spinner)와 스켈레톤(Skeleton) UI 비교
[참고] https://www.nngroup.com/articles/progress-indicators/
Progress Indicators Make a Slow System Less Insufferable
Users are more satisfied and will wait longer when a site uses wait animations such as percent-done bars and spinners to explain >1 s response times delays.
www.nngroup.com
스피너 : 로딩 중임을 나타내는 애니메이션 아이콘
사용자의 요청이나 액션에 따라 발생하는 진행 중인 작업을 보여줄 때 사용
예로, 버튼을 클릭하여 내용을 불러오거나 어떤 작업을 수행하는 동안 사용
스켈레톤
콘텐츠가 생기면 점진적으로 채워지는 빈 페이지를 사용
초기 로딩 상황에서 전체 페이지를 로드할 때 대기시간 감축 효과를 위해 사용
|
스피너 (Spinner) |
스켈레톤 UI (Skeleton) |
사용 시기 |
|
|
장점 |
|
|
단점 |
|
|
권장 사용
1. 혼합 사용
- 전체 페이지 로딩 👉 스켈레톤
- 부분 데이터 업데이트 👉 스피너
2. 컨텍스트 기반 선택
- 데이터 중심 페이지 👉 스켈레톤
- 단순 액션 피드백 👉 스피너
3. 사용자 경험 기준
- 긴 대기 시간 예상 👉 스켈레톤
- 즉각적 피드백 필요 👉 스피너
적용 [Vue.js]
기존 문제 상황 : 긴 로딩 시간 전체 화면 스피너 적용
프로젝트의 특정 페이지에 접근할 때, 테이블의 데이터를 완전히 불러오는데 5~6초 정도 걸립니다. 기존 구현은 전체 화면 스피너로 데이터가 로드 될 때까지 화면이 블로킹되는 형태였습니다.
그래서 사용자는 어떤 액션도 취할 수 없는 상태가 지속이 되고, 로딩 중의 상태에 대한 피드백이 부족하여 자칫 화면이 렉이 걸린 듯한 느낌을 줬습니다.
👉 Server resone Time : 3.88s (대략 4초)
화면이 뜨기까지 체감시간은 5~6 초 되는 것 같습니다. 화면 전체가 스피너가 돌면서 블락이 되기 때문에 그동안 사용자는 가만히 기다려야만 했는데요.
대부분의 스피너 로딩은 짧은 로딩 시간 (1~2초) 에 사용이 되기 때문에 3초 이상으로 로딩이 지속되는 곳에 스피너 로딩을 사용하게 된다면 아무런 정보 없이 계속 반복되는 것을 보고 사용자가 오류라고 받아들일 수 있게 됩니다.
그래서 시각적으로 정보가 보여지는 로딩인 스켈레톤은 빈 화면 대신에 다음 화면에 나올 것을 미리 보여줘서 긴 로딩시간에 스켈레톤UI로 바꾸는게 맞다고 판단했습니다.
해결방법 : 테이블 스켈레톤 UI 적용
테이블에 스켈레톤 UI를 적용하여 부분적으로 로딩을 처리하였습니다. 다른 UI 요소(필터, 다른 버튼 들..)들은 즉시 렌더링 되어 사용 가능하도록 하여 사용자의 답답함을 해소하였습니다.
스켈레톤 UI 적용
다크모드 UI 적용
기존 : 전체 페이지 로딩바 의 문제점 | 변경 : 점진적 렌더링(Progressive Rendering) -> 테이블에 스켈레톤 UI 적용 |
|
|
코드 구현
1. 컴포넌트 구조
- 테이블 형태의 스켈레톤 UI 구현
- 동적으로 계산된 행(computedRows) 만큼 반복하여 로딩 플레이스홀더 생성
// Skeleton.vue
<template>
<div class="skeleton-table">
<div v-for="n in computedRows" class="skeleton-row">
<div class="skeleton-box" />
</div>
</div>
</template>
`.skeleton-row `
테이블 배경 색상 css
.skeleton-row {
padding: 16px;
display: flex;
justify-content: center;
background-color: var(--color-theme); //색상 지정
}
`skeleton-box`
테이블 row 모양, 웨이브 그라데이션 색상 효과
.skeleton-box {
// 기본 스타일링
height: 20px;
width: 100%;
border-radius: 5px;
display: inline-block;
position: relative;
vertical-align: middle;
overflow: hidden;
background-color: var(--scales-3);
// 애니메이션 효과
&::after {
position: absolute;
top: 0;
right: 0;
bottom: 0;
left: 0;
transform: translateX(-100%);
background-image: linear-gradient(
90deg,
rgba(#fff, 0) 0,
rgba(#fff, 0.3) 20%,
rgba(#fff, 0.8) 50%,
rgba(#fff, 0.3) 80%,
rgba(#fff, 0)
);
animation: shimmer 4s infinite; //4초 주기로 무한 반복, 왼쪽에서 오른쪽으로 이동하는 애니메이션
content: '';
}
}
//반짝이는 효과
@keyframes shimmer {
100% {
transform: translateX(100%);
}
}
2. Props & Data
- height: 컴포넌트 높이 설정 prop (기본값 100%)
- parentHeight: 부모 요소 높이 저장용 데이터
props: {
height: {
default: '100%',
type: String,
}
},
data: {
parentHeight: 500 // 기본 높이
}
3. 높이 계산 로직
- 각 행의 높이를 계산 (content: 20px + padding: 16px * 2)
- 전체 높이를 행 높이로 나누어 필요한 행 수 계산
- 소수점 올림으로 행 수 보정
computed: {
computedRows() {
const rowHeight = 20 + 16 * 2; // 한 행의 높이 (content + padding)
if (this.height === '100%') {
return Math.ceil(this.parentHeight / rowHeight);
}
const containerHeight = parseInt(this.height);
return Math.ceil(containerHeight / rowHeight);
}
}
`table-body`(부모 요소인 테이블) 의 높이가 지정되어 있으면, row 수 계산하여 처리
4. 반응형 처리
- 마운트 시 부모 요소 높이 계산
- 리사이즈 이벤트 리스너 등록
- 메모리 누수 방지를 위한 이벤트 리스너 정리
mounted() {
if (this.height === '100%') {
this.updateParentDimensions();
window.addEventListener('resize', this.handleResize);
}
},
methods: {
updateParentDimensions() {
this.parentHeight = this.$el?.parentElement?.clientHeight || 500;
}
}
참고
New Relic 이 페이지 로드 시간을 측정하는 방법
New Relic이 페이지 로드 시간을 측정하는 방법 | New Relic Documentation
Request queuing 해당 계정에 브라우저 모니터링 및 APM이 연결되어 있는 경우 로드 시간 차트에 표시됩니다. 뉴렐릭에서 요청 큐잉은 요청이 프로덕션 시스템에 입력된 후 해당 요청이 도달하는 사
docs.newrelic.com
Skeleton Loading Animation with Vue.js
[원글] https://markus.oberlehner.net/blog/skeleton-loading-animation-with-vue/
Skeleton Loading Animation with Vue.js - Markus Oberlehner
Learn how to build a pure CSS skeleton loading animation with a pure CSS shimmer animation.
markus.oberlehner.net
'frond-end' 카테고리의 다른 글
GitHub Actions를 이용한 ChatGPT 기반 자동 코드 리뷰 기능 추가하기 (0) | 2025.01.07 |
---|---|
크롬 브라우저 비밀번호 자동 완성, 이렇게 하면 완벽 차단 (feat. formkit) (0) | 2025.01.07 |
웹 폰트 최적화 (Feat. cdn.jsdelivr.net 에러 사태 - 20240502) (0) | 2024.05.01 |
AWS S3 + CloudFront 배포 생성 (1) | 2024.04.28 |
Bitbucket 배포 환경 만들기 (0) | 2024.04.28 |