-
스켈레톤 + 컴파운드 패턴으로 UX 향상 시키기카테고리 없음 2024. 6. 4. 16:09
최근 웹뷰 개발을 하며 페이지 이동 시에 답답하다는 느낌을 받았다
이는 브라우저 로딩 시간과 네트워크 지연 때문인데, 이 문제를 해결하기 위한 고민과 실행 과정을 공유하고자 한다
웹뷰 구조상 브라우저 로딩자체를 단축하는것은 기술적으로 어렵고
간단하게 기다리는 시간을 지루하지 않게 하면 된다는 결론에 도달했다
그리고 스켈레톤 UI를 도입하는 방법을 검토하기 시작했다
처음엔 가볍게 isLoading이나 Suspense에 스켈레톤을 넣으면 간단하겠군. 하는 생각으로 시작
사전 조사
1. 스켈레톤의 과다한 노출은 ❌
매번 스켈레톤 이미지가 노출되는것은 오히려 덜그럭 거리는 느낌을 준다
화면을 키고 100ms~200ms 사이에는 노출하지 않는다는 분석 결과를 찾아냈다
2. Layout shift를 조심해라
1번에서 스켈레톤을 노출시키지 않는 시간 동안 빈화면이 뜰 것이고 데이터 로딩이 끝나며 UI가 생긴다
이때 급격한 layout shift가 일어나며 사용자가 멀미를 유발할 수 있다
3. 컴파운드 컴포넌트 패턴을 사용하면 좋을거 같다
디자인 시스템을 구현할 때 참고한 Ant Design 에서 발견한 패턴인데 간단히 설명하면
여러 컴포넌트를 하나의 모듈로 묶어 export를 통합하는 방식이다
Card라는 컴포넌트를 사용할 때 함께 export 한 Grid를 사용하고 있는 코드다
원래는 복잡한 컴포넌트를 잘게 나누어 수정과 관리를 용이하게 하기 위한 방법이다
<Item/> <ItemSkeleton/> 같이 따로따로 관리하는 대신
<Item/> <Item.Skeleton/> 형태로 관리하면 스켈레톤과 컴포넌트를 그룹화 할 수 있어
유지보수와 가독성이 크게 향상될 것 같다는 생각이 들었다
도입
1. 먼저 1번 사항을 위한 화면 노출 딜레이 컴포넌트를 구현했다
import styled from "@emotion/styled"; import { useEffect, useState } from "react"; interface Props { children: React.ReactNode; defaultHeight?: number; } const DeferredComponent = ({ children, defaultHeight }: Props) => { const [isDeferred, setIsDeferred] = useState(false); useEffect(() => { // 300ms 뒤에 화면 랜더링 const timeoutId = setTimeout(() => { setIsDeferred(true); }, 300); return () => clearTimeout(timeoutId); }, []); if (!isDeferred) { return <EmptySpace defaultHeight={defaultHeight ?? 0} />; } return <>{children}</>; }; const EmptySpace = styled.div<{ defaultHeight: number }>` height: ${({ defaultHeight }) => defaultHeight}px; width: 100%; `; export default DeferredComponent;
두 가지 기능을 가지고 있다
- 자식 요소를 0.3초 뒤에 노출
- 기본 높잇값 설정
2. 그리고 <Item/> 이라는 예시 컴포넌트를 구현했다
const Item = () => { return <div>~~~ 아이템 내용</div>; }; const Skeleton = () => { return <div>~~~ 아이템 내용에 맞는 스켈레톤 UI</div>; }; Item.Skeleton = Skeleton; export default Item;
두 가지를 합쳐서 코드에 적용해 보면
{isLoading ? ( <DeferredComponent> <Item.Skeleton /> </DeferredComponent> ) : ( <> <Item /> </> )}
이렇게 스켈레톤 UI 적용이 완료된다
적용된 화면을 보기 위해 로딩 3초를 걸어놓고 새로고침 테스트 진행
화면 덜그럭을 없애기 위해 아까 속성으로 넣어둔 defaultHeight에 값을 입력하고 다시 테스트
원한대로
1. layoutShift 없이
2. 0.3초까지 흰 화면이 노출되고
3. 로딩이 완료된 후에 원래 UI를 노출하는 모습이다
잊어버리기 전에 블로그에 정리를 해보았다