이 도움말에서는 DevTools 및 Blink 렌더러에서 색각 이상 시뮬레이션을 구현한 이유와 방법을 설명합니다.
배경: 색상 대비가 좋지 않음
저대비 텍스트는 웹에서 자동으로 감지되는 가장 일반적인 접근성 문제입니다.
WebAIM의 100만 개 인기 웹사이트 접근성 분석에 따르면 홈페이지의 86% 이상이 대비가 낮습니다. 각 홈페이지에는 평균적으로 저대비 텍스트가 36개 있습니다.
DevTools를 사용하여 대비 문제를 찾고 이해하고 수정하기
Chrome DevTools를 사용하면 개발자와 디자이너가 대비를 개선하고 웹 앱에 더 접근하기 쉬운 색 구성표를 선택할 수 있습니다.
- 웹페이지 상단에 표시되는 검사 모드 도움말에는 텍스트 요소의 대비율이 표시됩니다.
- DevTools 색상 선택 도구는 텍스트 요소의 나쁜 대비율을 표시하고, 더 나은 색상을 수동으로 선택하는 데 도움이 되는 권장 대비 선을 표시하며, 접근 가능한 색상을 제안할 수도 있습니다.
- CSS 개요 패널과 Lighthouse 접근성 감사 보고서 모두 페이지에서 발견된 저대비 텍스트 요소를 나열합니다.
최근 이 목록에 새로운 도구가 추가되었으며, 이 도구는 다른 도구와 약간 다릅니다. 위의 도구는 주로 대비율 정보를 표시하고 이를 수정하는 옵션을 제공하는 데 중점을 둡니다. Google은 개발자가 이 문제 영역을 더 깊이 이해할 수 있는 방법이 DevTools에 아직 없다는 것을 깨달았습니다. 이를 해결하기 위해 DevTools 렌더링 탭에 색맹 시뮬레이션을 구현했습니다.
Puppeteer에서는 새로운 page.emulateVisionDeficiency(type)
API를 사용하여 이러한 시뮬레이션을 프로그래매틱 방식으로 사용 설정할 수 있습니다.
색약
20명 중 약 1명은 색각이상 (일반적으로 '색맹'이라고 함)을 앓고 있습니다. 이러한 장애는 색상의 차이를 구별하기 어렵게 만들고 이로 인해 대비 문제가 커질 수 있습니다.
시력이 정상인 개발자는 DevTools에서 시각적으로 적절하다고 생각하는 색상 쌍에 나쁜 대비율을 표시할 수 있습니다. 명암비 수식에서 이러한 색맹을 고려하기 때문에 이러한 현상이 발생합니다. 경우에 따라 저대비 텍스트도 읽을 수 있지만 시각 장애가 있는 사용자는 읽을 수 없습니다.
Google은 디자이너와 개발자가 이러한 시각 장애가 자체 웹 앱에 미치는 영향을 시뮬레이션할 수 있도록 하여 누락된 부분을 제공하고자 합니다. 이제 DevTools를 사용하여 대비 문제를 찾고 수정할 뿐만 아니라 이해할 수도 있습니다.
HTML, CSS, SVG, C++로 색약 시뮬레이션
이 기능의 Blink 렌더러 구현을 자세히 살펴보기 전에 웹 기술을 사용하여 상응하는 기능을 구현하는 방법을 이해하는 것이 좋습니다.
이러한 각 색각이상 시뮬레이션은 전체 페이지를 덮는 오버레이라고 생각할 수 있습니다. 웹 플랫폼에는 이를 실행하는 방법이 있습니다. 바로 CSS 필터입니다. CSS filter
속성을 사용하면 blur
, contrast
, grayscale
, hue-rotate
등 사전 정의된 필터 함수를 사용할 수 있습니다. 더 세부적으로 제어하려면 filter
속성에서 맞춤 SVG 필터 정의를 가리킬 수 있는 URL도 허용합니다.
<style>
:root {
filter: url(#deuteranopia);
}
</style>
<svg>
<filter id="deuteranopia">
<feColorMatrix values="0.367 0.861 -0.228 0.000 0.000
0.280 0.673 0.047 0.000 0.000
-0.012 0.043 0.969 0.000 0.000
0.000 0.000 0.000 1.000 0.000">
</feColorMatrix>
</filter>
</svg>
위 예에서는 색상 매트릭스를 기반으로 하는 맞춤 필터 정의를 사용합니다. 개념적으로는 모든 픽셀의 [Red, Green, Blue, Alpha]
색상 값을 행렬로 곱하여 새 색상 [R′, G′, B′, A′]
을 만듭니다.
행렬의 각 행에는 5개의 값 (왼쪽에서 오른쪽으로) R, G, B, A의 배수와 상수 이동 값의 다섯 번째 값이 포함됩니다. 행은 4개입니다. 행렬의 첫 번째 행은 새 빨간색 값을 계산하는 데 사용되고, 두 번째 행은 녹색, 세 번째 행은 파란색, 마지막 행은 알파를 계산하는 데 사용됩니다.
이 예시의 정확한 숫자는 어디에서 가져오는지 궁금하실 수 있습니다. 이 색상 매트릭스가 제2색맹에 근접한 근사치인 이유는 무엇인가요? 답은 과학입니다. 이 값은 Machado, Oliveira, Fernandes의 생리학적으로 정확한 색각이상 시뮬레이션 모델을 기반으로 합니다.
어쨌든 이 SVG 필터가 있으므로 이제 CSS를 사용하여 페이지의 임의 요소에 적용할 수 있습니다. 다른 시각 장애에도 동일한 패턴을 반복할 수 있습니다. 다음은 이러한 방식으로 표시되는 방식을 보여주는 데모입니다.
원하는 경우 다음과 같이 DevTools 기능을 빌드할 수 있습니다. 사용자가 DevTools UI에서 시각 장애를 에뮬레이션하면 검사된 문서에 SVG 필터를 삽입한 다음 루트 요소에 필터 스타일을 적용합니다. 그러나 이 접근 방식에는 몇 가지 문제가 있습니다.
- 페이지의 루트 요소에 이미 필터가 있을 수 있으며 이 필터는 코드에서 재정의할 수 있습니다.
- 페이지에 이미
id="deuteranopia"
가 포함된 요소가 있어 필터 정의와 충돌할 수 있습니다. - 페이지가 특정 DOM 구조를 사용하고 있을 수 있으며 DOM에
<svg>
를 삽입하면 이러한 가정을 위반할 수 있습니다.
특이 사례는 제외하고 이 접근 방식의 주요 문제는 프로그래매틱 방식으로 페이지를 관찰 가능하게 변경한다는 점입니다. DevTools 사용자가 DOM을 검사하면 추가하지 않은 <svg>
요소나 작성하지 않은 CSS filter
가 갑자기 표시될 수 있습니다. 혼란스러울 수 있습니다. DevTools에서 이 기능을 구현하려면 이러한 단점이 없는 솔루션이 필요합니다.
이러한 알림을 덜 방해되게 만드는 방법을 살펴보겠습니다. 이 솔루션에는 숨겨야 할 두 가지 부분이 있습니다. 1) filter
속성이 있는 CSS 스타일, 2) 현재 DOM의 일부인 SVG 필터 정의입니다.
<!-- Part 1: the CSS style with the filter property -->
<style>
:root {
filter: url(#deuteranopia);
}
</style>
<!-- Part 2: the SVG filter definition -->
<svg>
<filter id="deuteranopia">
<feColorMatrix values="0.367 0.861 -0.228 0.000 0.000
0.280 0.673 0.047 0.000 0.000
-0.012 0.043 0.969 0.000 0.000
0.000 0.000 0.000 1.000 0.000">
</feColorMatrix>
</filter>
</svg>
문서 내 SVG 종속 항목 방지
2단계부터 시작하겠습니다. DOM에 SVG를 추가하지 않으려면 어떻게 해야 하나요? 별도의 SVG 파일로 이동하는 것이 좋습니다. 위의 HTML에서 <svg>…</svg>
를 복사하여 filter.svg
로 저장할 수 있지만, 먼저 몇 가지 사항을 변경해야 합니다. HTML의 인라인 SVG는 HTML 파싱 규칙을 따릅니다. 즉, 경우에 따라 속성 값에 따옴표를 생략하는 등의 작업을 실행할 수 있습니다. 하지만 별도의 파일에 있는 SVG는 유효한 XML이어야 하며 XML 파싱은 HTML보다 훨씬 엄격합니다. 다음은 HTML 내 SVG 스니펫입니다.
<svg>
<filter id="deuteranopia">
<feColorMatrix values="0.367 0.861 -0.228 0.000 0.000
0.280 0.673 0.047 0.000 0.000
-0.012 0.043 0.969 0.000 0.000
0.000 0.000 0.000 1.000 0.000">
</feColorMatrix>
</filter>
</svg>
이 SVG를 유효한 독립형 SVG (따라서 XML)로 만들려면 몇 가지 사항을 변경해야 합니다. 어떤 앱인지 맞춰 보세요.
<svg xmlns="http://www.w3.org/2000/svg">
<filter id="deuteranopia">
<feColorMatrix values="0.367 0.861 -0.228 0.000 0.000
0.280 0.673 0.047 0.000 0.000
-0.012 0.043 0.969 0.000 0.000
0.000 0.000 0.000 1.000 0.000" />
</filter>
</svg>
첫 번째 변경사항은 상단의 XML 네임스페이스 선언입니다. 두 번째 추가 사항은 <feColorMatrix>
태그가 요소를 열고 닫는다는 것을 나타내는 슬래시인 소위 '슬러시'입니다. 이 마지막 변경사항은 실제로 필요하지 않습니다 (명시적인 </feColorMatrix>
닫기 태그를 사용해도 됨). 하지만 XML과 HTML의 SVG 모두 이 />
약어를 지원하므로 이를 사용하는 것이 좋습니다.
어쨌든 이러한 변경사항을 통해 마침내 유효한 SVG 파일로 저장하고 HTML 문서의 CSS filter
속성 값에서 이를 가리킬 수 있습니다.
<style>
:root {
filter: url(filters.svg#deuteranopia);
}
</style>
이제 더 이상 문서에 SVG를 삽입할 필요가 없습니다. 훨씬 나아졌습니다. 하지만 이제 별도의 파일에 종속됩니다. 이는 여전히 종속 항목입니다. 어떻게든 제거할 수 있을까요?
파일이 실제로는 필요하지 않습니다. 데이터 URL을 사용하여 URL 내에서 전체 파일을 인코딩할 수 있습니다. 이렇게 하려면 이전에 있던 SVG 파일의 콘텐츠를 가져와 data:
접두사를 추가하고 적절한 MIME 유형을 구성하면 됩니다. 그러면 동일한 SVG 파일을 나타내는 유효한 데이터 URL이 생성됩니다.
data:image/svg+xml,
<svg xmlns="http://www.w3.org/2000/svg">
<filter id="deuteranopia">
<feColorMatrix values="0.367 0.861 -0.228 0.000 0.000
0.280 0.673 0.047 0.000 0.000
-0.012 0.043 0.969 0.000 0.000
0.000 0.000 0.000 1.000 0.000" />
</filter>
</svg>
이렇게 하면 HTML 문서에서 파일을 사용하기 위해 더 이상 파일을 어디에나 저장하거나 디스크에서 또는 네트워크를 통해 로드할 필요가 없습니다. 이제 이전과 같이 파일 이름을 참조하는 대신 데이터 URL을 가리킬 수 있습니다.
<style>
:root {
filter: url('data:image/svg+xml,\
<svg xmlns="http://www.w3.org/2000/svg">\
<filter id="deuteranopia">\
<feColorMatrix values="0.367 0.861 -0.228 0.000 0.000\
0.280 0.673 0.047 0.000 0.000\
-0.012 0.043 0.969 0.000 0.000\
0.000 0.000 0.000 1.000 0.000" />\
</filter>\
</svg>#deuteranopia');
}
</style>
URL 끝에는 이전과 마찬가지로 사용할 필터의 ID를 지정합니다. URL에서 SVG 문서를 Base64로 인코딩할 필요는 없습니다. 이렇게 하면 가독성이 떨어지고 파일 크기만 늘어납니다. 데이터 URL의 줄바꿈 문자가 CSS 문자열 리터럴을 종료하지 않도록 각 줄 끝에 백슬래시를 추가했습니다.
지금까지는 웹 기술을 사용하여 시각 장애를 시뮬레이션하는 방법만 알아봤습니다. 흥미롭게도 Blink 렌더러의 최종 구현은 실제로 매우 유사합니다. 다음은 동일한 기법을 기반으로 지정된 필터 정의가 포함된 데이터 URL을 만들기 위해 추가한 C++ 도우미 유틸리티입니다.
AtomicString CreateFilterDataUrl(const char* piece) {
AtomicString url =
"data:image/svg+xml,"
"<svg xmlns=\"http://www.w3.org/2000/svg\">"
"<filter id=\"f\">" +
StringView(piece) +
"</filter>"
"</svg>"
"#f";
return url;
}
다음은 필요한 모든 필터를 만드는 데 이 도구를 사용하는 방법입니다.
AtomicString CreateVisionDeficiencyFilterUrl(VisionDeficiency vision_deficiency) {
switch (vision_deficiency) {
case VisionDeficiency::kAchromatopsia:
return CreateFilterDataUrl("…");
case VisionDeficiency::kBlurredVision:
return CreateFilterDataUrl("<feGaussianBlur stdDeviation=\"2\"/>");
case VisionDeficiency::kDeuteranopia:
return CreateFilterDataUrl(
"<feColorMatrix values=\""
" 0.367 0.861 -0.228 0.000 0.000 "
" 0.280 0.673 0.047 0.000 0.000 "
"-0.012 0.043 0.969 0.000 0.000 "
" 0.000 0.000 0.000 1.000 0.000 "
"\"/>");
case VisionDeficiency::kProtanopia:
return CreateFilterDataUrl("…");
case VisionDeficiency::kTritanopia:
return CreateFilterDataUrl("…");
case VisionDeficiency::kNoVisionDeficiency:
NOTREACHED();
return "";
}
}
이 기법을 사용하면 아무것도 다시 구현하거나 새로운 방법을 고안하지 않고도 SVG 필터의 모든 기능을 사용할 수 있습니다. Blink 렌더러 기능을 구현하고 있지만 웹 플랫폼을 활용하여 구현하고 있습니다.
이제 SVG 필터를 구성하고 CSS filter
속성 값 내에 사용할 수 있는 데이터 URL로 변환하는 방법을 알아봤습니다. 이 기법에 문제가 있다고 생각하시나요? 타겟 페이지에 데이터 URL을 차단하는 Content-Security-Policy
가 있을 수 있으므로 모든 경우에 데이터 URL이 로드된다고 신뢰할 수는 없습니다. 최종 Blink 수준 구현은 로드 중에 이러한 '내부' 데이터 URL의 CSP를 우회하는 데 특별히 주의를 기울입니다.
특이 사례를 제외하고는 상당한 진전을 이루었습니다. 더 이상 동일한 문서에 인라인 <svg>
가 있는지 확인하지 않으므로 솔루션이 자체 포함된 단일 CSS filter
속성 정의로 효과적으로 축소되었습니다. 좋습니다. 이제 이 부분도 삭제해 보겠습니다.
문서 내 CSS 종속 항목 방지
지금까지의 상황을 요약하면 다음과 같습니다.
<style>
:root {
filter: url('data:…');
}
</style>
여전히 이 CSS filter
속성을 사용하고 있으며, 이로 인해 실제 문서의 filter
가 재정의되어 문제가 발생할 수 있습니다. DevTools에서 계산된 스타일을 검사할 때도 표시되어 혼란을 야기할 수 있습니다. 이러한 문제를 방지하려면 어떻게 해야 하나요? 개발자가 프로그래매틱 방식으로 관찰할 수 없도록 문서에 필터를 추가하는 방법을 찾아야 합니다.
한 가지 아이디어는 filter
처럼 동작하지만 이름이 --internal-devtools-filter
와 같이 다른 새로운 Chrome 내부 CSS 속성을 만드는 것이었습니다. 그런 다음 이 속성이 DevTools 또는 DOM의 계산된 스타일에 표시되지 않도록 하는 특수 로직을 추가할 수 있습니다. 필요한 요소인 루트 요소에서만 작동하도록 할 수도 있습니다. 하지만 이 솔루션은 이상적이지 않습니다. filter
에 이미 있는 기능을 중복으로 제공하게 되며, 이 비표준 속성을 숨기려고 노력하더라도 웹 개발자가 이를 발견하고 사용하기 시작할 수 있으므로 웹 플랫폼에 좋지 않습니다. DOM에서 관찰할 수 없는 다른 방법으로 CSS 스타일을 적용해야 합니다. 도와주실 수 있을까요?
CSS 사양에는 사용하는 시각적 형식 지정 모델을 소개하는 섹션이 있으며 여기서 주요 개념 중 하나는 뷰포트입니다. 사용자가 웹페이지를 참고하는 시각적 뷰입니다. 이와 밀접하게 관련된 개념은 초기 포함 블록으로, 이는 사양 수준에서만 존재하는 스타일 지정 가능한 뷰포트 <div>
와 유사합니다. 사양에서는 이 '뷰포트' 개념을 곳곳에서 참조합니다. 예를 들어 콘텐츠가 표시되지 않을 때 브라우저에서 스크롤바를 표시하는 방법을 알고 계신가요? 이 모든 것은 이 '표시 영역'을 기반으로 CSS 사양에 정의되어 있습니다.
이 viewport
는 Blink 렌더러 내에 구현 세부정보로 존재합니다. 다음은 사양에 따라 기본 뷰포트 스타일을 적용하는 코드입니다.
scoped_refptr<ComputedStyle> StyleResolver::StyleForViewport() {
scoped_refptr<ComputedStyle> viewport_style =
InitialStyleForElement(GetDocument());
viewport_style->SetZIndex(0);
viewport_style->SetIsStackingContextWithoutContainment(true);
viewport_style->SetDisplay(EDisplay::kBlock);
viewport_style->SetPosition(EPosition::kAbsolute);
viewport_style->SetOverflowX(EOverflow::kAuto);
viewport_style->SetOverflowY(EOverflow::kAuto);
// …
return viewport_style;
}
이 코드가 뷰포트 (더 정확하게는 초기 포함 블록)의 z-index
, display
, position
, overflow
를 처리하는지 확인하기 위해 C++ 또는 Blink의 스타일 엔진의 복잡성을 이해할 필요는 없습니다. 이러한 개념은 CSS에서 익숙할 수 있습니다. CSS 속성으로 직접 변환되지는 않지만, 전체적으로 이 viewport
객체는 DOM 요소와 마찬가지로 Blink 내에서 CSS를 사용하여 스타일을 지정할 수 있는 요소로 생각할 수 있습니다. 단, DOM의 일부가 아닙니다.
이렇게 하면 원하는 결과를 얻을 수 있습니다. 관찰 가능한 페이지 스타일이나 DOM을 방해하지 않고 렌더링에 시각적으로 영향을 미치는 viewport
객체에 filter
스타일을 적용할 수 있습니다.
결론
지금까지의 여정을 요약하면, C++ 대신 웹 기술을 사용하여 프로토타입을 빌드한 다음 일부를 Blink 렌더러로 이동하는 작업을 시작했습니다.
- 먼저 데이터 URL을 인라인으로 삽입하여 프로토타입을 더 독립형으로 만들었습니다.
- 그런 다음 로드를 특별히 처리하여 이러한 내부 데이터 URL을 CSP에 적합하게 만들었습니다.
- 스타일을 Blink 내부
viewport
로 이동하여 구현을 DOM과 무관하게 만들고 프로그래매틱 방식으로 관찰할 수 없도록 했습니다.
이 구현의 고유한 점은 HTML/CSS/SVG 프로토타입이 최종 기술 설계에 영향을 미쳤다는 것입니다. Blink 렌더러 내에서도 웹 플랫폼을 사용할 수 있는 방법을 찾았습니다.
자세한 배경은 설계 제안서 또는 모든 관련 패치를 참조하는 Chromium 추적 버그를 참고하세요.
미리보기 채널 다운로드
Chrome Canary, 개발자 또는 베타를 기본 개발 브라우저로 사용하는 것이 좋습니다. 이러한 미리보기 채널을 사용하면 최신 DevTools 기능에 액세스하고, 최신 웹 플랫폼 API를 테스트하고, 사용자가 발견하기 전에 사이트에서 문제를 찾을 수 있습니다.
Chrome DevTools팀에 문의하기
다음 옵션을 사용하여 DevTools와 관련된 새로운 기능, 업데이트 또는 기타 사항을 논의하세요.
- crbug.com에서 의견 및 기능 요청을 제출하세요.
- DevTools에서 옵션 더보기 > 도움말 > DevTools 문제 신고를 사용하여 DevTools 문제를 신고합니다.
- @ChromeDevTools에 트윗하세요.
- DevTools의 새로운 기능 YouTube 동영상 또는 DevTools 도움말 YouTube 동영상에 댓글을 남겨주세요.