AppSidebar: 웹 접근성(WAI-ARIA) 준수 및 rAF 인터랙션 최적화

작성일: 2026년 2월 26일 오후 12:33
조회수: 39

1) 문제 상황 (Problem)

  • 접근성 결여: 초기 사이드바 리사이즈 기능은 마우스 드래그에만 의존하여, 키보드 사용자 및 스크린 리더 사용자가 사이드바 너비를 조절할 수 없는 장벽이 존재함.
  • 성능 병목(Jank): mousemove 이벤트 발생 시마다 즉각적인 setState와 레이아웃 연산이 수행됨. 이로 인해 브라우저의 주사율(60Hz)을 초과하는 과도한 렌더링 시도가 발생하여 메인 스레드에 부하를 주고, 특히 저사양 환경에서 화면이 끊기는 현상이 관찰됨.

2) 해결 과정 (Approach)

Step 1: WAI-ARIA 표준 기반 접근성 강화

  • 리사이즈 핸들을 단순한 div가 아닌 role="separator"로 정의하고, 현재 너비를 실시간으로 전달하는 aria-valuenow 속성을 부여함.
  • tabIndex={0}과 키보드 핸들러(Arrow, Home, End)를 추가하여 포인팅 장치 없이도 정밀한 제어가 가능하도록 개선함.

Step 2: requestAnimationFrame(rAF)을 통한 렌더링 스케줄링

  • 브라우저의 렌더링 파이프라인과 자바스크립트 실행 타이밍을 동기화하기 위해 rAF 도입.
  • 이벤트 큐에 쌓인 고빈도 mousemove 요청을 다음 프레임 직전에 단 한 번만 처리하도록 설계하여 메인 스레드의 점유율을 최적화함.

3) 최종 코드 (Final Solution)

  • 마우스와 키보드 모두 대응 가능하며, rAF를 통한 렌더링 최적화 로직을 결합함.

    tsx
    // 주요 속성: role="separator", aria-valuenow, tabIndex={0}
    // 주요 로직: rAF 기반의 mousemove 스로틀링 및 키보드 이벤트 분기 처리
    
    export function AppSidebar() {
      // ... 생략 (상태 관리 로직)
    
      // 1. 키보드 접근성 핸들러 (ArrowLeft/Right, Home, End 대응)
      const handleKeyDown = useCallback(
      (e: React.KeyboardEvent<HTMLDivElement>) => {
        if (e.key === "ArrowLeft" || e.key === "ArrowRight") {
          e.preventDefault();
          const delta = e.key === "ArrowLeft" ? -KEYBOARD_STEP : KEYBOARD_STEP;
          const nextWidth = Math.min(
            MAX_SIDEBAR_WIDTH,
            Math.max(MIN_SIDEBAR_WIDTH, sidebarWidth + delta)
          );
    
          setSidebarWidth(nextWidth);
          updateSidebarWidth(nextWidth);
        }
    
        if (e.key === "Home") {
          e.preventDefault();
          setSidebarWidth(MIN_SIDEBAR_WIDTH);
          updateSidebarWidth(MIN_SIDEBAR_WIDTH);
        }
    
        if (e.key === "End") {
          e.preventDefault();
          setSidebarWidth(MAX_SIDEBAR_WIDTH);
          updateSidebarWidth(MAX_SIDEBAR_WIDTH);
        }
      },
      [sidebarWidth, setSidebarWidth, updateSidebarWidth]
    );
    
      // 2. rAF 기반 마우스 리사이즈 최적화 (mousemove)
      useEffect(() => {
        let frameId: number | null = null;
        const handleMouseMove = (e: MouseEvent) => {
          if (!isResizing) return;
          if (frameId) cancelAnimationFrame(frameId);
    
          frameId = requestAnimationFrame(() => {
            const nextWidth = Math.min(MAX_WIDTH, Math.max(MIN_WIDTH, e.clientX));
            setSidebarWidth(nextWidth);
          });
        };
        // ... 이벤트 등록 및 정리(Cleanup) 로직
      }, [isResizing]);
    
      return (
        <div
          role="separator"
          aria-valuenow={sidebarWidth}
          aria-valuemin={MIN_WIDTH}
          aria-valuemax={MAX_WIDTH}
          tabIndex={0}
          onKeyDown={handleKeyDown}
          // ... 스타일 및 이벤트 바인딩
        />
      );
    }

4) 결과 (Result): 데이터 기반 성능 검증

image.png

rAF 적용 전

image.png

rAF 적용 후

Chrome DevTools의 Performance 탭을 통해 최적화 전후 지표를 정량적으로 분석함.

지표최적화 전 (Direct Update)최적화 후 (rAF 적용)성과
Animation Track프레임 누락(Red Triangles) 다수 발생프레임 누락 지표 거의 소멸시각적 부드러움 확보
Main ThreadLong Task로 인한 스레드 병목 현상 관찰작업 단위 분절화로 부하 분산시스템 응답성 안정화
인터랙션 경험드래그 시 화면 끊김(Jank) 현상 체감60fps에 준하는 매끄러운 반응성 제공UX 품질 향상
  • 인사이트: 단순히 INP(Interaction to Next Paint) 수치(81ms → 86ms)에만 집중하지 않고, 사이드바 리사이즈와 같은 지속적인 인터랙션에서 중요한 **'프레임 연속성(Smoothness)'**을 확보하는 데 집중함.
  • 성과: 애니메이션 트랙 내 프레임 누락 지표를 약 80% 이상 개선하여 저사양 환경에서도 지연 없는 사용자 경험을 구축함.

무제.gif

0개의 댓글
💬

아직 댓글이 없습니다

첫 번째 댓글을 작성해보세요!