본문 바로가기

FrontEnd

CSS 애니메이션 : 접근성

반응형

전정기관 장애가 있는 사람들에겐 잘못된 애니메이션이 불쾌감을 유발할 수 있음

미디어 쿼리의 prefers-reduced-motion를 통해 애니메이션 효과를 줄일 수 있는 옵션 제공하기

1. 애니메이션 디폴트, 설정으로 끄기

.fancy-box {
  width: 100px;
  height: 100px;
  transform: scale(1);
  transition: transform 300ms;
}
.fancy-box:hover {
  transform: scale(1.2);
}
@media (prefers-reduced-motion: reduce) {
  .fancy-box {
    transition: none;
  }
}

2. 애니메이션 없음 디폴트, 설정으로 on

.fancy-box {
  width: 100px;
  height: 100px;
  transform: scale(1);
  /* No more `transition` here! */
}
.fancy-box:hover {
  transform: scale(1.2);
}
@media (prefers-reduced-motion: no-preference) {
  .fancy-box {
    transition: transform 300ms;
  }
}

주의사항 : 전역적 애니메이션 없애기

@media (prefers-reduced-motion: reduce) {
  * {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
    scroll-behavior: auto !important;
  }
}

위 스니펫의 문제점

  • prefers-reduced-motion 미디어 쿼리가 지원되지 않으면 애니메이션이 기본적으로 활성화되므로 이전 브라우저/운영 체제를 사용하는 사람들에게는 도움이 되지 않습니다.
  • 이 접근 방식은 특정 JS 애니메이션 라이브러리(예: React Spring)가 사용되는 경우 역효과를 일으켜 빠르고 어지러운 모션을 유발할 수 있습니다.
  • 모든 애니메이션을 비활성화할 필요는 없습니다.
가장 좋은 방법은 "prefers-reduced-motion " 설정을 활성화한 상태에서 제품을 주기적으로 테스트하여 모든 것이 의도한 대로 작동하는지 확인하는 것입니다.

자바스크립트에서 미디어 쿼리 접근

미디어 쿼리는 전적으로 CSS 내에서 발생하는 애니메이션(예: 전환, 키프레임 애니메이션)에 적합합니다.
하지만 CSS를 통해 완전히 수행할 수 없는 다양한 타입의 애니메이션이 있습니다.
  • 스프링 물리학을 사용한 애니메이션.
  • 커서 좌표, 스크롤 위치 또는 기타 "환경" 요소와 관련된 애니메이션.
  • HTML5 캔버스 애니메이션.
  • 특정 종류의 SVG 애니메이션.
다행히 JS 내에서 미디어 쿼리의 값에 액세스할 수 있습니다. 다음은 스니펫입니다.
function getPrefersReducedMotion() {
  const mediaQueryList = window.matchMedia(
    '(prefers-reduced-motion: no-preference)'
  );
  const prefersReducedMotion = !mediaQueryList.matches;
  return prefersReducedMotion;
}​
이 함수는 사용자가 reduced된 움직임을 선호하거나("모션 감소" 확인란을 선택함)
사용자가 이전 브라우저를 사용 중이고 실제 선호도가 무엇인지 모르는 경우 true를 반환합니다.
false를 반환하면 사용자에게 preference가 없다는 의미이므로 애니메이션을 활성화해야 합니다.
 
이벤트 리스너를 사용하여 이 값이 변경될 때 업데이트할 수도 있습니다.
const mediaQueryList = window.matchMedia(
  '(prefers-reduced-motion: no-preference)'
);
const listener = (event) => {
  const getPrefersReducedMotion = getPrefersReducedMotion();
};
mediaQueryList.addListener(listener);
// Later:
mediaQueryList.removeListener(listener);

리액트에서 위 로직 사용하기

훅으로 로직을 옮겨야 합니다.

“Accessible Animations in React”를 참고하세요


Motion vs. Animation

모든 애니메이션에 모션이 포함되는 것은 아닙니다.
예를 들어 무언가를 페이드 인하거나 요소의 텍스트 색상을 변경할 수 있습니다.
해당 애니메이션에서 "움직이는" 것은 없습니다.
 
저는 전정 장애가 있는 사람들과의 인터뷰를 통해 읽었으며
가장 일반적인 트리거는 매우 정교한 움직임인 경향이 있습니다.
  • 멀티 레이어 시차 애니메이션
  • 페이지 전체 전환
  • 큰 스위핑 동작
모든 애니메이션을 비활성화해야 합니까? 아니면 커다른 움직임이 있는 것들만 비활성화 하나요?
제가 전문가가 아니라서 확실한 답변을 드릴 수는 없지만 제 생각은 공유할 수 있습니다.
요소가 꽤 많은 픽셀을 이동하는 경우 비활성화합니다.
그리고 요소의 불투명도가 모션과 함께 변경되는 경우(여러 요소 간의 엇갈린 페이드) 비활성화합니다.
 
그러나 색상 변경이나 약간의 페이드 인 및 아웃을 비활성화하지 않습니다.
그리고 요소가 몇 픽셀만 이동하는 경우(예: 버튼 호버) 해당 요소도 그대로 유지할 수 있습니다.
혹은 애니메이션을 "가벼운" 대안으로 전환합니다. 다음은 예입니다.

인앱 설정?

Duolingo 및 Discord와 같은 일부 응용 프로그램에는 애니메이션을 비활성화하는 특정 사용자 설정이 있습니다.
3개의 서로 다른 그룹을 만들 수 있습니다.

표준 경험: 전체 애니메이션.

OS에서 "모션 줄이기"를 선택한 사람들: 가장 문제가 되는 애니메이션이 제거되거나 더 가벼운 대안으로 전환됩니다.

인앱 설정에서 애니메이션을 비활성화하는 사람들: 애니메이션이 전혀 없습니다.

이것은 상당한 유지 관리 오버헤드이며 대부분의 앱에서 가치가 없을 것입니다.
그러나 세계적 수준의 사용자 경험을 구축하고 싶다면
이것은 먼 길을 갈 수 있는 일종의 작은 세부 사항입니다.

예제 : 애니메이션 감소 옵션이 있는 경우, 페이드 인, 아니면 슬라이드인 

새로고침해서 우측 하단 ?가 페이드 인 하는지, 슬라이드 인 하는지 확인해보세요.

(옵션은 OS 설정에서 세팅하세요. 설정은 클릭해서도 확인하실 수 있습니다)

 

<style>
    @keyframes slide-in {
      from {
        transform: translateY(
          calc(100% + var(--spacing))
        );
      }
      to {
        transform: translateY(0%);
      }
    }
    @keyframes fade-in {
      from {
        opacity: 0;
      }
      to {
        opacity: 1;
      }
    }
    @media (prefers-reduced-motion: no-preference) {
      .help-circle {
        animation-name: slide-in;
      }
    }
    .help-circle {
    display: grid;
    place-content: center;
    width: 60px;
    height: 60px;
    color: white;
    background: slateblue;
    border-radius: 50%;
    border: 3px solid white;
    box-shadow: 0px 2px 8px
        hsl(0deg 0% 0% / 0.1),
      0px 4px 16px hsl(0deg 0% 0% / 0.1),
      0px 8px 32px hsl(0deg 0% 0% / 0.1);
    cursor: pointer;
          --spacing: 32px;
      position: fixed;
      bottom: var(--spacing);
      right: var(--spacing);
      animation: fade-in 500ms backwards;
      animation-delay: 1000ms;
  }
  .help-circle img {
    width: 32px;
    height: 32px;
  }

  .visually-hidden {
    position: absolute;
    overflow: hidden;
    clip: rect(0 0 0 0);
    height: 1px;
    width: 1px;
    margin: -1px;
    padding: 0;
    border: 0;
  }
  .wrapper{
    width:100%;
    height:200px;
  }
</style>

<div class="wrapper">
  <button class="help-circle">
    <img
      alt=""
      src="https://courses.joshwcomeau.com/cfj-mats/help-white.svg"
    />
    <span class="visually-hidden">
      Access help center
    </span>
  </button>
</div>
반응형