웹 접근성 향상시키기 (스크린 리더 편)

HYERI ·2023년 8월 12일
1
post-thumbnail

들어가기 전

MacOS에서 기본으로 탑재되어 있는 VoiceOver를 사용하였다. 스크린 리더 사용자는 윈도우 사용자가 많긴 하지만 내가 가지고 있는 노트북이 맥북 밖에 없어서 VoiceOver를 사용하게 되었다.

VoiceOver는 Cmd + f5를 클릭하면 간단하게 사용할 수 있다.

스크린리더와 함께 수정하다 보니 앞서 키보드 접근성으로 위해 수정했던 것에서 더 발전시켜야할 것들이 보였다.

aria-label와 a11y-hidden

문제점

aria-label과 a11y-hidden을 사용해 스크린 리더에 읽힐 텍스트를 적는 중 언제 무엇을 사용하면 좋을 지 모르겠었다. 대체 텍스트를 추가하면서 어떨 때 어떤 것을 써야할 지 고민이 많았다. 무작정 aria-label을 추가해봤을때 제대로 작동하지 않고 validation에서 misuse 일 수도 있다고 떠서 a11y-hidden을 써야할 곳도 있구나라고 생각했다.

aria-label이란?

aria-label은 웹 접근성을 향상시키기 위한 WAI-ARIA(웹 접근성 이니셔티브 - Accessible Rich Internet Applications) 속성 중 하나이다. 이 속성은 요소에 접근 가능한 이름을 제공하는 데 사용된다. 즉, 해당 요소의 의미나 역할을 설명하는 텍스트를 제공하여 스크린 리더 사용자나 다른 보조 기술을 통해 해당 요소의 의미를 이해할 수 있게 돕는다.

aria-label 속성은 다음과 같은 상황에서 사용될 수 있다:

  • 이미지 버튼: 이미지 버튼에 대체 텍스트를 제공하지 않는 경우, aria-label 속성을 사용하여 버튼의 목적이나 동작을 설명할 수 있다.
	<button aria-label="검색하기"><img src="search.png" alt="검색"></button>
  • 동적 콘텐츠: 동적으로 생성되는 콘텐츠나 엘리먼트에 대한 설명을 제공할 때 사용된다.
	<div role="button" aria-label="삭제하기">x</div>
  • 아이콘 또는 이미지: 아이콘 또는 이미지 요소의 의미를 명시적으로 설명할 때 사용된다.
	<span class="icon" aria-label="메일 보내기"></span>
  • 동적 콘텐츠: 동적으로 생성되는 요소에 대한 설명을 제공할 때 사용된다.
	<div role="button" aria-label="알림 확인">1</div>

aria-label 속성은 해당 요소가 가지고 있는 의미나 역할을 확실히 설명하는 텍스트를 제공하는 것이 중요하다. 이를 통해 스크린 리더 사용자나 보조 기술을 사용하는 사용자들도 웹 콘텐츠를 이해하고 상호 작용할 수 있게 된다.

필요에 따라 aria-labelledbyaria-describedby도 같이 사용하면 좋다. 개인적으로 VoiceOver에 읽히지 않아서 사용하지 않았다. 브라우저나 스크린리더마다 다르다고 하니 사용하기 전에 확인하는 것이 좋다.

  • aria-labelledby 속성은 요소에 접근 가능한 이름을 제공하는 데 사용된다.
  • aria-describedby 속성은 요소에 접근 가능한 설명을 제공하는 데 사용된다.

a11y-hidden

웹 접근성을 고려하여 특정 콘텐츠를 시각적으로 감추면서도 스크린 리더 사용자에게는 정보를 제공하는 방법 중 하나이다. 이를 통해 시각적으로는 보이지 않지만 스크린 리더 사용자에게는 의미 있는 정보를 전달할 수 있다.

참고로 프로젝트에 사용된 a11y-hidden 코드는 이것이다.

.a11y-hidden {
    clip: rect(1px, 1px, 1px, 1px);
    clip-path: inset(50%);
    width: 1px;
    height: 1px;
    margin: -1px;
    overflow: hidden;
    padding: 0;
    position: absolute;
}

언제 aria-label을 쓰고 언제 a11y-hidden를 사용해야 할까?

찾아본 결과 aria-label과 a11y-hidden은 각각 다른 목적과 상황에서 사용된다. 어떤 것을 선택해야 하는지에는 상황과 의도에 따라 달라진다.

aria-label 사용 시나리오:

시각적으로 보이는 콘텐츠에 대체 텍스트 제공: 이미지나 버튼과 같이 시각적으로 보이는 요소에 대체 텍스트를 제공하고 싶을 때 aria-label을 사용한다. 스크린 리더 사용자에게 해당 요소의 역할이나 목적을 설명하는 데 사용된다.

a11y-hidden 사용 시나리오:

레이블이나 안내문을 시각적으로 감추면서 스크린 리더 사용자에게만 제공: 특정 요소의 레이블이나 안내문을 시각적으로는 표시하지 않으면서 스크린 리더 사용자에게만 제공하고자 할 때 a11y-hidden 클래스를 사용한다. 이는 스크린 리더 사용자에게 중요한 정보를 전달하면서 시각적으로는 레이블을 보이지 않게 할 때 유용하다.

일반적으로 aria-label은 시각적으로 보이는 요소에 대한 대체 텍스트를 제공할 때 사용되며, a11y-hidden 클래스는 시각적으로는 숨기고 스크린 리더 사용자에게만 정보를 제공할 때 사용된다. 어떤 것을 선택할지는 웹 접근성의 목표와 콘텐츠의 특성을 고려하여 결정해야 한다.

프로젝트 내 적용 예시

<header>
    <h1><img src="./img/logo.png" alt="STUDY FRONT-END 밴딩머신"></h1>
    <button class="btn-press" aria-label="시작 버튼"><img src="./img/press-start.png" alt="press start">
      	<span class="a11y-hidden">시작하기 위해 화면을 클릭하거나 엔터를 누르세요.</span>
  	</button>
    <div class="slot-wrapper"></div>
  </header>
  • h1으로 들어간 로고는 alt로 충분히 설명되었기에 따로 설명해주지 않았다.
  • button에게는 aria-label을 사용해 시작 버튼이라는 설명을 제공했다. 앞서 설명했던 것과 같이 버튼이 보이는 요소이기에 aria-label을 사용했다.
  • 밑에는 따로 밴딩머신을 시작하기 위한 방법을 추가설명 해놨다.
    • 시작화면을 오직 디자인적인 이유로 만들었는데 이제 생각해보니 웹 접근성 면으로는 매우 떨어지는 디자인이란 것을 매우 느꼈다.

무엇을 쓰는 것에 대한 정답은 없지만 최대한 용도에 맞게 잘 사용하고 스크린리더를 직접 시도해보면서 작업해야할 것 같다.

aria-live: 변경된 값 읽어주기

문제점

동적으로 바뀌는 것들은 스크린리더가 읽어주지 않았다. 그때 aria-live란 속성을 사용해야 한다고 알게 되었다.

aria-live란?

aria-live 속성은 동적으로 업데이트되는 콘텐츠를 스크린 리더 사용자에게 즉시 알릴 때 사용되는 접근성 속성이다. 이 속성을 사용하면 사용자의 주의를 끌고, 변경된 정보를 놓치지 않도록 도와준다.

aria-live 속성은 다양한 값으로 설정될 수 있으며, 다음과 같은 값을 가질 수 있다:

  • off: 업데이트된 내용을 스크린 리더에 읽어주지 않는다.
  • polite: 현재 사용자 작업이 끝날 때까지 업데이트 내용을 큐에 저장하다가 사용자 작업이 종료되면 읽어준다.
  • assertive: 즉시 업데이트 내용을 읽어준다. 이 값을 사용할 때는 신중하게 사용해야 하며, 중요한 정보에만 적용하는 것이 좋다.

aria-live 속성은 일반적으로 동적으로 변경되는 내용이나 상태에 사용된다. 예를 들어, 채팅 메시지, 실시간 업데이트되는 콘텐츠, 알림 등의 경우에 사용될 수 있다.

적용 예시

<div class="button-wrapper">
    <p class="a11y-hidden">이 사이트는 bgm이 자동으로 재생됩니다. 멈추거나 다시 재생하기 위해서는 s를 눌러주세요.</p>
    <p class="notice-playing" aria-live="polite">bgm is playing ~ &#9834</p>
    <button class="btn-pause" type="button" aria-label="bgm 멈춤 버튼" aria-live="polite">
      <span class="text">||</span>
  	</button>
    <button class="btn-reset" type="button">reset</button>
</div>
  • bgm을 끄고 킬때 .notice-playingbtn-pause 내 텍스트와 aria-label 속성이 바뀌게 된다.
  • 동적으로 바뀌는 요소에게 모두 aria-live="polite" 속성을 추가해주었다.
  • 스크린리더로 확인결과 바뀔 때 마다 잘 읽히는 것을 확인할 수 있었다.

또 다른 문제

4000원 입금 후 잔액 액수 스크린 리더

4000원 입금 후 소지금 액수 스크린 리더

  • aria-live 속성을 사용하면 변경된 부분만 스크린 리더 사용자에게 알려준다. 이로써 동적으로 업데이트되는 내용 중에서 실제로 변경된 부분에 대한 정보만을 전달할 수 있다.
  • bgm 버튼의 경우 바뀐 값만 알면 되서 크게 문제가 없었지만 다른 요소의 경우 바뀐 값만 알려줄 경우 혼란이 있을 것 같았다.
  • 예를 들면 잔액과 소지금의 변화의 경우 바뀐 값은 결국 돈의 액수 밖에 없는데 돈의 액수만 읽어주면 무엇이 무엇인지 알 수 없었다.

해결법: aria-live 갱신

  1. 바꾼 내용과 읽어줬으면 하는 내용을 같이 다시 추가한다.
  2. 내용이 변경될때마다, aria-live="polite" 속성을 가진 요소 내의 내용을 갱신한 후 스크린 리더에게 알려주는 방식으로 구현한다.

나는 2번의 방법을 택했다.

적용 예시

  /* aria-live를 글 전체에다 적용하는 함수 */
  allAriaLive(target) {
    target.setAttribute("aria-live", "off");
    target.offsetWidth; // Reflow를 트리거하여 스크린 리더가 내용을 다시 읽도록 함
    target.setAttribute("aria-live", "polite");
    setTimeout(() => {
      target.removeAttribute("aria-live");
    }, 1000);
  }
  • target 요소의 aria-live 속성을 껐다가 다시 켜준다.
  • target 요소의 초기 마크업에 aria-live="polite"을 주면 작동이 안되기 때문에 주면 안된다.
  • 앞서 말한 것과 같이 마크업에 aria-live="polite"가 남아있으면 작동이 안되서 setTimeout으로 1초 있다가 삭제해주었다.

결과

<div class="possessed">
    <p>소지금: 
      <span class="money">50,000원</span> 
      <span class="a11y-hidden">이 남았습니다.</span> 
  	</p>
</div>
  • 5000원 입금 후 소지금 50000원에서 45000원으로 변화 스크린 리더
  • 소지금의 html 코드는 이러하고 스크린리더에는 소지금 액수가 변할 때마다 전부를 읽어주는 것을 확인할 수 있었다.

aria-live: 특정 행동 읽어주기

문제점

앞서 설정했듯이 변경된 값은 모두 잘 읽어주었지만 특정 행동을 했을 때 아무 알림이 없어서 무슨 행동을 했는지 확신하기 힘들었다.

  • 예를 들어 밴딩머신의 특정 아이템을 구매했는데 무슨 아이템인지 말해주지 않아 맞는 아이템을 산 것인지 확정할 수 없었다.

해결법: 새로운 요소 만들기

announceMessage(message) {
    const liveRegion = document.createElement("div");
    liveRegion.setAttribute("role", "region");
    liveRegion.setAttribute("aria-live", "assertive");
  	liveRegion.classList.add("a11y-hidden");
    liveRegion.textContent = message;
    document.body.appendChild(liveRegion);
    setTimeout(() => {
      liveRegion.remove();
    }, 3000);
  }
  • 앞의 방법과 비슷하지만 조금 다르게 아예 새로운 요소를 만들어 aria-live 속성에 assertive 값을 준다
  • assertive 값을 가진 요소는 즉시 업데이트 내용을 읽어준다.
  • 스크린 리더가 읽을 시간을 준 후 삭제한다.

프로젝트 내 적용 예시

  • 각 음료수 버튼을 누를때마다 acconceMessage 함수를 불렀다.
	this.announceMessage(item.dataset.item + " 1개를 장바구니에 담았습니다.");

  • 그 결과 누를때 스크린리더에 원하는 결과를 얻을 수 있었다.

hover 효과

문제점

밴딩머신 내 장바구니 아이템 수량조절을 하기 위해서는 해당 아이템 요소 위에 hover 해야지만 아이템 수량 조절 또는 아이템 삭제 버튼에 접근할 수 있다. 즉 tab 이동으로는 잡히지 않고 잡힌다고 해도 tab으로 버튼을 누를 수 없었다.

  • hover 안했을시
  • hover 했을시

해결법

  1. 해당 아이템을 focus할 수 있는 요소로 변경하거나 tabindex 속성을 부여한다. 밴딩머신 프로젝트 같은 경우 <li>가 하나의 아이템 요소이므로 tabindex 속성을 부여했다.
	currentItem.setAttribute("tabindex", 0);
  1. css에서 hover 했을때만 나오게한 효과를 focus 했을때도 나오게 변경한다.
.cart-item:hover, 
.cart-item:focus {
    .drink-count {
        position: absolute;
        left: 50%;
        transform: translateX(-50%);
        z-index: 10;
    }
    &::after {
        display: inline-block;
    }
    .btn-sub, .btn-add, .btn-remove {
        display: inline-block;
    }
}
  1. tab 대신 다른 방법을 이용해 버튼을 누를 수 있게 한다. 밴딩머신 프로젝트 같은 경우 방향 키보드로 조절할 수 있게 했다.
btnKeyboard(currentItem) {
  currentItem.addEventListener("keydown", (event) => {
    if (event.key === "ArrowLeft") {
      currentItem.querySelector(".btn-sub").click();
      this.allAriaLive(currentItem);
    } else if (event.key === "ArrowRight") {
      currentItem.querySelector(".btn-add").click();
      this.allAriaLive(currentItem);
    } else if (event.key === "ArrowUp") {
      currentItem.querySelector(".btn-remove").click();
    }
  });
}

0개의 댓글