Trong bài viết trước, chúng ta đã nói về cách cải thiện khả năng truy cập cho người dùng bàn phím bằng HTML và CSS. Những ngôn ngữ đó có thể thực hiện công việc hầu hết thời gian, nhưng một số yêu cầu thiết kế và bản chất của một số thành phần tạo ra nhu cầu về các tương tác phức tạp hơn và đây là lúc JavaScript phát huy tác dụng.
Đối với mục đích truy cập bằng bàn phím, hầu hết công việc được thực hiện bằng các công cụ cơ bản mở ra nhiều khả năng tương tác bằng bàn phím. Bài viết này đề cập đến một bộ công cụ mà bạn có thể kết hợp vào các thành phần khác nhau để cải thiện khả năng truy cập cho người dùng bàn phím.
Một trong những công cụ quan trọng nhất mà chúng ta có để thêm tính tương tác vào các dự án của mình là sự tồn tại của sự kiện, đây là quá trình thực thi các hàm kích hoạt khi phần tử bạn đang kiểm tra nhận được thay đổi.
Một ví dụ về sự kiện mà bạn có thể lắng nghe bằng API Web này là sự kiện
Hiện tại, điều này không được sử dụng để thêm khả năng truy cập bàn phím vào các phần tử như nút hoặc liên kết vì theo mặc định, khi bạn thêm Trình lắng nghe sự kiện
Để thêm một ví dụ, chúng ta hãy quay lại chú giải công cụ mà chúng ta đã tạo trong phần đầu của bài viết này. Tôi đã đề cập rằng chú giải công cụ này cần được đóng khi bạn nhấn phím Esc. Chúng ta cần một trình lắng nghe sự kiện
Chúng ta sẽ sử dụng keycode.info để kiểm tra bản dump sự kiện cho phím này. Nếu bạn nhấn phím Esc trên trang này, bạn sẽ thấy rằng
Lưu ý: Có hai cách khác để phát hiện phím đã nhấn, đó là kiểm tra
Với phương án đó, chúng ta cần chọn các nút và thêm trình lắng nghe sự kiện. Cách tiếp cận của tôi đối với vấn đề này là sử dụng trình lắng nghe sự kiện này để thêm một lớp vào nút và thêm lớp này làm ngoại lệ để hiển thị nó bằng cách sử dụng lớp giả
Bây giờ, với ngoại lệ này được thêm vào, hãy tạo trình lắng nghe sự kiện của chúng ta!
Và thế là xong! Chỉ với một chút JavaScript, chúng ta đã thêm một hàm trợ năng vào chú giải công cụ của mình. Và đó chỉ là khởi đầu cho những gì chúng ta có thể làm với trình lắng nghe sự kiện
Có một sự kiện khác mà chúng ta sẽ thường xuyên sử dụng. Cái này phát hiện khi phần tử ngừng nhận tiêu điểm. Trình lắng nghe sự kiện này rất quan trọng và hầu hết thời gian, bạn sẽ sử dụng nó để đảo ngược các thay đổi có thể đã thực hiện với trình lắng nghe sự kiện
Chúng ta hãy quay lại với chú giải công cụ. Hiện tại, nó có một vấn đề: nếu bạn nhấn phím Esc để đóng chú giải công cụ, sau đó bạn lại tập trung vào cùng một phần tử, chú giải công cụ sẽ không xuất hiện. Tại sao? Bởi vì chúng tôi đã thêm lớp
WCAG định nghĩa thay đổi ngữ cảnh là "những thay đổi lớn, nếu được thực hiện mà người dùng không nhận thức được, có thể làm mất phương hướng những người dùng không thể xem toàn bộ trang cùng một lúc". Một số ví dụ về thay đổi ngữ cảnh bao gồm:
Với những điều đã nói, có ít nhất một mẫu thành phần có thể hưởng lợi từ trình lắng nghe sự kiện này trong một số trường hợp, nhưng tôi sẽ đề cập đến nó sau khi tôi bắt đầu nói về các thành phần, vì vậy chúng ta hãy ghim chủ đề đó vào lúc này.
Bây giờ, đây là thứ chúng ta sẽ sử dụng thường xuyên! Phương pháp này từ API HTMLElement cho phép chúng ta đưa tiêu điểm bàn phím vào một phần tử cụ thể. Theo mặc định, nó sẽ vẽ chỉ báo tiêu điểm trong phần tử và sẽ cuộn trang đến vị trí của phần tử. Hành vi này có thể được thay đổi bằng một vài tham số:
Sau đó, chúng ta sẽ thêm trình lắng nghe sự kiện
Và thế là xong! Phương pháp này sẽ rất hữu ích trong nhiều thành phần kết hợp với thuộc tính
Tôi đã đề cập đến thuộc tính
Hãy ghi nhớ một vài điều trước khi nghĩ đến việc sử dụng thuộc tính này:
Sự kết hợp các công cụ này sẽ hữu ích cho mục đích trợ năng bàn phím. Bây giờ chúng ta hãy bắt đầu xem chúng hoạt động như thế nào!

Chúng ta đã học cách tạo chú giải công cụ trong phần trước và tôi đã đề cập đến cách cải thiện chú giải công cụ này bằng JavaScript, nhưng có một mẫu khác cho loại thành phần này được gọi là toggletip, đó là chú giải công cụ hoạt động khi bạn nhấp vào chúng, thay vì di chuột qua chúng.
Chúng ta hãy kiểm tra danh sách nhanh những gì chúng ta cần để đảm bảo điều đó xảy ra:
Ý tưởng là đưa HTML cần thiết vào bên trong phần tử với
Như Heydon đề cập trong cuốn sách của mình, chúng tôi sử dụng phương pháp này bằng cách đầu tiên xóa nội dung HTML của vùng chứa rồi sử dụng
Sau khi hoàn thành, đã đến lúc thêm khả năng truy cập bằng bàn phím vào thành phần này. Chúng ta không cần phải làm cho nội dung của toggletip hiển thị khi bạn nhấn nút vì một ngữ nghĩa HTML tốt đã làm điều đó cho chúng ta. Chúng ta cần làm cho nội dung của toggletip ngừng hiển thị khi bạn nhấn phím Esc và khi bạn ngừng tập trung vào nút này. Nó hoạt động rất giống với những gì chúng ta đã làm đối với chú giải công cụ trong phần trước làm ví dụ, vì vậy hãy bắt đầu làm việc với điều đó. Đầu tiên, chúng ta sẽ sử dụng trình lắng nghe sự kiện
Và bây giờ, chúng ta cần kiểm tra sự kiện
Và đây là kết quả của chúng ta!
Xem Bút [Bản demo Toggletip [phân nhánh]](https://codepen.io/smashingmag/pen/WNyjeJK) của Cristian Diaz.
Xem Bút Bản demo Toggletip [phân nhánh] của Cristian Diaz.
Như tôi đã đề cập, bản demo này hoạt động rất giống với chú giải công cụ mà chúng ta đã tạo, nhưng tôi làm vậy vì một lý do. Trừ khi bạn đang tạo ra thứ gì đó rất khác thường, những mẫu đó sẽ tự lặp lại khá thường xuyên.
Như Stephanie Eckles đã đề cập trong bài viết “4 bài kiểm tra bắt buộc trước khi vận chuyển các tính năng mới”, có một số hành vi dự kiến mà người dùng bàn phím mong đợi từ các thành phần, chẳng hạn như có thể đóng thứ gì đó bạn vừa mở bằng cách nhấn phím Esc hoặc có thể điều hướng một nhóm các tùy chọn liên quan bằng các phím Mũi tên.
Nếu bạn ghi nhớ những mẫu đó, bạn sẽ nhận thấy sự chồng chéo trong các hành vi của một số thành phần nhất định và điều đó sẽ lặp lại khi bạn bắt đầu tạo mã JavaScript để đảm bảo khả năng truy cập bằng bàn phím. Vì vậy, hãy ghi nhớ danh sách này để hiểu được các yêu cầu mà các thành phần bạn đang tạo sẽ cần.
Nói về điều đó, chúng ta hãy kiểm tra một mẫu thành phần phổ biến khác.

Với các tab, đây là hành vi mong đợi đối với những phần tử sau:
Chúng tôi đang sử dụng
Đã đến lúc thêm điều hướng bằng các phím mũi tên. Đối với điều này, chúng ta sẽ cần tạo một mảng với các tab và sau đó tạo một hàm cho nó. Bước tiếp theo của chúng ta là kiểm tra tab nào là tab đầu tiên và tab cuối cùng trong danh sách. Điều này rất quan trọng vì hành động sẽ xảy ra khi bạn nhấn một phím sẽ thay đổi nếu tiêu điểm bàn phím nằm trên một trong những phần tử đó.
Sau đó, chúng ta sẽ thêm trình lắng nghe sự kiện
Đây là những gì đang xảy ra ở đây:
Như tôi đã đề cập, đây là một quy trình rất giống nhau. Thay vì trừ một khỏi kết quả
Trước tiên, hãy tạo hàm hiển thị bảng điều khiển.
Những gì chúng ta đang làm ở đây là, một lần nữa, kiểm tra thuộc tính
Nếu đúng như vậy, chúng ta sẽ làm cho nó có thể tab bằng bàn phím bằng cách xóa thuộc tính
Nếu không tương ứng,
Bây giờ, tất cả những gì chúng ta cần làm là thêm trình lắng nghe sự kiện
Và thế là xong! Chúng tôi đã tạo chức năng để làm cho các tab hoạt động, nhưng chúng tôi có thể làm một số việc khác nếu cần.
Theo ARIA Authoring Practices Guide (APG), chúng ta có thể làm cho nội dung được hiển thị hiển thị khi bạn lấy nét vào tab. Khái niệm này thường được gọi là theo dõi tiêu điểm và có thể hữu ích cho người dùng bàn phím và trình đọc màn hình vì nó cho phép điều hướng dễ dàng hơn qua nội dung.
Tuy nhiên, bạn cần lưu ý một số điều sau:
Và thế là xong! Bây giờ, nội dung được hiển thị sẽ hoạt động mà không cần phải nhấp vào tab. Thực hiện điều đó hay chỉ hoạt động khi nhấp là tùy thuộc vào bạn và đáng ngạc nhiên là một câu hỏi rất tinh tế. Cá nhân tôi sẽ chỉ làm cho nó hiển thị khi bạn nhấn tab vì tôi nghĩ rằng trải nghiệm thay đổi thuộc tính
Đây là một nhiệm vụ rất dễ dàng. Chúng ta có thể tạo thêm một cặp câu lệnh
Và đây là kết quả của chúng ta!
Xem Bút [Bản demo tab [phân nhánh]](https://codepen.io/smashingmag/pen/YzvVXWw) của Cristian Diaz.
Xem Bút Bản demo tab [phân nhánh] của Cristian Diaz.
Với mã này, chúng tôi đã làm cho thành phần này có thể truy cập được đối với người dùng bàn phím và trình đọc màn hình. Điều này cho thấy các khái niệm cơ bản của trình lắng nghe sự kiện

Thực sự rất dễ, nhưng bạn cần lưu ý một điều: rất có thể nút sẽ mở hộp thoại và hộp thoại nằm rất xa trong DOM. Vì vậy, bạn cần quản lý tiêu điểm theo chương trình khi bạn quản lý thành phần này. Có một vấn đề nhỏ ở đây: bạn cần lưu trữ phần tử nào đã mở modal để chúng ta có thể trả về tiêu điểm bàn phím trở lại phần tử này tại thời điểm chúng ta đóng nó.
May mắn thay, có một cách dễ dàng để thực hiện điều đó, nhưng hãy bắt đầu bằng cách tạo đánh dấu cho trang web của chúng ta:
Như tôi đã đề cập, modal và button cách xa nhau trong DOM. Điều này sẽ giúp tạo bẫy tiêu điểm dễ dàng hơn sau này, nhưng bây giờ, chúng ta hãy kiểm tra ngữ nghĩa của modal:
Rất đơn giản:
Bây giờ, chúng ta cần tạo hàm để đóng hộp thoại:
Đây là lý do tại sao việc lưu trữ phần tử phù hợp lại quan trọng. Khi chúng ta đóng hộp thoại, chúng ta sẽ đưa tiêu điểm bàn phím trở lại phần tử đã mở hộp thoại đó. Với những hàm đã tạo, tất cả những gì chúng ta phải làm là thêm trình lắng nghe sự kiện cho những hàm đó! Hãy nhớ rằng chúng ta cũng cần phải đóng hộp thoại khi bạn nhấn phím Esc.
Hiện tại, trông nó rất đơn giản. Nhưng nếu chỉ có vậy, hộp thoại sẽ không được coi là một mẫu phức tạp cho khả năng truy cập, phải không? Đây là nơi chúng ta cần tạo một tác vụ rất quan trọng cho thành phần này và chúng ta có hai cách để thực hiện.
Một trong số đó là kiểm tra từng phần tử có thể được tab bằng bàn phím, sau đó lưu trữ phần tử đầu tiên và phần tử cuối cùng, và thực hiện như sau:
Tôi không muốn sử dụng cách tiếp cận này làm giải pháp chính vì nó không hoàn hảo. Đó là một số tình huống mà cách tiếp cận này không đề cập đến.
Đầu tiên là nó không tính đến trình đọc màn hình, đặc biệt là trình đọc màn hình di động. Như Rahul Kumar đã đề cập trong bài viết “Bẫy lấy nét để trợ năng (A11Y)”, Talkback và Voiceover cho phép người dùng cử chỉ và chạm đúp để điều hướng đến phần tử có thể lấy nét tiếp theo hoặc trước đó và những cử chỉ đó không thể được phát hiện bằng trình lắng nghe sự kiện vì về mặt kỹ thuật, những cử chỉ đó là điều không xảy ra trong trình duyệt. Có một giải pháp cho vấn đề đó, nhưng tôi sẽ ghim chủ đề đó vào một lúc.
Mối quan tâm khác là cách tiếp cận bẫy tập trung này có thể dẫn đến những hành vi kỳ lạ nếu bạn sử dụng một số kết hợp các thành phần có thể lập tab. Ví dụ, hãy lấy ví dụ về hộp thoại này:

Về mặt kỹ thuật, phần tử có thể tab đầu tiên là
Nếu chúng ta muốn tạo ra một giải pháp đáng tin cậy hơn, cách tiếp cận tốt nhất là sử dụng thuộc tính
Lưu ý: Điều quan trọng cần lưu ý là mặc dù bẫy tiêu điểm và sử dụng
Điều đầu tiên chúng ta sẽ làm là chọn tất cả các khu vực không có vai trò
Bây giờ chúng ta cần quay lại hàm
Còn khi bạn đóng modal thì sao? Bạn cần đến hàm
Và đây là kết quả của chúng ta!
Xem Bút [Kiểm tra chế độ [phân nhánh]](https://codepen.io/smashingmag/pen/NWzjqYM) của Cristian Diaz.
Xem Bút Kiểm tra chế độ [phân nhánh] của Cristian Diaz.
Giả sử bạn không cảm thấy thoải mái khi sử dụng thuộc tính
Vì vậy, cho dù bạn quyết định sử dụng thuộc tính
Bạn có thể nhận thấy đánh dấu mà tôi đã sử dụng và tôi đã không sử dụng phần tử
Nếu bạn chưa nghe về phần tử này, nó có một số chức năng để mở và đóng hộp thoại, cũng như một số chức năng mới sẽ hữu ích khi chúng ta tạo hộp thoại. Nếu bạn muốn kiểm tra cách thức hoạt động của nó, bạn có thể xem video của Kevin Powell về phần tử này.
Điều đó không có nghĩa là bạn không nên sử dụng nó. Tình hình của Accessibility về phần tử này đang được cải thiện, nhưng hãy nhớ rằng bạn vẫn cần cân nhắc một số chi tiết nhất định để đảm bảo nó hoạt động bình thường.
Với những điều đã nói, làm sao bạn có thể biết được những yêu cầu nào bạn sẽ cần cho một thành phần? Đây là câu trả lời có nhiều sắc thái mà bài viết này không thể đề cập đến. Có một số tài nguyên như Kho lưu trữ các thành phần có thể truy cập của Scott O’Hara hoặc Hệ thống thiết kế của chính phủ Anh, nhưng đây là một câu hỏi không có câu trả lời đơn giản. Điều quan trọng nhất về chủ đề này là luôn thử nghiệm chúng với người dùng khuyết tật để biết những lỗi nào có thể xảy ra về mặt khả năng truy cập.
Thật ấn tượng khi thấy những gì bạn có thể làm để có khả năng truy cập bằng bàn phím khi bạn nhận thấy hầu hết thời gian, công việc được thực hiện bằng cùng các công cụ cơ bản. Khi bạn hiểu những gì mình cần làm, bạn có thể kết hợp các công cụ đó để tạo ra trải nghiệm người dùng tuyệt vời cho người dùng bàn phím!
Đối với mục đích truy cập bằng bàn phím, hầu hết công việc được thực hiện bằng các công cụ cơ bản mở ra nhiều khả năng tương tác bằng bàn phím. Bài viết này đề cập đến một bộ công cụ mà bạn có thể kết hợp vào các thành phần khác nhau để cải thiện khả năng truy cập cho người dùng bàn phím.
Những điều cơ bản
Hầu hết thời gian, công việc của bạn với JavaScript để cải thiện khả năng truy cập bàn phím của các thành phần sẽ được thực hiện chỉ bằng một số ít công cụ, bao gồm việc sử dụng trình lắng nghe sự kiện và một số phương thức JavaScript của một số API Web có thể giúp chúng ta thực hiện nhiệm vụ này.Một trong những công cụ quan trọng nhất mà chúng ta có để thêm tính tương tác vào các dự án của mình là sự tồn tại của sự kiện, đây là quá trình thực thi các hàm kích hoạt khi phần tử bạn đang kiểm tra nhận được thay đổi.
Sự kiện keydown
Một ví dụ về sự kiện mà bạn có thể lắng nghe bằng API Web này là sự kiện keydown
, sự kiện này kiểm tra thời điểm một phím được nhấn.Hiện tại, điều này không được sử dụng để thêm khả năng truy cập bàn phím vào các phần tử như nút hoặc liên kết vì theo mặc định, khi bạn thêm Trình lắng nghe sự kiện
click
cho chúng, điều này cũng sẽ kích hoạt sự kiện khi bạn sử dụng phím Enter (cho nút và liên kết) và phím Space (chỉ dành cho nút). Thay vào đó, tiện ích của sự kiện keydown
xuất hiện khi bạn cần thêm chức năng cho các phím khác.Để thêm một ví dụ, chúng ta hãy quay lại chú giải công cụ mà chúng ta đã tạo trong phần đầu của bài viết này. Tôi đã đề cập rằng chú giải công cụ này cần được đóng khi bạn nhấn phím Esc. Chúng ta cần một trình lắng nghe sự kiện
keydown
để kiểm tra xem phím được nhấn có phải là Esc hay không. Để làm được điều đó, chúng ta cần phát hiện phím được nhấn của sự kiện. Trong trường hợp này, chúng ta sẽ kiểm tra thuộc tính của sự kiện key
.Chúng ta sẽ sử dụng keycode.info để kiểm tra bản dump sự kiện cho phím này. Nếu bạn nhấn phím Esc trên trang này, bạn sẽ thấy rằng
e.key
bằng "Escape"
.Lưu ý: Có hai cách khác để phát hiện phím đã nhấn, đó là kiểm tra
e.keyCode
và e.which
. Chúng sẽ trả về một số. Trong trường hợp của phím Esc, nó sẽ là 27. Nhưng hãy nhớ rằng đó là những phương án thay thế đã lỗi thời và mặc dù chúng vẫn hoạt động, e.key
là lựa chọn được ưu tiên.Với phương án đó, chúng ta cần chọn các nút và thêm trình lắng nghe sự kiện. Cách tiếp cận của tôi đối với vấn đề này là sử dụng trình lắng nghe sự kiện này để thêm một lớp vào nút và thêm lớp này làm ngoại lệ để hiển thị nó bằng cách sử dụng lớp giả
:not()
. Hãy bắt đầu thay đổi CSS của chúng ta một chút:
Mã:
button:not(.hide-tooltip):hover + [role="tooltip"],button:not(.hide-tooltip):focus + [role="tooltip"],[role="tooltip"]:hover { display: block;}
Mã:
const buttons = [...document.querySelectorAll("button")]buttons.forEach(element => { element.addEventListener("keydown", (e) => { if (e.key === "Escape") { element.classList.add("hide-tooltip") } })})
keydown
. Đây sẽ là một công cụ quan trọng để cải thiện khả năng truy cập bàn phím cho nhiều thành phần, nhưng có một trình lắng nghe sự kiện khác mà chúng ta nên cân nhắc.blur
Sự kiện
Có một sự kiện khác mà chúng ta sẽ thường xuyên sử dụng. Cái này phát hiện khi phần tử ngừng nhận tiêu điểm. Trình lắng nghe sự kiện này rất quan trọng và hầu hết thời gian, bạn sẽ sử dụng nó để đảo ngược các thay đổi có thể đã thực hiện với trình lắng nghe sự kiện keydown
.Chúng ta hãy quay lại với chú giải công cụ. Hiện tại, nó có một vấn đề: nếu bạn nhấn phím Esc để đóng chú giải công cụ, sau đó bạn lại tập trung vào cùng một phần tử, chú giải công cụ sẽ không xuất hiện. Tại sao? Bởi vì chúng tôi đã thêm lớp
hide-tooltip
khi bạn nhấn phím Esc, nhưng chúng tôi chưa bao giờ xóa lớp này. Đây là lúc blur
phát huy tác dụng. Hãy thêm một trình lắng nghe sự kiện để khôi phục chức năng này.
Mã:
element.addEventListener("blur", (e) => { if (element.classList.contains("hide-tooltip")) { element.classList.remove("hide-tooltip"); }});
Các trình lắng nghe sự kiện khác (và lý do tại sao bạn có thể không cần chúng)
Tôi đã đề cập rằng chúng ta sẽ cần hai trình lắng nghe sự kiện trong bộ công cụ của mình, nhưng có những trình lắng nghe sự kiện khác mà bạn có thể sử dụng, nhưfocusout
hoặc focus
. Tuy nhiên, tôi nghĩ rằng các trường hợp sử dụng cho chúng khá khan hiếm. Có một đề cập đặc biệt đến focus
vì ngay cả khi bạn có thể tìm thấy các trường hợp sử dụng tốt cho nó, bạn vẫn cần phải rất cẩn thận. Rốt cuộc, nếu bạn không sử dụng nó đúng cách, bạn có thể gây ra thay đổi ngữ cảnh.WCAG định nghĩa thay đổi ngữ cảnh là "những thay đổi lớn, nếu được thực hiện mà người dùng không nhận thức được, có thể làm mất phương hướng những người dùng không thể xem toàn bộ trang cùng một lúc". Một số ví dụ về thay đổi ngữ cảnh bao gồm:
- Mở một cửa sổ mới;
- Thay đổi đáng kể bố cục trang web của bạn;
- Di chuyển tiêu điểm đến một phần khác của trang web.
Nếu bạn không cẩn thận, việc sử dụng sai một hàm lắng nghe sự kiệnKhi bất kỳ thành phần giao diện người dùng nào nhận được tiêu điểm, nó không khởi tạo sự thay đổi ngữ cảnh.
— Tiêu chí thành công 3.2.1: Thứ tự tiêu điểm
focus
có thể tạo ra sự thay đổi ngữ cảnh. Điều đó có nghĩa là bạn không nên sử dụng nó không? Không hẳn vậy, nhưng thành thật mà nói, tôi khó có thể tìm ra cách sử dụng cho sự kiện này. Hầu hết thời gian, bạn sẽ sử dụng lớp giả :focus
để tạo các chức năng tương tự.Với những điều đã nói, có ít nhất một mẫu thành phần có thể hưởng lợi từ trình lắng nghe sự kiện này trong một số trường hợp, nhưng tôi sẽ đề cập đến nó sau khi tôi bắt đầu nói về các thành phần, vì vậy chúng ta hãy ghim chủ đề đó vào lúc này.
focus()
Phương pháp
Bây giờ, đây là thứ chúng ta sẽ sử dụng thường xuyên! Phương pháp này từ API HTMLElement cho phép chúng ta đưa tiêu điểm bàn phím vào một phần tử cụ thể. Theo mặc định, nó sẽ vẽ chỉ báo tiêu điểm trong phần tử và sẽ cuộn trang đến vị trí của phần tử. Hành vi này có thể được thay đổi bằng một vài tham số:-
preventScroll
Khi được đặt thànhtrue
, sẽ khiến trình duyệt không cuộn cho đến khi phần tử được lấy nét theo chương trình được hiển thị. -
focusVisible
Khi được đặt thànhfalse
, sẽ khiến phần tử được lấy nét theo chương trình được hiển thị không hiển thị chỉ báo lấy nét của nó. Thuộc tính này hiện chỉ hoạt động trên Firefox.
tabindex
với một số nguyên âm để có thể lấy nét. Bạn có thể xem cách tabindex
hoạt động trong phần đầu tiên của hướng dẫn này.
Mã:
Bring focus [HEADING=2]Nội dung modal[/HEADING]
click
vào nút để làm cho cửa sổ hộp thoại được lấy nét:
Mã:
const button = document.querySelector("#openModal");const modal = document.querySelector("#modal")button.addEventListener("click", () => { modal.focus()})
keydown
, vì vậy, việc hiểu cách thức hoạt động của cả hai là rất quan trọng.Thay đổi thuộc tính HTML bằng JavaScript
Một số thuộc tính HTML cần được sửa đổi bằng JavaScript để tạo khả năng truy cập trong các mẫu thành phần phức tạp. Hai thuộc tính quan trọng nhất đối với khả năng truy cập bằng bàn phím làtabindex
và inert
mới được thêm vào gần đây. tabindex
có thể được sửa đổi bằng setAttribute
. Thuộc tính này yêu cầu hai tham số:-
name
Nó kiểm tra tên của thuộc tính mà bạn muốn sửa đổi. -
value
Nó sẽ thêm chuỗi mà thuộc tính này yêu cầu nếu nó không yêu cầu một thuộc tính cụ thể (ví dụ, nếu bạn thêm các thuộc tínhhidden
hoặccontenteditable
, bạn sẽ cần sử dụng một chuỗi rỗng).
Mã:
const button = document.querySelector("button")button.setAttribute("tabindex", "-1")
setAttribute
sẽ giúp ích rất nhiều cho khả năng truy cập nói chung. (Tôi sử dụng nó rất nhiều để thay đổi các thuộc tính ARIA khi cần!) Nhưng khi chúng ta nói về khả năng truy cập bàn phím, tabindex
gần như là thuộc tính duy nhất bạn sẽ sửa đổi bằng phương pháp này.Tôi đã đề cập đến thuộc tính
inert
trước đó và thuộc tính này hoạt động hơi khác một chút vì nó có thuộc tính riêng trong HTMLElement Web API. HTMLElement.inert
là giá trị boolean cho phép chúng ta chuyển đổi thuộc tính inert
.Hãy ghi nhớ một vài điều trước khi nghĩ đến việc sử dụng thuộc tính này:
- Bạn sẽ cần một polyfill vì nó chưa được triển khai đầy đủ trong tất cả các trình duyệt và vẫn còn khá mới. Polyfill này do các kỹ sư Chrome tạo ra hoạt động khá tốt trong các thử nghiệm mà tôi đã thực hiện, vì vậy nếu bạn cần thuộc tính này, đây là một cách tiếp cận an toàn, nhưng hãy nhớ rằng nó có thể có những hành vi không mong muốn.
- Bạn cũng có thể sử dụng
setAttribute
để thay đổi thuộc tính này! Cả hai đều hoạt động tốt như nhau, ngay cả với polyfill. Tùy thuộc vào bạn quyết định sử dụng cái nào.
Mã:
const button = document.querySelector("button")// Cú pháp với HTMLElement.inertbutton.inert = true// Cú pháp với Element.setAttribute()button.setAttribute("inert", "")
Các mẫu thành phần
Toggletips

Chúng ta đã học cách tạo chú giải công cụ trong phần trước và tôi đã đề cập đến cách cải thiện chú giải công cụ này bằng JavaScript, nhưng có một mẫu khác cho loại thành phần này được gọi là toggletip, đó là chú giải công cụ hoạt động khi bạn nhấp vào chúng, thay vì di chuột qua chúng.
Chúng ta hãy kiểm tra danh sách nhanh những gì chúng ta cần để đảm bảo điều đó xảy ra:
- Khi bạn nhấn nút, thông tin sẽ được thông báo cho trình đọc màn hình. Điều đó sẽ xảy ra khi bạn nhấn lại nút. Nhấn nút sẽ không đóng toggletip.
- Toggletip sẽ đóng khi bạn nhấp ra ngoài toggletip, dừng tập trung vào nút hoặc nhấn phím Esc.
Mã:
Nếu bạn cần kiểm tra thêm thông tin, hãy kiểm tra tại đây ? Thông tin thêm
role="status"
. Điều đó sẽ khiến trình đọc màn hình thông báo nội dung khi bạn nhấp vào. Chúng tôi đang sử dụng phần tử button
để làm cho nó có thể tab. Bây giờ, hãy tạo tập lệnh để hiển thị nội dung!
Mã:
toggletipButton.addEventListener("click", () => { toggletipInfo.innerHTML = ""; setTimeout(() => { toggletipInfo.innerHTML = toggletipContent; }, 100);});
setTimeout
để thêm nội dung đó nhằm đảm bảo rằng mỗi khi bạn nhấp vào, nội dung đó sẽ được thông báo cho người dùng trình đọc màn hình. Bây giờ chúng ta cần kiểm tra xem khi bạn nhấp vào nơi khác, nội dung sẽ ngừng hiển thị.
Mã:
document.addEventListener("click", (e) => { if (toggletipContainer !== e.target) { toggletipInfo.innerHTML = "" }})
keydown
để kiểm tra thời điểm phím Esc được nhấn:
Mã:
toggletipContainer.addEventListener("keydown", (e) => { if (e.key === "Escape") { toggletipInfo.innerHTML = "" }})
blur
để thực hiện tương tự. Cái này phải nằm trên phần tử button
thay vì trên chính container.
Mã:
toggletipButton.addEventListener("blur", () => { toggletipInfo.innerHTML = "";});
Xem Bút [Bản demo Toggletip [phân nhánh]](https://codepen.io/smashingmag/pen/WNyjeJK) của Cristian Diaz.
Xem Bút Bản demo Toggletip [phân nhánh] của Cristian Diaz.
Như tôi đã đề cập, bản demo này hoạt động rất giống với chú giải công cụ mà chúng ta đã tạo, nhưng tôi làm vậy vì một lý do. Trừ khi bạn đang tạo ra thứ gì đó rất khác thường, những mẫu đó sẽ tự lặp lại khá thường xuyên.
Như Stephanie Eckles đã đề cập trong bài viết “4 bài kiểm tra bắt buộc trước khi vận chuyển các tính năng mới”, có một số hành vi dự kiến mà người dùng bàn phím mong đợi từ các thành phần, chẳng hạn như có thể đóng thứ gì đó bạn vừa mở bằng cách nhấn phím Esc hoặc có thể điều hướng một nhóm các tùy chọn liên quan bằng các phím Mũi tên.
Nếu bạn ghi nhớ những mẫu đó, bạn sẽ nhận thấy sự chồng chéo trong các hành vi của một số thành phần nhất định và điều đó sẽ lặp lại khi bạn bắt đầu tạo mã JavaScript để đảm bảo khả năng truy cập bằng bàn phím. Vì vậy, hãy ghi nhớ danh sách này để hiểu được các yêu cầu mà các thành phần bạn đang tạo sẽ cần.
Nói về điều đó, chúng ta hãy kiểm tra một mẫu thành phần phổ biến khác.
Tab

Roving
Giao diện có tab là các mẫu mà bạn vẫn có thể thấy thỉnh thoảng. Chúng có một chức năng rất thú vị khi chúng ta nói về điều hướng bàn phím: khi bạn nhấn phím Tab, nó sẽ chuyển đến bảng tab đang hoạt động. Để điều hướng giữa danh sách tab, bạn sẽ cần sử dụng các phím Mũi tên. Đây là một kỹ thuật được gọi là di chuyển tabindex
tabindex
bao gồm việc loại bỏ khả năng của các phần tử không hoạt động để được tababble bằng cách thêm thuộc tính tabindex="-1"
và sau đó sử dụng các phím khác để cho phép điều hướng giữa các mục đó.Với các tab, đây là hành vi mong đợi đối với những phần tử sau:
- Khi bạn nhấn các phím Trái hoặc Lên, nó sẽ di chuyển tiêu điểm bàn phím vào tab trước đó. Nếu tiêu điểm nằm ở tab đầu tiên, nó sẽ di chuyển tiêu điểm đến tab cuối cùng.
- Khi bạn nhấn các phím Phải hoặc Xuống, nó sẽ di chuyển tiêu điểm bàn phím đến tab tiếp theo. Nếu tiêu điểm nằm ở tab đầu tiên, nó sẽ di chuyển tiêu điểm đến tab cuối cùng.
tabindex
bằng setAttribute
, trình lắng nghe sự kiện keydown
và phương thức focus()
. Hãy bắt đầu bằng cách kiểm tra đánh dấu của thành phần này:
Mã:
[LIST]
[*] Tomato
[*] Onion
[*] Cần tây
[*] Cà rốt [/LIST]
aria-selected="true"
để hiển thị tab nào đang hoạt động và chúng tôi đang thêm tabindex="-1"
để không thể chọn các tab không hoạt động bằng phím Tab. Tabpanels phải có thể tab nếu không có phần tử có thể tab bên trong nó, vì vậy đây là lý do tại sao tôi đã thêm thuộc tính tabindex="0"
và các tabpanels không hoạt động được ẩn bằng cách sử dụng thuộc tính hidden
.Đã đến lúc thêm điều hướng bằng các phím mũi tên. Đối với điều này, chúng ta sẽ cần tạo một mảng với các tab và sau đó tạo một hàm cho nó. Bước tiếp theo của chúng ta là kiểm tra tab nào là tab đầu tiên và tab cuối cùng trong danh sách. Điều này rất quan trọng vì hành động sẽ xảy ra khi bạn nhấn một phím sẽ thay đổi nếu tiêu điểm bàn phím nằm trên một trong những phần tử đó.
Mã:
const TABS = [...TABLIST.querySelectorAll("[role='tab']")];const createKeyboardNavigation = () => { const firstTab = TABS[0]; const lastTab = TABS[TABS.length - 1];}
keydown
vào mỗi tab. Tôi sẽ bắt đầu bằng cách thêm chức năng với các mũi tên Left và Up.
Mã:
// Mã trước của hàm createKeyboardNavigationTABS.forEach((element) => { element.addEventListener("keydown", function (e) { if (e.key === "ArrowUp" || e.key === "ArrowLeft") { e.preventDefault(); if (element === firstTab) { lastTab.focus(); } else { const focusableElement = TABS.indexOf(element) - 1; TABS[focusableElement].focus(); } } }}
- Đầu tiên, chúng ta kiểm tra xem phím được nhấn có phải là Up hay Mũi tên Trái. Đối với điều đó, chúng ta kiểm tra
event.key
. - Nếu đúng như vậy, chúng ta cần ngăn các phím đó cuộn trang vì hãy nhớ rằng, theo mặc định, chúng sẽ làm như vậy. Chúng ta có thể sử dụng
e.preventDefault()
cho mục tiêu này. - Nếu phím được lấy nét là tab đầu tiên, nó sẽ tự động đưa tiêu điểm bàn phím đến tab cuối cùng. Điều này được thực hiện bằng cách gọi phương thức
focus()
để lấy tiêu điểm vào tab cuối cùng (mà chúng ta lưu trữ trong một biến). - Nếu không phải vậy, chúng ta cần kiểm tra vị trí của tab đang hoạt động. Khi chúng ta lưu trữ các phần tử tab trong một mảng, chúng ta có thể sử dụng phương thức
indexOf()
để kiểm tra vị trí. - Khi chúng ta đang cố gắng điều hướng đến tab trước đó, chúng ta có thể trừ
1
khỏi kết quả củaindexOf()
rồi tìm kiếm phần tử tương ứng trong mảngTABS
và lập trình để tập trung vào phần tử đó bằng phương thứcfocus()
.
Mã:
// Mã trước của hàm createKeyboardNavigationelse if (e.key === "ArrowDown" || e.key === "ArrowRight") { e.preventDefault(); if (element == lastTab) { firstTab.focus(); } else { const focusableElement = TABS.indexOf(element) + 1; TABS[focusableElement].focus(); }}
indexOf()
, chúng ta thêm 1
vì chúng ta muốn đưa tiêu điểm bàn phím đến phần tử tiếp theo.Hiển thị nội dung và thay đổi thuộc tính HTML
Chúng ta đã tạo điều hướng và bây giờ chúng ta cần hiển thị và ẩn nội dung cũng như thao tác các thuộc tính aria-selected
và tabindex
. Hãy nhớ rằng, chúng ta cần thực hiện điều đó khi tiêu điểm bàn phím nằm trên bảng điều khiển đang hoạt động và bạn nhấn Shift + Tab, tiêu điểm sẽ nằm trong tab đang hoạt động.Trước tiên, hãy tạo hàm hiển thị bảng điều khiển.
Mã:
const showActivePanel = (element) => { const selectedId = element.target.id; TABPANELS.forEach((e) => { e.hidden = "true"; }); const activePanel = document.querySelector( `[aria-labelledby="${selectedId}"]` ); activePanel.removeAttribute("hidden");}; { const selectedId = element.target.id; TABS.forEach((e) => { const id = e.getAttribute("id"); if (id === selectedId) { e.removeAttribute("tabindex", "0"); e.setAttribute("aria-selected", "true"); } else { e.setAttribute("tabindex", "-1"); e.setAttribute("aria-selected", "false"); } });};
id
và sau đó xem xét từng tab. Chúng ta sẽ kiểm tra xem id
của tab này có tương ứng với id
của phần tử được nhấn hay không.Nếu đúng như vậy, chúng ta sẽ làm cho nó có thể tab bằng bàn phím bằng cách xóa thuộc tính
tabindex
(vì nó là button
, do đó theo mặc định, nó có thể tab bằng bàn phím) hoặc bằng cách thêm thuộc tính tabindex="0"
. Ngoài ra, chúng tôi sẽ thêm một chỉ báo cho người dùng trình đọc màn hình biết rằng đây là tab đang hoạt động bằng cách thêm thuộc tính aria-selected="true"
.Nếu không tương ứng,
tabindex
và aria-selected
sẽ được đặt thành -1
và false
tương ứng.Bây giờ, tất cả những gì chúng ta cần làm là thêm trình lắng nghe sự kiện
click
vào mỗi tab để xử lý cả hai chức năng.
Mã:
TABS.forEach((element) => { element.addEventListener("click", (element) => { showActivePanel(element), handleSelectedTab(element); });});
Kích hoạt Tab khi lấy nét
Bạn có nhớ những gì tôi đã đề cập về trình lắng nghe sự kiện focus
không? Bạn nên cẩn thận khi sử dụng nó vì nó có thể vô tình tạo ra sự thay đổi ngữ cảnh, nhưng nó có một số công dụng và thành phần này là cơ hội hoàn hảo để sử dụng nó!Theo ARIA Authoring Practices Guide (APG), chúng ta có thể làm cho nội dung được hiển thị hiển thị khi bạn lấy nét vào tab. Khái niệm này thường được gọi là theo dõi tiêu điểm và có thể hữu ích cho người dùng bàn phím và trình đọc màn hình vì nó cho phép điều hướng dễ dàng hơn qua nội dung.
Tuy nhiên, bạn cần lưu ý một số điều sau:
- Nếu việc hiển thị nội dung có nghĩa là phải tạo nhiều bản kiến nghị và theo đó, làm chậm mạng, thì việc làm cho nội dung được hiển thị theo tiêu điểm là không mong muốn.
- Nếu nó thay đổi bố cục theo cách đáng kể, thì đó có thể được coi là thay đổi ngữ cảnh. Điều đó phụ thuộc vào loại nội dung bạn muốn hiển thị và việc thay đổi ngữ cảnh theo tiêu điểm là vấn đề về khả năng truy cập, như tôi đã giải thích trước đó.
tiêu điểm
. Chúng ta có thể chỉ cần sao chép và dán trình lắng nghe sự kiện mà chúng ta đã tạo và chỉ cần thay đổi click
thành focus
.
Mã:
TABS.forEach((element) => { element.addEventListener("click", (element) => { showActivePanel(element), handleSelectedTab(element); }); element.addEventListener("focus", (element) => { showActivePanel(element), handleSelectedTab(element); });});
aria-selected
chỉ bằng cách tập trung vào phần tử có thể hơi khó hiểu. Tuy nhiên, đây chỉ là giả thuyết của tôi nên hãy cân nhắc những gì tôi nói và luôn kiểm tra với người dùng.Trình lắng nghe sự kiện
Chúng ta hãy quay lại keydown
bổ sungcreateKeyboardNavigation
một lúc. Có một vài phím chúng ta có thể thêm vào. Chúng ta có thể làm cho phím Home và End đưa tiêu điểm bàn phím đến tab đầu tiên và tab cuối cùng. Điều này hoàn toàn tùy chọn, vì vậy không sao nếu bạn không làm, nhưng chỉ để nhắc lại cách trình lắng nghe sự kiện keydown
giúp ích, tôi sẽ làm điều đó.Đây là một nhiệm vụ rất dễ dàng. Chúng ta có thể tạo thêm một cặp câu lệnh
if
để kiểm tra xem phím Home và End có được nhấn hay không và vì chúng ta đã lưu tab đầu tiên và tab cuối cùng trong các biến, nên bạn có thể tập trung chúng bằng phương thức focus()
.
Mã:
// Mã trước của hàm createKeyboardNavigationelse if (e.key === "Home") { e.preventDefault(); firstTab.focus()} else if (e.key === "End") { e.preventDefault(); lastTab.focus()}
Xem Bút [Bản demo tab [phân nhánh]](https://codepen.io/smashingmag/pen/YzvVXWw) của Cristian Diaz.
Xem Bút Bản demo tab [phân nhánh] của Cristian Diaz.
Với mã này, chúng tôi đã làm cho thành phần này có thể truy cập được đối với người dùng bàn phím và trình đọc màn hình. Điều này cho thấy các khái niệm cơ bản của trình lắng nghe sự kiện
keydown
, phương thức focus()
và những thay đổi mà chúng ta có thể thực hiện với setAttribute
hữu ích như thế nào để thao tác các thành phần phức tạp. Hãy kiểm tra thêm một điều nữa, một điều rất phức tạp, và cách thuộc tính inert
có thể giúp chúng ta xử lý nhiệm vụ này một cách dễ dàng.Modals

Mở và đóng hộp thoại
Hộp thoại là một mẫu khá phức tạp khi chúng ta nói về khả năng truy cập bằng bàn phím, vì vậy chúng ta hãy bắt đầu bằng một nhiệm vụ dễ dàng — mở và đóng hộp thoại.Thực sự rất dễ, nhưng bạn cần lưu ý một điều: rất có thể nút sẽ mở hộp thoại và hộp thoại nằm rất xa trong DOM. Vì vậy, bạn cần quản lý tiêu điểm theo chương trình khi bạn quản lý thành phần này. Có một vấn đề nhỏ ở đây: bạn cần lưu trữ phần tử nào đã mở modal để chúng ta có thể trả về tiêu điểm bàn phím trở lại phần tử này tại thời điểm chúng ta đóng nó.
May mắn thay, có một cách dễ dàng để thực hiện điều đó, nhưng hãy bắt đầu bằng cách tạo đánh dấu cho trang web của chúng ta:
Mã:
Mở modal [HEADING=2]Nội dung modal[/HEADING] [LIST]
[*] [URL=#]Liên kết modal 1[/URL]
[*] [URL=#]Liên kết modal 2[/URL]
[*] [URL=#]Liên kết modal 3[/URL] [/LIST] Đóng modal
-
role="dialog"
sẽ cung cấp cho phần tử ngữ nghĩa cần thiết cho trình đọc màn hình. Nó cần có nhãn để được nhận dạng là cửa sổ hộp thoại, vì vậy chúng ta sẽ sử dụng tiêu đề của modal làm nhãn bằng cách sử dụng thuộc tínharia-labelledby
. -
aria-modal="true"
giúp người dùng trình đọc màn hình chỉ có thể đọc nội dung của các phần tử con của phần tử, vì vậy nó chặn quyền truy cập từ trình đọc màn hình. Tuy nhiên, như bạn có thể thấy trên trangaria-modal
của a11ysupport.com, nó không được hỗ trợ đầy đủ, vì vậy bạn không thể chỉ dựa vào đó cho nhiệm vụ này. Nó sẽ hữu ích cho trình đọc màn hình hỗ trợ nó, nhưng bạn sẽ thấy có một cách khác để đảm bảo người dùng trình đọc màn hình không tương tác với bất kỳ thứ gì ngoài modal sau khi nó được mở. - Như tôi đã đề cập, chúng ta cần đưa tiêu điểm bàn phím vào modal của mình, vì vậy đây là lý do tại sao chúng ta thêm thuộc tính
tabindex="-1"
.
document.activeElement
để kiểm tra phần tử nào đang được tập trung vào bàn phím ngay bây giờ và lưu trữ nó trong một biến. Đây là cách tiếp cận của tôi cho nhiệm vụ này:
Mã:
letfocusedElementBeforeModalconst modal = document.querySelector("[role='dialog']");const modalOpenButton = document.querySelector("#openModal")const modalCloseButton = document.querySelector("#closeModal")const openModal = () => {focusedElementBeforeModal = document.activeElement modal.hidden = false; modal.focus();};
- Chúng ta lưu trữ nút đã mở hộp thoại;
- Sau đó, chúng ta hiển thị nút đó bằng cách xóa thuộc tính
hidden
; - Sau đó, chúng ta đưa tiêu điểm vào hộp thoại bằng phương thức
focus()
.
Bây giờ, chúng ta cần tạo hàm để đóng hộp thoại:
Mã:
const closeModal = () => { modal.hidden = true;focusedElementBeforeModal.focus()}
Mã:
modalOpenButton.addEventListener("click", () => openModal())modalCloseButton.addEventListener("click", () => closeModal())modal.addEventListener("keydown", (e) => { if (e.key === "Escape") { closeModal() }})
Tạo Focus Trap
Focus trap đảm bảo tiêu điểm bàn phím không thể thoát khỏi thành phần. Điều này rất quan trọng vì nếu người dùng bàn phím có thể tương tác với bất kỳ thứ gì bên ngoài một hộp thoại sau khi hộp thoại đó được mở, thì nó có thể tạo ra trải nghiệm rất khó hiểu. Chúng ta có hai cách để thực hiện điều đó ngay bây giờ.Một trong số đó là kiểm tra từng phần tử có thể được tab bằng bàn phím, sau đó lưu trữ phần tử đầu tiên và phần tử cuối cùng, và thực hiện như sau:
- Khi người dùng nhấn Shift + Tab và tiêu điểm bàn phím nằm trên phần tử có thể tab đầu tiên (hãy nhớ rằng, bạn có thể kiểm tra điều đó bằng
document.activeElement
), tiêu điểm sẽ chuyển đến phần tử có thể tab cuối cùng. - Khi người dùng nhấn Tab và tiêu điểm bàn phím nằm trên phần tử có thể tab cuối cùng, tiêu điểm bàn phím sẽ chuyển đến phần tử có thể tab đầu tiên.
Tôi không muốn sử dụng cách tiếp cận này làm giải pháp chính vì nó không hoàn hảo. Đó là một số tình huống mà cách tiếp cận này không đề cập đến.
Đầu tiên là nó không tính đến trình đọc màn hình, đặc biệt là trình đọc màn hình di động. Như Rahul Kumar đã đề cập trong bài viết “Bẫy lấy nét để trợ năng (A11Y)”, Talkback và Voiceover cho phép người dùng cử chỉ và chạm đúp để điều hướng đến phần tử có thể lấy nét tiếp theo hoặc trước đó và những cử chỉ đó không thể được phát hiện bằng trình lắng nghe sự kiện vì về mặt kỹ thuật, những cử chỉ đó là điều không xảy ra trong trình duyệt. Có một giải pháp cho vấn đề đó, nhưng tôi sẽ ghim chủ đề đó vào một lúc.
Mối quan tâm khác là cách tiếp cận bẫy tập trung này có thể dẫn đến những hành vi kỳ lạ nếu bạn sử dụng một số kết hợp các thành phần có thể lập tab. Ví dụ, hãy lấy ví dụ về hộp thoại này:

Về mặt kỹ thuật, phần tử có thể tab đầu tiên là
đầu vào
đầu tiên. Tuy nhiên, tất cả các đầu vào trong ví dụ này phải tập trung vào phần tử có thể tab cuối cùng (trong trường hợp này là phần tử button
) khi người dùng nhấn các phím Shift + Tab. Nếu không, nó có thể gây ra hành vi kỳ lạ nếu người dùng nhấn các phím đó khi tiêu điểm bàn phím đang ở đầu vào
thứ hai hoặc thứ ba.Nếu chúng ta muốn tạo ra một giải pháp đáng tin cậy hơn, cách tiếp cận tốt nhất là sử dụng thuộc tính
inert
để làm cho nội dung bên ngoài không thể truy cập được đối với trình đọc màn hình và người dùng bàn phím, đảm bảo họ chỉ có thể tương tác với nội dung của hộp thoại. Hãy nhớ rằng, điều này sẽ yêu cầu polyfill inert
để tăng thêm độ mạnh mẽ cho kỹ thuật này.Lưu ý: Điều quan trọng cần lưu ý là mặc dù bẫy tiêu điểm và sử dụng
inert
trong thực tế giúp đảm bảo khả năng truy cập bằng bàn phím cho các hộp thoại, nhưng chúng không hoạt động giống hệt nhau. Sự khác biệt chính là khi đặt tất cả các tài liệu ngoại trừ hộp thoại là inert
, thì nó vẫn cho phép bạn di chuyển ra khỏi trang web và tương tác với các thành phần của trình duyệt. Điều này có thể tốt hơn cho các mối quan ngại về bảo mật nhưng quyết định xem bạn muốn tạo bẫy tiêu điểm theo cách thủ công hay sử dụng thuộc tính inert
là tùy thuộc vào bạn.Điều đầu tiên chúng ta sẽ làm là chọn tất cả các khu vực không có vai trò
dialog.
Vì inert
sẽ xóa mọi tương tác của bàn phím và trình đọc màn hình với các phần tử và các phần tử con của chúng, chúng ta sẽ chỉ cần chọn các phần tử con trực tiếp của body.
Đây là lý do tại sao chúng ta để vùng chứa modal tồn tại ở cùng cấp với các thẻ như main
, header
hoặc footer
.
Mã:
// Bộ chọn này hoạt động tốt cho cấu trúc HTML cụ thể này. Điều chỉnh theo dự án của bạn.const nonModalAreas = document.querySelectorAll("body > *:not([role='dialog'])")
openModal
. Sau khi mở modal, chúng ta cần thêm thuộc tính inert
vào các phần tử đó. Đây sẽ là bước cuối cùng trong hàm:
Mã:
const openModal = () => { // Mã đã thêm trước đó nonModalAreas.forEach((element) => { element.inert = true })};
closeModal
và xóa thuộc tính này. Thuộc tính này cần phải được thực hiện trước khi mọi thứ khác trong mã chạy. Nếu không, trình duyệt sẽ không thể tập trung vào nút đã mở hộp thoại này.
Mã:
const closeModal = () => { nonModalAreas.forEach((element) => { element.inert = false; });// Mã đã thêm trước đó};
Xem Bút [Kiểm tra chế độ [phân nhánh]](https://codepen.io/smashingmag/pen/NWzjqYM) của Cristian Diaz.
Xem Bút Kiểm tra chế độ [phân nhánh] của Cristian Diaz.
Giả sử bạn không cảm thấy thoải mái khi sử dụng thuộc tính
inert
ngay bây giờ và muốn tạo bẫy tiêu điểm theo cách thủ công, như A11y Solutions đã chỉ ra. Bạn có thể làm gì để đảm bảo người dùng trình đọc màn hình không thể thoát khỏi chế độ modal? aria-modal
có thể giúp bạn, nhưng hãy nhớ rằng, hỗ trợ cho thuộc tính này khá không ổn định, đặc biệt là đối với Talkback và VoiceOver cho iOS. Vì vậy, điều tốt nhất tiếp theo mà chúng ta có thể làm là thêm thuộc tính aria-hidden="true"
vào tất cả các phần tử không phải là chế độ modal. Đây là một quy trình rất giống với quy trình chúng ta đã thực hiện cho thuộc tính inert
và bạn cũng có thể sử dụng các phần tử giống nhau trong mảng mà chúng ta đã sử dụng cho chủ đề này!
Mã:
const openModal = () => { //Mã đã thêm trước đó nonModalAreas.forEach((element) => { element.setAttribute("aria-hidden", "true") });};const closeModal = () => { nonModalAreas.forEach((element) => { element.removeAttribute("aria-hidden") }); // Mã đã thêm trước đó};
inert
hay tạo bẫy tiêu điểm theo cách thủ công, bạn vẫn có thể đảm bảo trải nghiệm người dùng cho người dùng bàn phím và trình đọc màn hình hoạt động tốt nhất.
Phần tử
tương đối mới và có lý do cho điều đó. Đúng vậy, phần tử này giúp ích rất nhiều bằng cách quản lý tiêu điểm vào hộp thoại và nút mở dễ dàng, nhưng như Scott O’Hara chỉ ra trong bài viết “Có hộp thoại mở”, nó vẫn có một số vấn đề về khả năng truy cập mà ngay cả với polyfill vẫn chưa được giải quyết hoàn toàn. Vì vậy, tôi quyết định sử dụng một cách tiếp cận mạnh mẽ hơn ở đó với đánh dấu.Nếu bạn chưa nghe về phần tử này, nó có một số chức năng để mở và đóng hộp thoại, cũng như một số chức năng mới sẽ hữu ích khi chúng ta tạo hộp thoại. Nếu bạn muốn kiểm tra cách thức hoạt động của nó, bạn có thể xem video của Kevin Powell về phần tử này.
Điều đó không có nghĩa là bạn không nên sử dụng nó. Tình hình của Accessibility về phần tử này đang được cải thiện, nhưng hãy nhớ rằng bạn vẫn cần cân nhắc một số chi tiết nhất định để đảm bảo nó hoạt động bình thường.
Các mẫu thành phần khác
Tôi có thể tiếp tục với nhiều mẫu thành phần, nhưng thành thật mà nói, tôi nghĩ rằng nó sẽ bắt đầu trở nên thừa thãi vì thực tế là các mẫu đó khá giống nhau giữa các loại thành phần khác nhau mà bạn có thể tạo ra. Trừ khi bạn phải tạo ra thứ gì đó rất khác thường, thì những mẫu mà chúng ta đã thấy ở đây là đủ rồi!Với những điều đã nói, làm sao bạn có thể biết được những yêu cầu nào bạn sẽ cần cho một thành phần? Đây là câu trả lời có nhiều sắc thái mà bài viết này không thể đề cập đến. Có một số tài nguyên như Kho lưu trữ các thành phần có thể truy cập của Scott O’Hara hoặc Hệ thống thiết kế của chính phủ Anh, nhưng đây là một câu hỏi không có câu trả lời đơn giản. Điều quan trọng nhất về chủ đề này là luôn thử nghiệm chúng với người dùng khuyết tật để biết những lỗi nào có thể xảy ra về mặt khả năng truy cập.
Kết thúc
Khả năng truy cập bằng bàn phím có thể khá khó, nhưng bạn có thể đạt được điều đó khi hiểu cách người dùng bàn phím tương tác với trang web và những nguyên tắc bạn cần ghi nhớ. Hầu hết thời gian, HTML và CSS sẽ thực hiện tốt công việc đảm bảo khả năng truy cập bằng bàn phím, nhưng đôi khi bạn sẽ cần JavaScript cho các mẫu phức tạp hơn.Thật ấn tượng khi thấy những gì bạn có thể làm để có khả năng truy cập bằng bàn phím khi bạn nhận thấy hầu hết thời gian, công việc được thực hiện bằng cùng các công cụ cơ bản. Khi bạn hiểu những gì mình cần làm, bạn có thể kết hợp các công cụ đó để tạo ra trải nghiệm người dùng tuyệt vời cho người dùng bàn phím!