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