Bài viết này mô tả lý do và cách chúng tôi triển khai việc mô phỏng khiếm khuyết về thị giác màu trong Công cụ cho nhà phát triển và Trình kết xuất Blink.
Nền: độ tương phản màu kém
Văn bản có độ tương phản thấp là vấn đề về khả năng tiếp cận có thể phát hiện tự động phổ biến nhất trên web.
Theo dữ liệu phân tích về khả năng tiếp cận của WebAIM đối với 1 triệu trang web hàng đầu, hơn 86% số trang chủ có độ tương phản thấp. Trung bình, mỗi trang chủ có 36 trường hợp riêng biệt của văn bản có độ tương phản thấp.
Sử dụng Công cụ cho nhà phát triển để tìm, hiểu và khắc phục các vấn đề về độ tương phản
Công cụ của Chrome cho nhà phát triển có thể giúp các nhà phát triển và nhà thiết kế cải thiện độ tương phản và chọn các bảng phối màu dễ tiếp cận hơn cho các ứng dụng web:
- Chú thích về Chế độ kiểm tra xuất hiện ở đầu trang web sẽ hiển thị tỷ lệ tương phản của các phần tử văn bản.
- Công cụ chọn màu trong Công cụ cho nhà phát triển đề xuất tỷ lệ tương phản kém cho các phần tử văn bản, hiện đường tương phản đề xuất để giúp bạn chọn màu chất lượng hơn theo cách thủ công và thậm chí có thể đề xuất các màu dễ tiếp cận.
- Cả bảng điều khiển Tổng quan về CSS và báo cáo kiểm tra Hỗ trợ tiếp cận bằng Lighthouse đều liệt kê các thành phần văn bản có độ tương phản thấp như tìm thấy trên trang của bạn.
Gần đây, chúng tôi đã thêm một công cụ mới vào danh sách này và công cụ này hơi khác so với những công cụ khác. Các công cụ trên chủ yếu tập trung vào thông tin về tỷ lệ tương phản nổi bật và cung cấp cho bạn các lựa chọn để khắc phục. Chúng tôi nhận thấy Công cụ cho nhà phát triển vẫn chưa có cách giúp nhà phát triển tìm hiểu sâu hơn về không gian gặp vấn đề này. Để giải quyết vấn đề này, chúng tôi đã triển khai mô phỏng tình trạng khiếm thị trong thẻ Kết xuất trong Công cụ cho nhà phát triển.
Trong Puppeteer, API page.emulateVisionDeficiency(type)
mới cho phép bạn bật các chương trình mô phỏng này theo phương thức lập trình.
Khiếm khuyết về thị lực màu
Khoảng 1/20 người bị khiếm thị màu (còn được gọi là thuật ngữ "mù màu" kém chính xác hơn). Những khiếm khuyết như vậy khiến khó phân biệt các màu sắc khác nhau, điều này có thể làm gia tăng các vấn đề về độ tương phản.
Là một nhà phát triển có tầm nhìn thường xuyên, bạn có thể thấy Công cụ cho nhà phát triển hiển thị tỷ lệ tương phản kém đối với các cặp màu mà bạn nhìn thấy vừa mắt. Điều này xảy ra vì các công thức tỷ lệ tương phản có tính đến những khiếm khuyết về tầm nhìn màu! Bạn vẫn có thể đọc được văn bản có độ tương phản thấp trong một số trường hợp, nhưng những người bị suy giảm thị lực không có đặc quyền đó.
Bằng cách cho phép các nhà thiết kế và nhà phát triển mô phỏng ảnh hưởng của những khiếm thị này trên ứng dụng web của riêng họ, chúng tôi mong muốn cung cấp phần còn thiếu: Công cụ cho nhà phát triển không chỉ có thể giúp bạn tìm và khắc phục các vấn đề về độ tương phản, mà giờ đây bạn còn có thể hiểu rõ các vấn đề đó!
Mô phỏng khiếm khuyết về thị giác màu với HTML, CSS, SVG và C++
Trước khi tìm hiểu kỹ hơn về cách triển khai tính năng Blink Renderer (Trình kết xuất Blink) cho tính năng này, chúng tôi muốn tìm hiểu cách bạn sẽ triển khai chức năng tương đương bằng công nghệ web.
Bạn có thể coi mỗi phần mô phỏng khiếm khuyết về thị lực màu là một lớp phủ bao phủ toàn bộ trang. Nền tảng web có một cách để làm điều đó: Bộ lọc CSS! Với thuộc tính filter
của CSS, bạn có thể sử dụng một số hàm bộ lọc định sẵn, chẳng hạn như blur
, contrast
, grayscale
, hue-rotate
và nhiều hàm khác. Để có nhiều quyền kiểm soát hơn, thuộc tính filter
cũng chấp nhận một URL có thể trỏ đến định nghĩa bộ lọc SVG tuỳ chỉnh:
<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>
Ví dụ ở trên sử dụng định nghĩa bộ lọc tùy chỉnh dựa trên ma trận màu. Về mặt lý thuyết, giá trị màu [Red, Green, Blue, Alpha]
của mỗi pixel được nhân với ma trận để tạo ra màu [R′, G′, B′, A′]
mới.
Mỗi hàng trong ma trận chứa 5 giá trị: một hệ số cho (từ trái sang phải) R, G, B và A, cũng như giá trị thứ năm cho giá trị dịch chuyển không đổi. Có 4 hàng: hàng đầu tiên của ma trận được dùng để tính giá trị Đỏ mới, hàng thứ hai Xanh lục, hàng thứ ba Xanh dương và hàng cuối cùng Alpha.
Bạn có thể thắc mắc những con số chính xác trong ví dụ của chúng tôi đến từ đâu. Điều gì khiến ma trận màu này là kết quả gần đúng của chứng mù màu xanh lục? Câu trả lời là: khoa học! Các giá trị này dựa trên mô hình mô phỏng khiếm khuyết thị lực màu chính xác về mặt sinh lý của Machado, Oliveira và Fernandes.
Dù sao thì chúng ta cũng có bộ lọc SVG này và giờ đây chúng ta có thể áp dụng bộ lọc đó cho các phần tử tuỳ ý trên trang bằng cách sử dụng CSS. Chúng tôi có thể lặp lại cùng một thói quen cho những khiếm khuyết khác về thị lực. Dưới đây là bản minh hoạ về quy trình này:
Nếu muốn, chúng ta có thể xây dựng tính năng Công cụ cho nhà phát triển như sau: khi người dùng mô phỏng khiếm thị trong giao diện người dùng của Công cụ cho nhà phát triển, chúng ta chèn bộ lọc SVG vào tài liệu được kiểm tra, sau đó áp dụng kiểu bộ lọc trên phần tử gốc. Tuy nhiên, có một số vấn đề với phương pháp đó:
- Trang có thể đã có một bộ lọc trên phần tử gốc mà mã của chúng tôi sau đó có thể ghi đè.
- Trang có thể đã có một phần tử với
id="deuteranopia"
, xung đột với định nghĩa bộ lọc của chúng ta. - Trang có thể dựa vào một cấu trúc DOM nhất định và khi chèn
<svg>
vào DOM, chúng tôi có thể vi phạm các giả định này.
Bên cạnh các trường hợp phức tạp, vấn đề chính với phương pháp này là chúng tôi sẽ thực hiện các thay đổi có thể ghi nhận được theo phương thức lập trình đối với trang. Nếu người dùng Công cụ cho nhà phát triển kiểm tra DOM, họ có thể đột nhiên thấy phần tử <svg>
mà họ chưa từng thêm vào hoặc một CSS filter
mà họ chưa từng viết. Như vậy sẽ gây nhầm lẫn! Để triển khai chức năng này trong Công cụ cho nhà phát triển, chúng ta cần một giải pháp không có những hạn chế này.
Hãy cùng xem chúng ta có thể làm gì để phiên bản này bớt gây phiền hà hơn. Có hai phần trong giải pháp này mà chúng ta cần ẩn: 1) kiểu CSS có thuộc tính filter
và 2) định nghĩa bộ lọc SVG, hiện là một phần của DOM.
<!-- 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>
Tránh phần phụ thuộc SVG trong tài liệu
Hãy bắt đầu với phần 2: làm cách nào để chúng ta có thể tránh việc thêm SVG vào DOM? Bạn nên chuyển tệp đó sang một tệp SVG riêng biệt. Chúng ta có thể sao chép <svg>…</svg>
từ HTML ở trên và lưu dưới dạng filter.svg
, nhưng trước tiên chúng ta cần thực hiện một số thay đổi! SVG nội tuyến trong HTML tuân theo các quy tắc phân tích cú pháp HTML. Tức là trong một số trường hợp, bạn có thể bỏ qua dấu ngoặc kép liên quan đến giá trị thuộc tính. Tuy nhiên, SVG trong các tệp riêng biệt được cho là XML hợp lệ và việc phân tích cú pháp XML nghiêm ngặt hơn HTML. Dưới đây là đoạn mã SVG trong HTML của chúng tôi:
<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>
Để tạo SVG độc lập hợp lệ này (và do đó là XML), chúng ta cần thực hiện một số thay đổi. Bạn có đoán được đó là sản phẩm nào không?
<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>
Thay đổi đầu tiên là khai báo không gian tên XML ở trên cùng. Phần bổ sung thứ hai có tên là “solidus” – dấu gạch chéo cho biết thẻ <feColorMatrix>
vừa mở vừa đóng phần tử. Thay đổi cuối cùng này không thực sự cần thiết (thay vào đó, chúng ta chỉ cần sử dụng thẻ đóng </feColorMatrix>
rõ ràng), nhưng vì cả XML và SVG trong HTML đều hỗ trợ cách viết tắt />
này, nên chúng ta cũng có thể sử dụng nó.
Dù sao thì với những thay đổi đó, cuối cùng chúng ta cũng có thể lưu tệp này dưới dạng tệp SVG hợp lệ và trỏ đến tệp đó từ giá trị thuộc tính CSS filter
trong tài liệu HTML của chúng ta:
<style>
:root {
filter: url(filters.svg#deuteranopia);
}
</style>
Rất tiếc, chúng ta không còn phải chèn SVG vào tài liệu nữa! Như vậy đã tốt hơn rất nhiều. Nhưng... hiện chúng tôi phụ thuộc vào một tệp riêng biệt. Đó vẫn là một phần phụ thuộc. Chúng ta có thể loại bỏ nó bằng cách nào đó không?
Hoá ra là chúng ta thực sự không cần tệp. Chúng tôi có thể mã hoá toàn bộ tệp trong một URL bằng cách sử dụng URL dữ liệu. Để làm được điều này, chúng ta lấy nội dung của tệp SVG chúng ta đã có trước đó, thêm tiền tố data:
, định cấu hình loại MIME phù hợp và đã có một URL dữ liệu hợp lệ đại diện cho cùng một tệp SVG:
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>
Lợi ích là giờ đây, chúng ta không cần phải lưu trữ tệp ở bất cứ đâu, hoặc tải tệp từ ổ đĩa hoặc qua mạng chỉ để sử dụng tệp trong tài liệu HTML. Vì vậy, thay vì tham chiếu đến tên tệp như trước đây, giờ đây chúng ta có thể trỏ đến URL dữ liệu:
<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>
Ở cuối URL, chúng ta vẫn ghi rõ mã nhận dạng của bộ lọc mà mình muốn sử dụng, giống như trước đó. Xin lưu ý rằng bạn không cần phải mã hoá tài liệu SVG trong URL bằng Base64. Việc này sẽ chỉ ảnh hưởng đến khả năng đọc và tăng kích thước tệp. Chúng tôi đã thêm dấu gạch chéo ngược ở cuối mỗi dòng để đảm bảo các ký tự dòng mới trong URL dữ liệu không chấm dứt giá trị cố định của chuỗi CSS.
Cho đến nay, chúng ta mới chỉ nói về cách mô phỏng khiếm thị bằng công nghệ web. Điều thú vị là cách triển khai cuối cùng của chúng ta trong Trình kết xuất Blink thực sự khá giống nhau. Sau đây là tiện ích trợ giúp C++ mà chúng tôi đã thêm vào để tạo URL dữ liệu với định nghĩa bộ lọc cho trước, dựa trên cùng một kỹ thuật:
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;
}
Và dưới đây là cách chúng tôi đang sử dụng công cụ này để tạo tất cả các bộ lọc cần thiết:
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 "";
}
}
Lưu ý rằng kỹ thuật này cho phép chúng ta tiếp cận toàn bộ sức mạnh của bộ lọc SVG mà không cần phải triển khai lại bất kỳ thứ gì hoặc sáng chế lại bất kỳ bánh xe nào. Chúng tôi đang triển khai tính năng Trình kết xuất liên kết (Blink Renderer), nhưng chúng tôi đang thực hiện điều này bằng cách tận dụng Nền tảng web.
Vậy là chúng ta đã tìm được cách tạo bộ lọc SVG và chuyển chúng thành các URL dữ liệu để sử dụng trong giá trị thuộc tính filter
CSS. Bạn có nghĩ ra vấn đề với kỹ thuật này không? Hoá ra là chúng ta không thể dựa vào URL dữ liệu được tải trong mọi trường hợp, vì trang đích có thể có Content-Security-Policy
chặn URL dữ liệu. Khi triển khai ở cấp Blink cuối cùng, chúng tôi đặc biệt chú ý để bỏ qua CSP đối với những URL dữ liệu "nội bộ" này trong quá trình tải.
Bên cạnh những trường hợp hiếm gặp, chúng tôi đã có tiến bộ đáng kể. Vì không còn phụ thuộc vào việc có <svg>
cùng dòng trong cùng một tài liệu, nên chúng tôi đã giảm thiểu giải pháp của mình một cách hiệu quả xuống chỉ còn một định nghĩa thuộc tính CSS filter
độc lập. Tuyệt vời! Giờ hãy loại bỏ điều đó nữa.
Tránh phần phụ thuộc CSS trong tài liệu
Tóm lại, đây là kết quả của chúng tôi cho đến thời điểm hiện tại:
<style>
:root {
filter: url('data:…');
}
</style>
Chúng ta vẫn phụ thuộc vào thuộc tính filter
CSS này. Thuộc tính này có thể ghi đè filter
trong tài liệu thực và gây ra lỗi. Mã này cũng sẽ xuất hiện khi kiểm tra kiểu đã tính toán trong Công cụ cho nhà phát triển, điều này sẽ gây nhầm lẫn. Làm cách nào để chúng tôi có thể tránh những vấn đề này? Chúng ta cần tìm cách thêm bộ lọc vào tài liệu để nhà phát triển không thể ghi nhận bộ lọc đó bằng phương thức lập trình.
Một ý tưởng nảy ra là tạo một thuộc tính CSS nội bộ mới trên Chrome, hoạt động như filter
nhưng có tên khác như --internal-devtools-filter
. Sau đó, chúng ta có thể thêm logic đặc biệt để đảm bảo thuộc tính này không bao giờ xuất hiện trong Công cụ cho nhà phát triển hoặc trong kiểu đã tính toán trong DOM. Thậm chí, chúng tôi còn có thể đảm bảo tính năng này chỉ hoạt động trên một phần tử mà chúng tôi cần: phần tử gốc. Tuy nhiên, giải pháp này sẽ không lý tưởng: chúng tôi sẽ sao chép chức năng đã có với filter
và ngay cả khi chúng tôi cố gắng ẩn thuộc tính không chuẩn này, các nhà phát triển web vẫn có thể phát hiện ra và bắt đầu sử dụng nó, điều này không tốt cho Nền tảng web. Chúng ta cần một số cách khác để áp dụng kiểu CSS mà không cần quan sát được trong DOM. Google có ý tưởng nào không?
Thông số kỹ thuật CSS có một phần giới thiệu mô hình định dạng hình ảnh mà phần này sử dụng, và một trong những khái niệm chính có khung nhìn. Đây là chế độ xem trực quan mà qua đó người dùng tham khảo trang web. Một khái niệm liên quan chặt chẽ là khối chứa tên viết tắt, giống như một khung nhìn có thể tạo kiểu <div>
chỉ tồn tại ở cấp độ thông số kỹ thuật. Thông số kỹ thuật đề cập đến khái niệm "khung nhìn" này ở khắp nơi. Ví dụ: bạn biết cách trình duyệt hiển thị thanh cuộn khi nội dung không vừa? Điều này được xác định trong quy cách CSS, dựa trên "khung nhìn" này.
viewport
này cũng tồn tại trong Trình kết xuất Blink, dưới dạng chi tiết triển khai. Đây là mã áp dụng kiểu khung nhìn mặc định theo thông số kỹ thuật:
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;
}
Bạn không cần phải hiểu rõ về C++ hoặc các chi tiết phức tạp của công cụ Style (Kiểu của Blink) để đảm bảo rằng mã này xử lý z-index
, display
, position
và overflow
của khung nhìn (hay chính xác hơn là khối ban đầu chứa khối). Đó là tất cả những khái niệm mà bạn có thể đã quen thuộc với CSS! Có một số phép thuật khác liên quan đến ngữ cảnh xếp chồng, không chuyển trực tiếp sang thuộc tính CSS, nhưng nhìn chung, bạn có thể coi đối tượng viewport
này là một đối tượng có thể được tạo kiểu bằng CSS từ trong Blink, giống như phần tử DOM, ngoại trừ nó không phải là một phần của DOM.
Điều này cung cấp cho chúng tôi chính xác những gì chúng tôi muốn! Chúng ta có thể áp dụng kiểu filter
cho đối tượng viewport
. Đối tượng này ảnh hưởng đến quá trình kết xuất hình ảnh mà không gây trở ngại cho các kiểu trang có thể quan sát hoặc DOM theo bất kỳ cách nào.
Kết luận
Để tóm tắt hành trình nhỏ này, chúng tôi bắt đầu bằng việc xây dựng một nguyên mẫu sử dụng công nghệ web thay vì C++, sau đó bắt đầu chuyển các phần của nguyên mẫu sang Trình kết xuất Blink.
- Trước tiên, chúng tôi tạo ra nguyên mẫu độc lập hơn bằng cách chèn URL dữ liệu cùng dòng.
- Sau đó, chúng tôi làm cho những URL dữ liệu nội bộ đó thân thiện với CSP bằng cách phân biệt chữ hoa chữ thường trong quá trình tải.
- Chúng tôi làm cho quá trình triển khai không phụ thuộc vào DOM và không thể ghi nhận theo phương thức lập trình bằng cách di chuyển các kiểu sang
viewport
nội bộ Blink.
Điểm độc đáo của cách triển khai này là nguyên mẫu HTML/CSS/SVG của chúng tôi cuối cùng đã ảnh hưởng đến thiết kế kỹ thuật cuối cùng. Chúng tôi đã tìm ra cách sử dụng Nền tảng web, ngay cả trong Trình kết xuất Blink!
Để biết thêm thông tin cơ bản, hãy xem đề xuất thiết kế của chúng tôi hoặc lỗi theo dõi Chromium, trong đó tham chiếu đến tất cả bản vá liên quan.
Tải kênh xem trước xuống
Hãy cân nhắc việc sử dụng Chrome Canary, Nhà phát triển hoặc Beta làm trình duyệt phát triển mặc định của bạn. Các kênh xem trước này cho phép bạn truy cập vào các tính năng mới nhất của Công cụ cho nhà phát triển, kiểm thử các API nền tảng web tiên tiến và tìm ra các vấn đề trên trang web của bạn trước khi người dùng làm điều đó!
Liên hệ với nhóm Công cụ của Chrome cho nhà phát triển
Sử dụng các lựa chọn sau để thảo luận về các tính năng và thay đổi mới trong bài đăng, hoặc bất kỳ nội dung nào khác liên quan đến Công cụ cho nhà phát triển.
- Gửi đề xuất hoặc phản hồi cho chúng tôi qua crbug.com.
- Báo cáo sự cố Công cụ cho nhà phát triển bằng cách sử dụng Tuỳ chọn khác > Trợ giúp > Báo cáo vấn đề về Công cụ cho nhà phát triển trong Công cụ cho nhà phát triển.
- Tweet tại @ChromeDevTools.
- Để lại bình luận về Tính năng mới của chúng tôi trong Video trên YouTube hoặc Mẹo trong Công cụ cho nhà phát triển Video trên YouTube.