스트리밍은 대부분의 브라우저와
Developer 앱에서 사용할 수 있습니다.
-
웹 손쉬운 사용의 새로운 기능
맞춤형 컨트롤, SSML 및 대화상자 요소를 통해 풍부하고 손쉽게 사용할 수 있는 웹 앱을 빌드하는 기술을 확인하세요. 다양한 보조 기술에 대해 논의하고 이를 사용하여 웹 앱의 손쉬운 사용 여부를 테스트하는 방법을 배울 수 있도록 도와드립니다.
리소스
관련 비디오
WWDC21
-
다운로드
♪ ♪
안녕하세요. 제 이름은 Tyler이고 WebKit 접근성 팀의 엔지니어입니다 오늘 세션에서는 스크린리더와 같은 보조 기술에 대한 간략한 개요부터 시작하여 최신 웹 접근성을 둘러볼 것입니다
그런 다음 Web Speech API 및 대화 요소에서 사용자 지정 컨트롤, 음성 합성 마크업 언어 또는 SSML과 같은 도구를 사용하여 풍부하고 누구나 접근 가능한 웹 앱을 구축할 수 있는 방법에 대해 설명합니다
그럼, 보조 기술에 대해 먼저 알아보도록 하겠습니다 전 세계적으로 약 7명 중 1명은 주변 세상, 장치 및 웹과 상호 작용하는 방식에 영향을 주는 장애를 가지고 있습니다 사람들은 나이, 기간, 심각도에 관계없이 장애를 경험할 수 있습니다 Apple은 사용자가 자신에게 가장 적합한 방식으로 기기와 상호 작용할 수 있도록 다양한 도구를 구축했습니다 이러한 도구에는 장치 사용의 대체 수단을 제공하는 VoiceOver, 스위치 제어, 음성 제어 및 전체 키보드 액세스가 포함됩니다 이러한 도구 및 기타 도구에 대해 자세히 알아보려면 작년에 제공된 "Support Full Keyboard Access in your iOS app"을 확인하세요
실제 웹 페이지에서 이것이 어떻게 적용되는지 보기 위해 VoiceOver를 사용하여 샘플 퀴즈 평가 웹 사이트를 탐색해 보겠습니다 iPad에서 상단 버튼을 세 번 눌러 VoiceOver를 활성화하겠습니다 VoiceOver가 켜져 있습니다 사파리, 사이드바 보기, 버튼
이제 VoiceOver가 활성화된 상태에서 페이지 제목을 눌러 초점을 맞추겠습니다 팝 퀴즈, 제목 수준 1 그리고 오른쪽으로 스와이프하여 이 페이지의 요소 사이를 이동합니다 6개 중 1개: 총 8조각으로 구성된 피자의 1/4에는 몇 조각이 들어 있습니까? 두 조각, 라디오 버튼, 선택 안 함, 네 조각 중 하나 세 조각 네 조각 여섯 조각 다음 질문, 버튼 웹 개발자는 VoiceOver와 같은 기술 사용자가 페이지를 쉽게 사용할 수 있도록 하는 많은 도구를 마음대로 사용할 수 있습니다 예를 들어 Safari에는 버튼, h1부터 h6까지의 시맨틱 HTML 요소, 테이블 요소, 목록 요소 등과 같은 의미론적 HTML 요소에 대한 접근성 지원이 내장되어 있습니다 이러한 시맨틱 HTML 요소를 사용하는 것이 기본값이어야 합니다. 이렇게 하면 모든 브라우저에서 사용자에게 일관되고 쉽게 접근 가능한 경험을 보장할 수 있기 때문입니다 그러나 때로는 시맨틱 HTML로 완전히 충족되지 않고 JavaScript로 사용자 정의 구성 요소를 만들어야 할 필요가 있을 수 있습니다 이 경우 의미 체계가 보조 기술에 적절하게 전달되도록 구성 요소를 ARIA 속성으로 보완해야 할 수도 있습니다 오늘의 두 번째 주제인 사용자 지정 컨트롤로 이동합니다 피자 퀴즈 질문을 더 흥미롭게 만들고 싶었다고 가정해 보겠습니다 우리가 할 수 있는 한 가지는 라디오 버튼을 사용자가 한번의 탭으로 접시에서 조각을 추가하고 제거할 수 있는 사용자 정의 컨트롤로 교체하는 것입니다 이 사용자 지정 컨트롤에 대한 마크업은 div 및 ID로 시작할 수 있습니다
탭 또는 클릭과 상호 작용하는 사용자가 이 구성 요소를 작동할 수 있도록 하려면 클릭 이벤트 리스너를 추가해야 합니다 요소의 ID를 허용하는 생성자를 사용하여 새로운 PizzaControl 클래스를 생성해 보겠습니다
ID로 해당 요소를 가져온 다음 해당 요소에 대한 클릭 이벤트 리스너를 추가합니다
이 리스너는 탭된 위치를 기반으로 새로운 조각의 수를 계산한 다음 해당 값을 업데이트라는 함수에 전달하여 컨트롤을 다시 렌더링합니다 이것은 일부 사용자에게 효과적이지만 모두에게 그렇지는 않습니다
예를 들어 클릭하거나 탭해야 할 위치를 모르는 시각 장애가 있는 사용자는 어떻습니까? 사용자 지정 구성 요소를 구축할 때에는 모든 유형의 보조 기술 사용자가 구성 요소와 상호 작용하는 방식을 항상 고려해야 합니다 이를 염두에 두고 구성 요소에 쉽게 접근할 수 있도록 하는 첫 번째 단계는 "slider" 값을 가진 역할 속성을 지정하는 것입니다 우리의 컨트롤은 슬라이더 모델에 아주 잘 매핑됩니다 최소값은 0 조각, 최대값은 8 조각이며 현재 값은 4 조각입니다 또한 구성 요소가 키보드 및 기타 비 터치 인터페이스 사용자에게 초점을 맞출 수 있도록 tabindex 0을 추가해야 합니다
또한 몇 가지 ARIA 속성도 추가해야 합니다
Aria-valuemin 및 aria-valuemax는 이 슬라이더의 최소값과 최대값을 보조 기술에 알립니다
이러한 속성은 기본 범위 유형 입력에 사용할 수 있는 최소 및 최대 속성과 유사합니다
다음으로 컨트롤의 현재 값을 전달하기 위해 aria-valuenow를 추가해 보겠습니다
또한 aria-valuetext를 사용하여 4 조각인 현재 값에 대한 보다 유용한 설명을 제공합니다
이제 컨트롤을 포커스 가능한 슬라이더로 설정했으므로 보조 기술에서 컨트롤 값에 대한 업데이트를 처리해야 합니다 iOS에서 VoiceOver는 한 손가락으로 위로 스와이프하여 슬라이더를 증가시키고 아래로 스와이프하여 슬라이더를 감소시켜 슬라이더 조정을 용이하게 합니다 Safari는 이러한 제스처를 쉽게 처리할 수 있는 방법을 제공합니다 VoiceOver 사용자가 슬라이더에 포커스를 두고 위로 쓸어올리면 Safari는 자동으로 오른쪽 화살표 키 이벤트를 시뮬레이션합니다 마찬가지로 VoiceOver 사용자가 슬라이더에 포커스를 두고 아래로 스와이프하면 화살표 키 왼쪽 이벤트가 시뮬레이션됩니다 이러한 시뮬레이션된 이벤트는 실제 키 누르기와 동일하게 작동하므로 키 이벤트 리스너에서 처리할 수 있습니다 도구 벨트에 대한 이 새로운 지식을 사용하여 피자 컨트롤에 키다운 수신기를 추가해 보겠습니다 활성화된 키가 오른쪽 화살표 또는 위쪽 화살표인 경우 현재 조각 양에 1을 더한 값으로 컨트롤을 업데이트합니다 마찬가지로 활성화된 키가 왼쪽 화살표 또는 아래쪽 화살표인 경우 현재 조각 수에서 1을 뺀 값으로 컨트롤을 업데이트합니다 이 키 이벤트 리스너를 추가하면 VoiceOver 사용자뿐만 아니라 웹 앱이 키보드에 접근할 수 있다는 것에 대부분 또는 전적으로 의존하는 전체 키보드 접근 사용자에게도 도움이 됩니다 두 이벤트 리스너가 모두 설정되었으므로 이제 업데이트 기능도 정의해야 할 것입니다 먼저 주어진 값이 0에서 8 사이의 범위 내에 있는지 확인하고 저장된 조각 수 상태를 이 값으로 업데이트합니다 다음으로 컨트롤의 시각적 표현과 ARIA 표현을 모두 업데이트해야 합니다 사용자 지정 구성 요소를 만들 때 구성 요소의 시각적 표현을 업데이트하는 경우 ARIA 표현을 업데이트하는 방법도 생각하는 것이 좋습니다
이 경우 aria-valuenow 및 aria-valuetext 속성을 모두 업데이트해 새로운 제어 상태의 보조기술을 사용자에게 알려야 합니다
aria-valuenow를 현재 조각 수로 설정하는 것으로 시작하겠습니다
다음은 조각 수를 설명할 때 보다 인간 친화적으로 할 수 있도록 aria-valuetext에 '슬라이스' 또는 '조각'이라는 단어를 추가하도록 합니다 자, 이제 모든 것이 준비되었으므로 퀴즈 평가 웹 앱으로 돌아가서 VoiceOver를 사용한 경험이 어떤지 확인하겠습니다 피자 컨트롤을 눌러 초점을 맞추는 것부터 시작하겠습니다 4 조각, 조절가능 한 손가락으로 위 또는 아래로 스와이프하여 값을 조정합니다 VoiceOver가 슬라이더의 초기 값인 4 조각을 읽고 조정가능하다고 말했습니다 VoiceOver의 프롬프트에 따라 위로 스와이프하여 선택한 조각 수를 늘릴 수 있습니다 5 조각, 6 조각 그리고 아래로 스와이프하여 선택한 조각의 수를 줄이세요 5 조각, 4 조각 이러한 변경 사항을 적용하면 이제 사용자 지정 슬라이더 구성 요소에 훨씬 더 쉽게 접근할 수 있습니다 이제 Web Speech API에서 SSML을 사용하여 모든 사용자를 위한 보다 풍부한 경험을 구축하는 방법에 대해 이야기해 보겠습니다 Web Speech API는 오디오 입력을 위한 인터페이스인 SpeechRecognition와 텍스트 음성 변환 오디오 출력을 위한 SpeechSynthesis의 두 가지의 기본 JavaScript 인터페이스로 구성됩니다
Web Speech는 웹 앱에 음성 지원 또는 음성 전용 인터페이스를 제공하는 기능을 제공합니다
이것은 마우스, 키보드 또는 터치스크린과 같은 다른 제어 수단을 사용하는 데 어려움을 겪을 수 있는 운동 장애가 있는 사용자에게 유용할 수 있습니다 Safari의 SpeechSynthesis의 새로운 기능은 SSML을 사용하여 텍스트가 말하는 방식을 조작하는 기능입니다 SSML에는 수많은 기능이 있습니다 예를 들어, break 요소를 사용하여 선택한 시간에 음성에 일시 중지를 삽입할 수 있습니다 사용자에게 숨을 들이쉬고 내쉬도록 요청할 수 있습니다
음소 요소를 사용하여 "tomayto" 대 "tomahto"와 같은 단어의 발음을 제어할 수 있습니다
운율 요소를 사용하면 음성 텍스트의 높낮이 속도 및 볼륨을 조작할 수 있습니다 그리고 이것들은 SSML 기능의 극히 일부일 뿐입니다 자세한 내용은 w3.org에서 SSML 사양을 확인하세요 SSML에 대한 새로운 지식을 활용해 보겠습니다 퀴즈의 마지막 질문에 대해 학생들에게 "물"이라는 문구의 정확한 스페인어 번역이 있는 라디오 버튼을 선택하도록 요청합니다 사용자가 버튼을 눌러 질문과 답변을 텍스트 음성 변환으로 읽을 수 있도록 하고 스페인어 로케일 음성으로 스페인어 문구를 읽을 수 있도록 함으로써 이 질문을 더 매력적으로 만들 수 있습니다 이제 aria-hidden이 true로 설정된 범위에서 스피커 이모티콘을 감싸도록 하여 버튼을 만들어 보죠 이 이모티콘의 설명은 여기에서 특히 유용하지 않기 때문입니다
다음으로, 말할 구와 음성 로케일을 사용하는 wrapWithSSML이라는 도우미 JavaScript 함수를 만들어 보겠습니다
강조용으로 각 구 앞에 짧은 일시 중지를 삽입하기 위해 break 요소를 사용하여 SSML 문자열을 작성하기 시작합니다
prosody 요소를 사용하여 기본 비율의 80%로 구를 말하도록 지정합니다
마지막으로 lang 요소를 사용하여 말하려는 문구의 로케일별 음성을 선택할 수 있습니다
이제 클릭 이벤트 리스너를 질문 읽기 버튼에 추가하고 내부에 SSML 문자열을 빌드합니다 먼저 전체 문자열을 speak 요소로 코드를 감싸야 합니다
말하기는 합성 프로세서 내부의 모든 것이 SSML로 간주되어야 함을 나타내기 때문에 중요합니다
다음으로 질문 '물을 스페인어로 어떻게 말하나요?'을 포함합니다 wrapWithSSML 도우미 기능을 사용하여 번역되는 구문을 강조하고 미국 영어 로케일 음성으로 읽을 수 있습니다
또한 네 가지 잠재적인 답변 모두에 wrapWithSSML을 사용하여 강조하고 스페인어 로케일 음성으로 읽도록 요청합니다
마지막으로 SSML 문자열을 사용하여 새 SpeechSynthesisUtterance 개체를 만들고 새 SpeechSynthesisUtterance 개체를 만들고 이를 창의 SpeechSynthesis 말하기 메서드에 전달하여 읽을 수 있습니다
모든 것이 준비되었으므로 웹에서의 경험이 어떤지 확인해 보죠 마지막 질문이 있는 페이지에서 '질문 읽기' 버튼을 탭하고 들어보겠습니다 물을 스페인어로 어떻게 말하나요? El agua. La abuela. La abeja. El árbol. SSML 덕분에 학생들에게 훨씬 더 매력적인 경험을 제공했습니다
웹의 또 다른 일반적인 디자인 패턴은 모달입니다
웹 앱에서 로그인 또는 가입 양식, 확인 대화 상자 등으로 사용할 수 있습니다
접근 가능한 모달 경험을 제공하는 한 가지 방법은 aria-modal 속성입니다 aria-modal="true"를 사용하면 Safari는 모달 외부에 있는 모든 접근 가능한 요소를 무시하는 것으로 간주합니다 최근 Safari는 대화 상자 요소에 대한 지원도 추가했습니다 Dialog를 사용하면 표준 포커스 상호 작용, Escape 키 및 iOS의 스크럽 제스처와 같은 모달 닫기 제스처의 즉시 처리를 통해 접근성 친화적인 모달 경험을 훨씬 쉽게 제공할 수 있습니다 이를 실제로 보려면 퀴즈 평가 웹 앱에서 "점수 표시" 버튼을 변경하여 결과가 포함된 대화 상자를 열어 보겠습니다
먼저 대화 요소를 만들어야 합니다 마크업은 다음과 같이 보일 수 있습니다 나중에 점수 표시 버튼에서 참조할 수 있도록 대화 상자에 ID를 지정합니다 또한 대화 상자의 내용을 메서드 대화 상자가 있는 형식으로 감싸기합니다
그렇게 하면 버튼과 같은 제출 유형 컨트롤로 인해 대화 상자가 닫힙니다 모달을 열려면 약간의 JavaScript도 필요합니다 대화 상자 요소에서 showModal()을 호출하는 Show Score 버튼에 클릭 이벤트 리스너를 추가해 보겠습니다
이제 한 번 해 보도록 하죠 VoiceOver가 활성화된 상태에서 "점수 표시" 버튼을 눌러 초점을 맞춥니다
점수 표시, 버튼
그런 다음 화면의 아무 곳이나 한 손가락으로 두 번 탭하여 버튼을 누릅니다 점수를 보여주세요, 웹 대화 상자, 닫기 버튼 이제 모달이 생겼습니다 왼쪽으로 스와이프하여 모달의 내용을 이동하여 내 점수를 들을 수 있습니다 여섯 개 질문을 모두 맞췄습니다 잘했습니다 작업이 끝나면 오른쪽으로 스와이프하여 닫기 버튼으로 돌아갈 수 있습니다 닫기 버튼 모달을 닫으려면 두 번 탭합니다 선택 해제 앞서 언급했듯이 대화 상자 요소는 기본적으로 모달 클로저를 위한 iOS 스크럽 제스처를 처리합니다 시연을 위해 두 번 탭하여 모달을 다시 열겠습니다 점수 표시, 버튼 웹 대화상자, 닫기 , 버튼 그런 다음 화면에서 두 손가락을 오른쪽, 왼쪽, 오른쪽으로 빠르게 움직여 스크럽 제스처를 수행합니다
점수 표시, 버튼 좋습니다, 이제 기능적인 모달이 완성되었지만 여기에서 다 나아갈 수 있습니다 모달을 열었을 때 VoiceOver가 '웹 대화 상자, 닫기, 버튼'만 읽는다는 것을 눈치 채셨나요? 이 상황에서 보조 기술 사용자에게 더 많은 정보를 제공하려면 aria-label 또는 aria-labelledby 속성을 사용하는 것이 합리적일 것입니다 이 짧은 모달 콘텐츠는 사용자에게 얼마나 많은 답을 맞췄는지 알려주기만 하면 되니 이를 라벨에 사용해 보겠습니다 먼저 ID가 있는 범위의 모달 콘텐츠를 감쌉니다 그런 다음 모달 콘텐츠 ID를 가리키는 대화 상자에 aria-labelledby 속성을 추가할 수 있습니다
또한 초기 모달 포커스 요소를 autofocus 속성이 있는 닫기 버튼으로 명시적으로 설정해 보겠습니다
이는 이 단순한 모달의 기본 동작으로 이미 설정되었지만 모달에 더 많은 콘텐츠가 있거나 더 많은 컨트롤이 있어 복잡하다면 그렇지 않을 수도 있었습니다
예를 들어 콘텐츠가 많은 모달에서는 최상위 제목에 자동 초점을 배치하는 것이 더 합리적일 수 있습니다 모달 작성자는 사용자에게 훌륭한 경험을 제공하는 것이 무엇인지 가장 잘 알 것입니다
새로운 속성이 적용되면 VoiceOver의 경험이 어떤지 다시 살펴보겠습니다 먼저 점수 표시 버튼을 한 번 눌러 초점을 맞추겠습니다 점수 표시, 버튼
그런 다음 두 번 탭하여 누릅니다 여섯 개의 질문을 모두 맞췄습니다 잘했습니다, 웹 대화상자, 닫기 버튼 경험이 향상되었습니다 VoiceOver 사용자는 aria-labelledby 덕분에 즉시 점수를 듣고 이미 닫기 버튼에 집중하고 있으므로 두 번 탭하여 모달을 종료할 수 있습니다 이상으로 오늘의 세션을 마칠 시간입니다 풍부하고 누구나 접근 가능한 웹 앱을 구축하여 모든 사용자에게 훌륭한 경험을 제공하는 기술 몇 가지를 배웠기를 바랍니다 최신 Safari에서 이러한 기능을 사용해 보고 버그를 발견하면 WebKit 버그 추적기 bugs.webkit.org에 알려주세요 현대적인 웹 접근성에 대한 오늘 숨가쁜 투어에 함께해 주셔서 감사합니다. 즐거운 WWDC 보내시길 바랍니다 ♪ ♪
-
-
3:06 - PizzaControl class with click event listener
class PizzaControl { constructor(id) { this.control = document.getElementById(id); this.sliceCount = 4; this.control.addEventListener("click", (event) => { const newSliceCount = this.computeSliceCount(event); this.update(newSliceCount); }); } }
-
4:23 - PizzaControl HTML markup
<div id="pizza-input" role="slider" tabindex="0" aria-valuemin="0" aria-valuemax="8" aria-valuenow="4" aria-valuetext="4 slices"> </div>
-
5:15 - PizzaControl class with keydown event listener
class PizzaControl { constructor(id) { this.control = document.getElementById(id); this.sliceCount = 4; // …click event listener… this.control.addEventListener("keydown", (event) => { const key = event.key; if (key === "ArrowRight" || key === "ArrowUp") this.update(this.sliceCount + 1); else if (key === "ArrowLeft" || key === "ArrowDown") this.update(this.sliceCount - 1); }); } }
-
5:41 - PizzaControl class update function
class PizzaControl { // …constructor… update(newSliceCount) { this.sliceCount = Math.max(0, Math.min(newSliceCount, 8)); // Visually re-render `this.sliceCount` slices // … // Update the ARIA representation of the control this.control.setAttribute("aria-valuenow", this.sliceCount); const sliceModifier = this.sliceCount === 1 ? "slice" : "slices"; this.control.setAttribute("aria-valuetext", `${this.sliceCount} ${sliceModifier}`); } }
-
7:52 - SSML examples
<speak> Breathe in <break time="3s"/> and breathe out. </speak> <speak> <phoneme alphabet="ipa" ph="təˈmeɪtoʊ">tomato</phoneme> <phoneme alphabet="ipa" ph="təˈmɑːtəʊ">tomato</phoneme> </speak> <speak> <prosody pitch="-2st" rate="slow" volume="loud"> Hello world! </prosody> </speak>
-
8:45 - "Read question" button HTML markup
<button id="read-question-btn"> Read question<span aria-hidden="true">🔊</span> </button>
-
8:57 - wrapWithSSML JavaScript function
function wrapWithSSML(phrase, locale) { return ` <break time=“100ms"/> <prosody rate=“80%"> <lang xml:lang="${locale}"> ${phrase} </lang> </prosody> `; }
-
9:24 - Read question button click event listener
const readQuestionButton = document.getElementById("read-question-btn"); readQuestionButton.addEventListener("click", () => { const ssml = ` <speak> How do you say ${wrapWithSSML("the water", "en-US")} in Spanish? ${wrapWithSSML("El agua", "es-MX")} ${wrapWithSSML("La abuela", "es-MX")} ${wrapWithSSML("La abeja", "es-MX")} ${wrapWithSSML("El árbol", "es-MX")} </speak> `; const utterance = new SpeechSynthesisUtterance(ssml); window.speechSynthesis.speak(utterance); });
-
11:33 - Show score dialog HTML markup
<dialog id="show-score-modal"> <form method="dialog"> You got all six questions correct. Great work! <button type="submit">Close</button> </form> </dialog>
-
11:51 - JavaScript to open show score dialog
const showScoreButton = document.getElementById("show-score-btn"); showScoreButton.addEventListener("click", () => { document .getElementById("show-score-modal") .showModal(); });
-
13:23 - Show score dialog with autofocus and aria-labelledby attribute
<dialog id="show-score-modal" aria-labelledby="modal-content"> <form method="dialog"> <span id="modal-content"> You got all six questions correct. Great work! </span> <button type="submit" autofocus>Close</button> </form> </dialog>
-
-
찾고 계신 콘텐츠가 있나요? 위에 주제를 입력하고 원하는 내용을 바로 검색해 보세요.
쿼리를 제출하는 중에 오류가 발생했습니다. 인터넷 연결을 확인하고 다시 시도해 주세요.