완성된 동작

// 문제의 코드: css 속성이 잘못된 경우
return (
  <section className="relative rounded-lg overflow-hidden group">
    {/* 이미지 컨테이너 */}
    <div className="aspect-[4/3] max-h-[80vh]">
      <TransformViwer 
        currentImageSrcMetadata={currentImageSrcMetadata}
        isLoaded={mainImageIsLoaded}
      />
    </div>

    <aside
        id="thumbnails-panel"
        ref={thumbnailPanelRef}
        className={`absolute left-0 top-0 bg-black bg-opacity-80 w-full md:w-1/2 h-full overflow-y-auto z-30 ${
          isExpanded ? "opacity-100" : "opacity-0"
        }`}
        role="dialog"
        aria-label="썸네일 갤러리"
        aria-modal={isExpanded}
        aria-hidden={!isExpanded}
      >
        ... 내부 코드
 </aside>

    {/* 이전/다음 버튼 - humbnails 뒤에 렌더링됨 */}
    <button 
      ref={prevButtonRef}
      onClick={handlePrev}
      aria-label="이전 이미지"
      className="absolute left-4 top-1/2 -translate-y-1/2 bg-black/60 text-white px-3 py-5 rounded-2xl z-10 control-visibility"
      tabIndex={0}
    >
      <GrPrevious aria-hidden="true" />
    </button>
    <button 
      ref={nextButtonRef}
      onClick={handleNext}
      aria-label="다음 이미지"
      className="absolute right-4 top-1/2 -translate-y-1/2 bg-black/60 text-white px-3 py-5 rounded-2xl z-10 control-visibility"
      tabIndex={0}
    >
      <GrNext aria-hidden="true" />
    </button>
  </section>
);

문제 상황

이미지 갤러리에서 '이전', '다음' 버튼을 구현했는데, tabIndex={0}을 명시적으로 설정했음에도 불구하고 키보드 탭 키로 해당 버튼에 포커스가 되지 않았습니다. 스타일과 속성은 모두 올바르게 적용되었으나, 어떤 이유에서인지 키보드 네비게이션이 작동하지 않았습니다.

원인 파악

많은 디버깅 끝에 발견한 원인은 의외로 단순했습니다.

1. 숨겨져 있는 썸네일 컴포넌트의 css 속성이

isExpanded ? "opacity-100" : "opacity-0"

와 같이 opacity로 되어있어 ( opacity 속성은 숨겨져 있음에도 tab focus가 가능하다)

관련 내용: https://lim-2.tistory.com/110

[엘리먼트를 안 보이게 하는 다양한 방식과 각각의 장단점

엘리먼트를 안 보이게 하는 다양한 방식과 각각의 장단점CSS에서 요소를 시각적으로 숨기는 여러 방법이 있으며, 각 방법마다 고유한 장단점이 있습니다. 특히 접근성 측면에서 큰 차이가 있습

lim-2.tistory.com](https://lim-2.tistory.com/110)

숨겨져 있는 컴포넌트 내부로 이동한거였습니다

해결책

해결책은 간단했습니다. 엘리먼트의 숨김을 처리하는 css 속성을

isExpanded ? "block" : "hidden"

hidden 
===
{
    display: none
}

으로 바꿔주니 원하는 대로 내부에 foucs 되지않고 외부에 있는 요소들 에게만 이동했습니다.

추가적인 깨달음

설계할 때는 사용자의 자연스러운 행동 패턴을 이해하는 것이 중요합니다. 제가 예측한 사용자 행동 우선순위는 다음과 같습니다.

예상되는 사용자 행동 패턴

  1. 기본 탐색: 좌우 버튼 사용
    • 가장 빈번하게 사용되는 행동입니다.
    • 사용자는 자연스럽게 화살표 버튼이나 키보드 방향키를 통해 이미지 간 이동을 시도합니다.
    • 이는 선형적인 콘텐츠 소비 방식과 일치하여 직관적입니다.
    • 일반적으로 원하는 사진을 찾을 때 좌우 버튼을 가장 우선해서 사용한다고 생각했습니다.
  2. 효율적인 탐색: 썸네일 갤러리 활용
    • 여러 이미지를 빠르게 살펴보고 특정 이미지로 직접 이동하려는 사용자들이 많습니다.
    • 특히 많은 수의 이미지가 있을 때 중간 지점으로 빠르게 이동하는 데 유용합니다.
    • 원하는 자동차의 부위를 빠르게 탐색할 때 사용합니다.
  3. 몰입형 보기: 전체화면 모드
    • 자동차의 이미지를 큰 화면으로 보고 싶을 때 사용합니다
  4. 세부 정보 확인: 확대/축소 기능
    • 원하는 부분을 고르고 자동차의 세부 사항을 보기 위해 확대, 축소 버튼들을 사용합니다.

제가 생각하는 사용자의 행동 패턴이었습니다

탐색을 우선으로 두고 그 뒤에 세부적인 조작이 올 것 이라고 생각했습니다.

 

그래서 tab을 사용한 focus의 순서는 컴포넌트 렌더링의 순서이기 때문에

해당하는 동작들을 담당하는 버튼들을 아래와 같이 배치하여 원하는 동작을 이뤄냈습니다.

// 수정한 코드
return (
    <button 
      ref={prevButtonRef}
      onClick={handlePrev}
      aria-label="이전 이미지"
      className="absolute left-4 top-1/2 -translate-y-1/2 bg-black/60 text-white px-3 py-5 rounded-2xl z-10 control-visibility"
      tabIndex={0}
    >
      <GrPrevious aria-hidden="true" />
    </button>
    <button 
      ref={nextButtonRef}
      onClick={handleNext}
      aria-label="다음 이미지"
      className="absolute right-4 top-1/2 -translate-y-1/2 bg-black/60 text-white px-3 py-5 rounded-2xl z-10 control-visibility"
      tabIndex={0}
    >
      <GrNext aria-hidden="true" />
    </button>
    {/* 썸네일 컨테이너 */}
     <aside
        id="thumbnails-panel"
        ref={thumbnailPanelRef}
        className={`absolute left-0 top-0 bg-black bg-opacity-80 w-full md:w-1/2 h-full overflow-y-auto z-30 ${
          isExpanded ? "block" : "hidden"
        }`}
        role="dialog"
        aria-label="썸네일 갤러리"
        aria-modal={isExpanded}
        aria-hidden={!isExpanded}
      >
        ... 내부 코드
     </aside>

     <section className="relative rounded-lg overflow-hidden group">
    {/* 이미지 컨테이너 */}
    <div className="aspect-[4/3] max-h-[80vh]">
      <TransformViwer 
        currentImageSrcMetadata={currentImageSrcMetadata}
        isLoaded={mainImageIsLoaded}
      />
    </div>
  </section>
);

 

결론

  • 적절한 css 속성 활용으로 의도에 맞는 동작을 하게 함
  • 사용자의 동작을 예상하여 컴포넌트를 배치하여 컴포넌트 렌더링 순서를 조정함

+ Recent posts