Frontend/웹 접근성

웹 접근성 - 모바일 링크(a) 요소 내 초점 분리 이슈 해결법

둉이 2022. 12. 2. 22:49

증상

스마일 프레시에서 발생하는 초점 분리

 

Android/iOS 모바일 기기에서 TalkBack, VoiceOver 등의 스크린 리더를 사용하여 화면을 읽는 경우, 링크(a 태그) 요소 내에 위치한 모든 텍스트 요소에 초점이 잡히는 문제가 발생한다.

 

G마켓의 스마일프레시 모바일 화면을 예시로 가져왔다.

 

스마일프레시의 아이템 카드는 a 태그로 감싸져 있으며, a 태그 내에 위치한 span, p 태그 내 텍스트 요소에도 초점이 옮겨가는 문제가 발생하고 있다.

<a
  href="http://mitem.gmarket.co.kr/Item?goodscode=2522267922"
  class="gds-item-card"
>
  <div class="gds-thumbnail">
    <span class="gds-thumbnail__label gds-theme-bg-color">1</span>
    <span class="gds-thumbnail__image">
      <img
        class="image no-js-400?ver=1669796444"
        src="//gdimg.gmarket.co.kr/2522267922/still/400?ver=1669796444"
        alt="22년산 이맛쌀 20kg"
      />
      <noscript></noscript>
    </span>
  </div>
  <div class="gds-item-card__info">
    <div class="gds-item-card__info-inner">
      <span class="gds-item-card__price">
        <em class="gds-item-card__price-num">45,820</em>원
      </span>
      <div class="gds-item-card__sale">
        <span class="for-a11y">할인율</span>
        <span class="gds-item-card__sale--percent">14%</span>
        <span class="for-a11y">할인 전 금액</span>
        <del class="gds-item-card__sale--text">
          <span class="gds-item-card__sale--price">53,900</span>
          <span class="gds-item-card__sale--won">원</span>
        </del>
      </div>
      <p class="gds-item-card__title">22년산 이맛쌀 20kg</p>
      <div class="gds-item-card__score">
        <i class="gds-icon gds-icon-star-small"></i>
        <span class="for-a11y">평점</span>
        <span class="gds-item-card__score-number">4.8</span> 
        <span class="for-a11y">후기</span>
        <span class="gds-item-card__score-count">(5,306)</span>
        <span class="for-a11y">건</span>
      </div>
      <div class="gds-item-card__nudging-group">
        <span class="gds-nudging-label">
          <i class="gds-icon gds-icon-coupon-small-green500"></i>15% 쿠폰
        </span>
        <span class="gds-nudging-label">
          <i class="gds-icon gds-icon-credit-card-small-green500"></i>카드
          10%
        </span>
      </div>
    </div>
  </div>
</a>

 

실제로 초점이 잡혀야 할 링크 요소 외에도 불필요한 초점이 늘어나기 때문에 탐색에 대한 depth도 늘어나게 되므로 스크린 리더 사용자는 웹 사이트 이용에 굉장한 불편을 겪는다.

 

원인은 스크린 리더 자체의 문제로, 원래는 iOS VoiceOver에서만 발생했지만 최근에는 Android Talkback에서도 발생하고 있다.

 

구글링을 해보니 네이버 팀에서도 해당 문제 관련하여 iOS 측에 문의를 했고, 개선하겠다는 답장을 받았다고 한다.

 

하지만 대체 언제...?

 

모바일 접근성을 위한 널리 접근성팀의 노력

널리 알리는 기술 소식 다양한 접근성과 사용성, UI 개발에 대한 소식을 널리 알리고 참여하세요! Spread your knowledge! 구독 아티클 목록 모바일 접근성을 위한 널리 접근성팀의 노력 Nts Nuli 2022-09-29

nuli.navercorp.com

 

 

 

해결 방법

초점을 합치기 위해 사용되는 널리 알려진 방법으로는 크게 두 가지가 있다.

 

 

role="text" 속성 추가

초점 대상인 a 태그에 role="text" 속성을 추가해 주면 해당 요소의 하위 텍스트 요소에는 스크린 리더 초점이 잡히지 않는다.

 

여기서 알아둬야 할 점은, role="text"는 w3c Role Attribute에는 포함되지 않는 비표준 속성이다.

 

표준은 아니지만 iOS Safari를 포함하여 데스크톱의 거의 모든 브라우저에서 지원되고 있다.

 

이 방법의 최대 단점은 Android에서는 초점 합치기가 동작하지 않는다는 점이다.

 

따라서 크로스 브라우징을 위해서는 아래 방법을 이용해야 한다.

 

 

role="none" aria-hidden="true" 속성 추가

다른 방법으로는 초점에서 제외할 요소에 role="none" aria-hidden="true" 속성을 추가한 후, 가장 상위 요소인 a 태그에 aria-label 속성으로 요소 내 모든 텍스트를 추출하여 값으로 넘겨주는 방법이다.

 

텍스트 값 추출 방법은 아래와 같이 a 태그의 innerText에 정규표현식을 사용하면 쉽게 텍스트만 추출할 수 있는데, 추출한 텍스트를 a 태그의 aria-label 속성에 넣어주면 된다.

const text = a_태그.innerText.replace(/\r\n|\r|\n/g, ' ');

 

그리고 초점이 잡히는 모든 요소에 role="none" aria-hidden="true"를 추가해 주면 초점을 합칠 수 있다.

const linkEl = document.querySelector('a');
const text = linkEl.innerText.replace(/\r\n|\r|\n/g, ' ');

linkEl.setAttribute('aria-label', text);
Array.from(linkEl.querySelectorAll('*'))
  .filter((el) => {
    return !['img', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6'].includes(
      el.tagName?.toLowerCase()
    );
  })
  .forEach((el) => {
    el.setAttribute('role', 'none');
    el.setAttribute('aria-hidden', 'true');
  });

 

 

 

리액트에서의 훅 사용

아래는 리액트에서 사용할 수 있는 커스텀 훅이다.

 

사용 방법은 해당 훅을 import한 후, a 태그를 가리키는 ref를 전달하면 된다.

import { useEffect } from 'react';

const useItemCardLabel = ({ ref }) => {
  useEffect(() => {
    if (!ref) return;

    const text = ref.current.innerText.replace(/\r\n|\r|\n/g, ' ');
    ref.current.setAttribute('aria-label', text);

    Array.from(ref.current.querySelectorAll('*'))
      .filter((el) => {
        return !['img', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6'].includes(
          el.tagName?.toLowerCase()
        );
      })
      .forEach((el) => {
        el.setAttribute('role', 'none');
        el.setAttribute('aria-hidden', 'true');
      });
  }, [ref]);
};

export default useItemCardLabel;

 

초점이 하나로 잘 합쳐진 모습

 

 

참고 자료