FE
[SvelteKit] UX 개선기: 반응형 디자인과 상태 관리
| 서론
안녕하세요, 팡입니다!
현재 진행 중인 회고 기록 서비스 “Re:Log” 프로젝트에서 사용자 경험(UX)을 개선한 과정을 공유하고자 합니다.
특히 여러 개의 입력 섹션으로 구성된 회고 작성 페이지는
사용자가 가장 많은 시간을 보내는 핵심 영역이었기에, 이곳의 사용성을 높이는 것이 매우 중요했습니다.
이번 포스팅에서는 모바일 환경의 불편함 개선과 코드 효율성 향상을 목표로 진행한 두 가지 핵심 기술 개선 사항을 다룹니다.
| 문제 인식 — 긴 스크롤과 분산된 로직
기존 회고 작성 페이지는 데스크탑의 경우 작성 항목이 세로로, 모바일의 경우 가로로 길게 나열된 구조였습니다.
이는 두 가지의 문제점이 있었는데요?
모바일 사용성 저하
데스크탑에서는 세로로 긴 스크롤을 해야 했고, 모바일에서는 가로로 가려진 영역을 스크롤해야만 발견할 수 있었습니다.
또한, 마지막 항목 작성 후, 최하단의 ‘작성 완료’ 버튼까지 다시 스크롤해야 하는 불편함이 있었습니다.
중복된 로직
여러 컴포넌트에서 window.innerWidth를 개별적으로 확인하며 반응형 처리를 중복 구현하고 있었습니다.
이는 유지보수성과 확장성을 떨어뜨리는 주요 원인이었습니다.
이를 해결하기 위해, 크게 두 가지로 개선해보고자 했습니다.
1. 섹션 간 네비게이션 기능으로 개선하기
2. Svelte Store 기반의 중앙화된 상태 관리로 개선하기
| 해결책 1 — 섹션 간 네비게이션과 반응형 UI 구현
모바일에서의 긴 스크롤 문제를 해결하기 위해,
세로 스크롤 대신 가로 스크롤 기반의 섹션 이동 방식을 적용했습니다.
1) CSS 기반 가로 스크롤 레이아웃
섹션 목록을 감싸는 컨테이너에 overflow-x: auto, white-space: nowrap 속성을 적용하여 가로 스크롤이 가능하게 했습니다.
각 섹션은 flex-shrink: 0 속성으로 너비를 유지합니다.
2) JavaScript 기반 부드러운 스크롤 제어 (Smooth Scrolling)
각 섹션 상단에 이전/다음 섹션 이동 화살표 버튼을 추가했습니다.
클릭 시 부드럽게 해당 섹션으로 이동하는 scrollToSection() 함수를 구현했습니다.
데스크탑(세로 스크롤)과 모바일(가로 스크롤) 환경을 모두 고려했습니다.
// in RetrospectSection.svelte
function scrollToSection(id: string) {
const el = document.getElementById(id);
if (!el) return;
const isMobileNow = window.innerWidth < 640;
if (isMobileNow) {
// ✅ 모바일: 가로 스크롤
const container = document.querySelector('.section-list');
if (container instanceof HTMLElement) {
const left = el.offsetLeft; // 대상 요소의 왼쪽 위치
container.scrollTo({ left, behavior: 'smooth' });
}
} else {
// ✅ 데스크탑: 세로 스크롤
const top = el.getBoundingClientRect().top + window.scrollY - 80; // 헤더 높이 보정
window.scrollTo({ top, behavior: 'smooth' });
}
}3) Svelte 조건부 렌더링을 통한 반응형 제출 버튼
모바일에서는 마지막 섹션 내부에만 ‘작성 완료’ 버튼이 나타나도록 변경했습니다.
기존의 페이지 하단 버튼은 모바일에서 숨김 처리했습니다.
{#if !nextTitle && $isMobile} 조건으로 간단히 제어했습니다.
| 해결책 2 — Svelte Store를 이용한 상태 관리 중앙화
여러 컴포넌트에서 window.innerWidth를 직접 확인하던 중복 문제를
Svelte의 writable Store를 통해 중앙화했습니다.
1) writable 스토어 생성
/lib/stores/write/writeStore.ts에 전역 상태 isMobile을 생성했습니다.
writable 스토어는 어디서든 값을 읽고 업데이트할 수 있는 반응형 컨테이너입니다.
// in writeStore.ts
import { writable } from 'svelte/store';
export const isMobile = writable(false);2) onMount와 resize 이벤트를 통한 동기화
컴포넌트 마운트 시 화면 크기를 확인하고,
resize 이벤트 리스너를 등록하여 동적으로 isMobile 값을 업데이트합니다.
컴포넌트 언마운트 시 이벤트 리스너를 제거하여 메모리 누수 방지도 함께 처리했습니다.
// in RetrospectSection.svelte
import { onMount } from 'svelte';
import { isMobile } from '$lib/stores/write/writeStore';
onMount(() => {
const check = () => isMobile.set(window.innerWidth < 640);
check(); // 초기 실행
window.addEventListener('resize', check);
return () => window.removeEventListener('resize', check); // 클린업
});3) (Auto-subscription)를 이용한 반응형 UI
스토어를 $isMobile로 구독하여, 값이 바뀔 때마다 자동으로 UI가 갱신됩니다.
<!-- in SubmitButtonSection.svelte -->
{#if !$isMobile}
<div class="submit-box">
<button on:click={submitRetrospect}>? 작성 완료</button>
</div>
{/if}| 결론
이번 개선을 통해 Re:Log 회고 작성 페이지는 모바일 환경에서도 훨씬 직관적이고 효율적인 UX를 제공하게 되었습니다.
또한, Svelte Store를 통한 중앙화된 상태 관리로 코드 중복을 제거하고 유지보수성을 크게 향상시켰습니다.
단순히 기능을 구현하는 데서 멈추지 않고, 사용자의 불편을 기술적으로 해결하는 과정을 통해
“사용자 중심의 개발”이 얼마나 중요한지 다시금 느낄 수 있었습니다.
앞으로도 계속해서 더 나은 경험을 만들어가기 위해 개선을 이어가겠습니다!