Thông tin tổng quan cơ bản về cách tạo thành phần thích ứng, thích ứng và dễ tiếp cận, có nhiều lựa chọn để sắp xếp và lọc trải nghiệm người dùng.
Trong bài đăng này, tôi muốn chia sẻ suy nghĩ về cách tạo một thành phần chọn nhiều đối tượng. Hãy thử bản minh hoạ.
Nếu bạn thích video, đây là phiên bản YouTube của bài đăng này:
Tổng quan
Người dùng thường thấy các mục, đôi khi là rất nhiều mục. Trong những trường hợp này, bạn nên cung cấp cách giảm bớt danh sách để tránh quá tải lựa chọn. Bài đăng trên blog này khám phá cách lọc giao diện người dùng để giảm bớt các lựa chọn. Trình phân tích cú pháp thực hiện việc này bằng cách hiển thị các thuộc tính mục mà người dùng có thể chọn hoặc bỏ chọn, giúp giảm kết quả và do đó giảm quá tải lựa chọn.
Số lượt tương tác
Mục tiêu là cho phép nhanh chóng truyền tải các tuỳ chọn bộ lọc cho tất cả người dùng và các loại dữ liệu đầu vào khác nhau của họ. Giao diện này sẽ được phân phối với một cặp thành phần có thể thích ứng và thích ứng. Một thanh bên truyền thống bao gồm các hộp đánh dấu dành cho máy tính, bàn phím và trình đọc màn hình, và một <select
multiple>
dành cho người dùng cảm ứng.
Quyết định sử dụng tính năng chọn nhiều đối tượng được tích hợp sẵn cho thao tác chạm (chứ không phải cho máy tính) sẽ giúp tiết kiệm công việc và tạo ra công việc, nhưng tôi tin rằng sẽ mang lại trải nghiệm phù hợp mà không phải tốn nhiều mã hơn là xây dựng toàn bộ trải nghiệm thích ứng trong một thành phần.
Cảm ứng
Thành phần cảm ứng giúp tiết kiệm không gian và giúp người dùng tương tác chính xác trên thiết bị di động. Thao tác này giúp tiết kiệm không gian bằng cách thu gọn toàn bộ thanh bên của hộp đánh dấu vào một trải nghiệm chạm lớp phủ tích hợp <select>
. Giao diện này giúp nhập chính xác bằng cách hiển thị trải nghiệm lớp phủ cảm ứng lớn do hệ thống cung cấp.
Bàn phím và tay điều khiển trò chơi
Dưới đây là phần minh hoạ cách sử dụng <select multiple>
qua bàn phím.
Lựa chọn nhiều tính năng tích hợp sẵn này không tạo kiểu được và chỉ được cung cấp trong một bố cục nhỏ gọn, không phù hợp để đưa ra nhiều lựa chọn. Làm thế nào bạn thực sự không thể thấy nhiều lựa chọn trong chiếc hộp nhỏ bé đó? Mặc dù bạn có thể thay đổi kích thước, nhưng nó vẫn không thể sử dụng được như thanh bên của hộp đánh dấu.
Markup (note: đây là tên ứng dụng)
Cả hai thành phần này sẽ đều nằm trong cùng một phần tử <form>
. Kết quả của biểu mẫu này, cho dù là các hộp đánh dấu hay một mục nhiều lựa chọn, sẽ được quan sát và dùng để lọc lưới, nhưng cũng có thể được gửi đến máy chủ.
<form>
</form>
Thành phần hộp đánh dấu
Các nhóm hộp đánh dấu phải được gói trong một phần tử <fieldset>
và cung cấp một <legend>
.
Khi HTML được cấu trúc theo cấu trúc này, trình đọc màn hình và FormData sẽ tự động hiểu được mối quan hệ của các phần tử.
<form>
<fieldset>
<legend>New</legend>
… checkboxes …
</fieldset>
</form>
Khi đã nhóm được, hãy thêm <label>
và <input type="checkbox">
cho từng bộ lọc. Tôi đã chọn đưa mã vào một <div>
để thuộc tính CSS gap
có thể giãn cách đều nhau và duy trì căn chỉnh khi các nhãn xuất hiện nhiều dòng.
<form>
<fieldset>
<legend>New</legend>
<div>
<input type="checkbox" id="last 30 days" name="new" value="last 30 days">
<label for="last 30 days">Last 30 Days</label>
</div>
<div>
<input type="checkbox" id="last 6 months" name="new" value="last 6 months">
<label for="last 6 months">Last 6 Months</label>
</div>
</fieldset>
</form>
Thành phần <select multiple>
Một tính năng ít khi được sử dụng của phần tử <select>
là multiple
.
Khi thuộc tính này được sử dụng với phần tử <select>
, người dùng được phép chọn nhiều phần tử trong danh sách. Điều này giống như thay đổi hoạt động tương tác từ một danh sách radio thành một danh sách hộp đánh dấu.
<form>
<select multiple="true" title="Filter results by category">
…
</select>
</form>
Để gắn nhãn và tạo nhóm bên trong <select>
, hãy sử dụng phần tử <optgroup>
và cung cấp cho phần tử này giá trị và thuộc tính label
. Phần tử và giá trị thuộc tính này gần giống với các phần tử <fieldset>
và <legend>
.
<form>
<select multiple="true" title="Filter results by category">
<optgroup label="New">
…
</optgroup>
</select>
</form>
Bây giờ, hãy thêm các phần tử <option>
cho bộ lọc.
<form>
<select multiple="true" title="Filter results by category">
<optgroup label="New">
<option value="last 30 days">Last 30 Days</option>
<option value="last 6 months">Last 6 Months</option>
</optgroup>
</select>
</form>
Theo dõi thông tin đầu vào bằng bộ đếm để cung cấp thông tin cho công nghệ hỗ trợ
Kỹ thuật vai trò trạng thái được sử dụng trong trải nghiệm người dùng này để theo dõi và duy trì việc kiểm đếm bộ lọc cho trình đọc màn hình và các công nghệ hỗ trợ khác. Video YouTube sẽ
minh hoạ tính năng này. Quá trình tích hợp bắt đầu bằng HTML và thuộc tính role="status"
.
<div role="status" class="sr-only" id="applied-filters"></div>
Phần tử này sẽ đọc to những thay đổi đối với nội dung. Chúng tôi có thể cập nhật nội dung bằng bộ đếm CSS khi người dùng tương tác với các hộp đánh dấu. Để làm việc đó, trước tiên, chúng ta cần tạo một bộ đếm có tên trên phần tử mẹ của đầu vào và phần tử trạng thái.
aside {
counter-reset: filters;
}
Theo mặc định, số lượng sẽ là 0
, rất tuyệt, không có gì là :checked
theo mặc định trong thiết kế này.
Tiếp theo, để tăng bộ đếm mới tạo, chúng ta sẽ nhắm đến phần tử con của phần tử <aside>
là :checked
. Khi người dùng thay đổi trạng thái của dữ liệu đầu vào, bộ đếm filters
sẽ cộng lại.
aside :checked {
counter-increment: filters;
}
CSS hiện đã biết số liệu kiểm đếm chung của giao diện người dùng có hộp đánh dấu cũng như phần tử vai trò trạng thái đang trống và đang chờ các giá trị. Vì CSS đang duy trì số kiểm đếm trong bộ nhớ, nên hàm counter()
cho phép truy cập vào giá trị từ nội dung của phần tử giả:
aside #applied-filters::before {
content: counter(filters) " filters ";
}
Giờ đây, HTML cho phần tử vai trò trạng thái sẽ thông báo "2 bộ lọc " cho trình đọc màn hình. Đây là một khởi đầu tốt, nhưng chúng tôi có thể làm tốt hơn, chẳng hạn như chia sẻ số lượng kết quả mà các bộ lọc đã cập nhật. Chúng tôi sẽ thực hiện công việc này từ JavaScript, vì nó nằm ngoài khả năng của bộ đếm.
Sự phấn khích lồng ghép
Thuật toán bộ đếm cho thấy hiệu quả với CSS Nesting-1, vì tôi có thể đưa toàn bộ logic vào một khối. Linh hoạt và tập trung để đọc và cập nhật.
aside {
counter-reset: filters;
& :checked {
counter-increment: filters;
}
& #applied-filters::before {
content: counter(filters) " filters ";
}
}
Bố cục
Phần này mô tả bố cục giữa hai thành phần. Hầu hết các kiểu bố cục đều dành cho thành phần hộp đánh dấu dành cho máy tính.
Biểu mẫu
Để tối ưu hoá mức độ dễ đọc và dễ quét cho người dùng, biểu mẫu được cung cấp chiều rộng tối đa là 30 ký tự, về cơ bản là đặt chiều rộng đường quang cho mỗi nhãn bộ lọc. Biểu mẫu này sử dụng bố cục lưới và thuộc tính gap
để tách biệt các tập trường.
form {
display: grid;
gap: 2ch;
max-inline-size: 30ch;
}
Phần tử <select>
Danh sách nhãn và hộp đánh dấu đều chiếm quá nhiều không gian trên thiết bị di động. Do đó, bố cục sẽ kiểm tra xem thiết bị trỏ chính của người dùng nhằm thay đổi trải nghiệm đối với thao tác chạm.
@media (pointer: coarse) {
select[multiple] {
display: block;
}
}
Giá trị coarse
cho biết người dùng sẽ không thể tương tác với màn hình với độ chính xác cao bằng thiết bị đầu vào chính. Trên thiết bị di động, giá trị con trỏ thường là coarse
, vì tương tác chính là chạm. Trên thiết bị máy tính, giá trị con trỏ thường là fine
vì bạn thường kết nối chuột hoặc thiết bị đầu vào có độ chính xác cao khác.
Các trường
Kiểu và bố cục mặc định của <fieldset>
với <legend>
là duy nhất:
Thông thường, để tách biệt các phần tử con, tôi sẽ sử dụng thuộc tính gap
, nhưng vị trí duy nhất của <legend>
gây khó khăn cho việc tạo nhóm phần tử con có khoảng cách đồng đều. Thay vì gap
, bộ chọn đồng cấp liền kề và margin-block-start
sẽ được sử dụng.
fieldset {
padding: 2ch;
& > div + div {
margin-block-start: 2ch;
}
}
Việc này giúp <legend>
không thể điều chỉnh không gian bằng cách chỉ nhắm đến phần tử con <div>
.
Nhãn bộ lọc và hộp đánh dấu
Là phần tử con trực tiếp của <fieldset>
và trong chiều rộng tối đa 30ch
của biểu mẫu, văn bản nhãn có thể bị bao bọc nếu quá dài. Việc xuống dòng tự động là rất tốt, nhưng không nhất quán giữa văn bản và hộp đánh dấu. Flexbox là lựa chọn lý tưởng dành cho trường hợp này.
fieldset > div {
display: flex;
gap: 2ch;
align-items: baseline;
}
![Ảnh chụp màn hình cho thấy cách dấu kiểm căn chỉnh với dòng văn bản đầu tiên trong trường hợp xuống dòng nhiều dòng.](https://web.dev/static/articles/building/a-multi-select-component/image/screenshot-showing-the-c-b135285ef7d8a.png?authuser=5&hl=vi)
Lưới ảnh động
Ảnh động bố cục được thực hiện bởi Isotope. Một trình bổ trợ hiệu quả và mạnh mẽ để sắp xếp và lọc mang tính tương tác.
JavaScript
Ngoài việc giúp sắp xếp một lưới tương tác, ảnh động gọn gàng, JavaScript còn được dùng để tinh chỉnh một vài cạnh thô.
Chuẩn hoá hoạt động đầu vào của người dùng
Thiết kế này có một dạng với hai cách khác nhau để cung cấp dữ liệu đầu vào và không chuyển đổi tuần tự giống nhau. Tuy nhiên, với một số JavaScript, chúng ta có thể chuẩn hoá dữ liệu.
Tôi đã chọn căn chỉnh cấu trúc dữ liệu của phần tử <select>
với cấu trúc hộp đánh dấu được nhóm. Để thực hiện việc này, trình nghe sự kiện input
sẽ được thêm vào phần tử <select>
, tại thời điểm đó, trình nghe sự kiện selectedOptions
sẽ được liên kết.
document.querySelector('select').addEventListener('input', event => {
// make selectedOptions iterable then reduce a new array object
let selectData = Array.from(event.target.selectedOptions).reduce((data, opt) => {
// parent optgroup label and option value are added to the reduce aggregator
data.push([opt.parentElement.label.toLowerCase(), opt.value])
return data
}, [])
})
Bây giờ, bạn có thể gửi biểu mẫu hoặc trong trường hợp bản minh hoạ này, hãy hướng dẫn Isotope về nội dung cần lọc.
Hoàn tất phần tử vai trò trạng thái
Phần tử này chỉ kiểm đếm và công bố số lượng bộ lọc dựa trên hoạt động tương tác với hộp đánh dấu, nhưng tôi thấy nên chia sẻ thêm số lượng kết quả và đảm bảo các lựa chọn của phần tử <select>
cũng được tính.
Lựa chọn phần tử <select>
được phản ánh trong counter()
Trong phần chuẩn hoá dữ liệu, một trình nghe đã được tạo sẵn trên dữ liệu đầu vào. Ở cuối hàm này, số bộ lọc đã chọn và số lượng kết quả của các bộ lọc đó đã biết. Các giá trị có thể được chuyển đến phần tử vai trò trạng thái như thế này.
let statusRoleElement = document.querySelector('#applied-filters')
statusRoleElement.style.counterSet = selectData.length
Kết quả được phản ánh trong phần tử role="status"
:checked
cung cấp phương thức tích hợp sẵn để chuyển số lượng bộ lọc đã chọn vào phần tử vai trò trạng thái, nhưng không hiển thị số lượng kết quả đã lọc.
JavaScript có thể theo dõi các hoạt động tương tác với các hộp đánh dấu và sau khi lọc lưới, hãy thêm textContent
như phần tử <select>
đã làm.
document
.querySelector('aside form')
.addEventListener('input', e => {
// isotope demo code
let filterResults = IsotopeGrid.getFilteredItemElements().length
document.querySelector('#applied-filters').textContent = `giving ${filterResults} results`
})
Tổng cộng công việc này hoàn thành thông báo "2 bộ lọc cho 25 kết quả".
Giờ đây, trải nghiệm công nghệ hỗ trợ tuyệt vời của chúng tôi sẽ được cung cấp cho tất cả người dùng, bất kể họ có tương tác với ứng dụng đó hay không.
Kết luận
Giờ bạn đã biết tôi làm được như thế nào, bạn sẽ làm thế nào 🙂
Hãy đa dạng hoá phương pháp tiếp cận của chúng ta và tìm hiểu tất cả các cách xây dựng trên web. Hãy tạo một bản minh hoạ, đường liên kết tweet me và tôi sẽ thêm bản phối lại đó vào phần bản phối lại của cộng đồng bên dưới!
Bản phối lại của cộng đồng
Chưa có nội dung nào để xem ở đây!