Xây dựng thành phần chọn nhiều mục

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ạ.

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.

Ảnh chụp màn hình so sánh cho thấy màn hình sáng và tối trên máy tính với một thanh bên có các hộp đánh dấu, so với iOS và Android dành cho thiết bị di động có phần tử chọn nhiều mục.

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 xem trước ảnh chụp màn hình của phần tử chọn nhiều mục trong Chrome trên Android, iPhone và iPad. iPad và iPhone đều bật chế độ chọn nhiều mục, và mỗi thiết bị đều có được một trải nghiệm riêng biệt được tối ưu hoá cho kích thước màn hình.

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><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>

Ảnh chụp màn hình có lớp phủ thông tin cho các phần tử chú giải và
  tập hợp trường, cho thấy màu sắc và tên phần tử.

Thành phần <select multiple>

Một tính năng ít khi được sử dụng của phần tử <select>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><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>

Ảnh chụp màn hình kết xuất một phần tử chọn nhiều mục trên màn hình.

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>: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.

Ảnh chụp màn hình trình đọc màn hình MacOS cho biết số lượng bộ lọc đang hoạt động.

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:

Ảnh chụp màn hình các kiểu mặc định cho một nhóm trường và chú giải.

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ề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 chụp màn hình cho thấy khoảng cách lề giữa các dữ liệu đầu vào nhưng không phải là chú giải.

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.
Chơi thêm trong Codepen này

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.

Ảnh chụp màn hình bảng điều khiển JavaScript của Công cụ cho nhà phát triển cho thấy mục tiêu và kết quả dữ liệu đã chuẩn hoá.

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ả".

Ảnh chụp màn hình trình đọc màn hình MacOS đang thông báo 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!