Tổng quan cơ bản về cách tạo một thanh điều hướng bên trượt ra thích ứng
Trong bài đăng này, tôi muốn chia sẻ với bạn cách tôi tạo bản minh hoạ cho một thành phần Sidenav (Trình đơn điều hướng bên) cho web. Thành phần này có khả năng thích ứng, có trạng thái, hỗ trợ thao tác điều hướng bằng bàn phím, hoạt động có và không có JavaScript, đồng thời hoạt động trên nhiều trình duyệt. Hãy dùng thử bản minh hoạ.
Nếu bạn thích xem video, hãy xem phiên bản video của bài đăng này trên YouTube:
Tổng quan
Rất khó để xây dựng một hệ thống điều hướng thích ứng. Một số người dùng sẽ sử dụng bàn phím, một số người dùng sẽ có máy tính để bàn mạnh mẽ và một số người dùng sẽ truy cập từ thiết bị di động nhỏ. Mọi người truy cập đều có thể mở và đóng trình đơn.
Chiến thuật web
Trong quá trình khám phá thành phần này, tôi rất vui khi có thể kết hợp một số tính năng quan trọng của nền tảng web:
- CSS
:target
- Lưới CSS
- Chuyển đổi CSS
- Truy vấn nội dung đa phương tiện CSS cho khung nhìn và lựa chọn ưu tiên của người dùng
- JS cho
focus
Các tính năng nâng cao trải nghiệm người dùng
Giải pháp của tôi có một thanh bên và chỉ bật/tắt khi ở khung nhìn "thiết bị di động" có kích thước 540px
trở xuống.
540px
sẽ là điểm ngắt để chuyển đổi giữa bố cục tương tác trên thiết bị di động và bố cục tĩnh trên máy tính.
Lớp giả lập :target
CSS
Một đường liên kết <a>
đặt hàm băm URL thành #sidenav-open
và đường liên kết còn lại đặt hàm băm thành trống (''
). Cuối cùng, một phần tử có id
để khớp với hàm băm:
<a href="#sidenav-open" id="sidenav-button" title="Open Menu" aria-label="Open Menu">
<a href="#" id="sidenav-close" title="Close Menu" aria-label="Close Menu"></a>
<aside id="sidenav-open">
…
</aside>
Khi nhấp vào từng đường liên kết này, trạng thái băm của URL trang sẽ thay đổi, sau đó với một lớp giả, tôi hiển thị và ẩn thanh điều hướng bên:
@media (max-width: 540px) {
#sidenav-open {
visibility: hidden;
}
#sidenav-open:target {
visibility: visible;
}
}
Lưới CSS
Trước đây, tôi chỉ sử dụng các thành phần và bố cục điều hướng bên ở vị trí tuyệt đối hoặc cố định. Tuy nhiên, với cú pháp grid-area
, Grid cho phép chúng ta gán nhiều phần tử cho cùng một hàng hoặc cột.
Ngăn xếp
Phần tử bố cục chính #sidenav-container
là một lưới tạo ra 1 hàng và 2 cột, trong đó mỗi hàng và cột có tên là stack
. Khi không gian bị hạn chế, CSS sẽ gán tất cả phần tử con của phần tử <main>
cho cùng một tên lưới, đặt tất cả phần tử vào cùng một không gian, tạo một ngăn xếp.
#sidenav-container {
display: grid;
grid: [stack] 1fr / min-content [stack] 1fr;
min-height: 100vh;
}
@media (max-width: 540px) {
#sidenav-container > * {
grid-area: stack;
}
}
Phông nền trình đơn
<aside>
là phần tử ảnh động chứa thanh điều hướng bên. Thành phần này có 2 thành phần con: vùng chứa điều hướng <nav>
tên là [nav]
và phông nền <a>
tên là [escape]
, dùng để đóng trình đơn.
#sidenav-open {
display: grid;
grid-template-columns: [nav] 2fr [escape] 1fr;
}
Điều chỉnh 2fr
và 1fr
để tìm tỷ lệ bạn muốn cho lớp phủ trình đơn và nút đóng không gian âm của lớp phủ đó.
Hiệu ứng chuyển đổi và biến đổi 3D CSS
Bố cục của chúng ta hiện được xếp chồng ở kích thước khung nhìn trên thiết bị di động. Cho đến khi tôi thêm một số kiểu mới, lớp phủ này sẽ phủ lên bài viết theo mặc định. Sau đây là một số trải nghiệm người dùng mà tôi hướng đến trong phần tiếp theo:
- Tạo ảnh động mở và đóng
- Chỉ tạo ảnh động bằng chuyển động nếu người dùng đồng ý
- Tạo ảnh động cho
visibility
để tiêu điểm bàn phím không đi vào phần tử ngoài màn hình
Khi bắt đầu triển khai ảnh động chuyển động, tôi muốn bắt đầu bằng khả năng hỗ trợ tiếp cận.
Chuyển động hỗ trợ tiếp cận
Không phải ai cũng muốn trải nghiệm thao tác trượt ra. Trong giải pháp của chúng tôi, tuỳ chọn ưu tiên này được áp dụng bằng cách điều chỉnh biến CSS --duration
bên trong truy vấn nội dung nghe nhìn. Giá trị truy vấn nội dung nghe nhìn này thể hiện lựa chọn ưu tiên của người dùng về hệ điều hành đối với chuyển động (nếu có).
#sidenav-open {
--duration: .6s;
}
@media (prefers-reduced-motion: reduce) {
#sidenav-open {
--duration: 1ms;
}
}
Bây giờ, khi thanh điều hướng bên trượt mở và đóng, nếu người dùng muốn giảm chuyển động, tôi sẽ di chuyển phần tử vào chế độ xem ngay lập tức, duy trì trạng thái mà không có chuyển động.
Chuyển đổi, biến đổi, dịch
Sidenav out (mặc định)
Để đặt trạng thái mặc định của thanh điều hướng bên trên thiết bị di động thành trạng thái ngoài màn hình, tôi sẽ định vị phần tử bằng transform: translateX(-110vw)
.
Lưu ý: Tôi đã thêm một 10vw
khác vào mã ngoài màn hình thông thường của -100vw
, để đảm bảo box-shadow
của thanh điều hướng bên không xem trộm vào khung nhìn chính khi thanh điều hướng bên bị ẩn.
@media (max-width: 540px) {
#sidenav-open {
visibility: hidden;
transform: translateX(-110vw);
will-change: transform;
transition:
transform var(--duration) var(--easeOutExpo),
visibility 0s linear var(--duration);
}
}
Thanh điều hướng bên trong
Khi phần tử #sidenav
khớp với :target
, hãy đặt vị trí translateX()
thành homebase 0
,
và xem CSS trượt phần tử từ vị trí ngoài -110vw
, sang vị trí "trong"
0
trên var(--duration)
khi hàm băm URL thay đổi.
@media (max-width: 540px) {
#sidenav-open:target {
visibility: visible;
transform: translateX(0);
transition:
transform var(--duration) var(--easeOutExpo);
}
}
Chế độ hiển thị chuyển đổi
Mục tiêu hiện tại là ẩn trình đơn khỏi trình đọc màn hình khi trình đơn đó ẩn, để hệ thống không đặt tiêu điểm vào trình đơn ngoài màn hình. Tôi thực hiện việc này bằng cách đặt chuyển đổi chế độ hiển thị khi :target
thay đổi.
- Khi đi vào, đừng chuyển đổi chế độ hiển thị; hãy hiển thị ngay để tôi có thể thấy phần tử trượt vào và chấp nhận tiêu điểm.
- Khi thoát, hãy chuyển đổi chế độ hiển thị nhưng trì hoãn chế độ này để chuyển sang
hidden
khi kết thúc quá trình chuyển đổi.
Cải tiến trải nghiệm người dùng về khả năng hỗ trợ tiếp cận
Liên kết
Giải pháp này dựa vào việc thay đổi URL để quản lý trạng thái.
Đương nhiên, bạn nên sử dụng phần tử <a>
ở đây và phần tử này sẽ nhận được một số tính năng hỗ trợ tiếp cận miễn phí. Hãy trang trí các thành phần tương tác của chúng ta bằng các nhãn thể hiện rõ ý định.
<a href="#" id="sidenav-close" title="Close Menu" aria-label="Close Menu"></a>
<a href="#sidenav-open" id="sidenav-button" class="hamburger" title="Open Menu" aria-label="Open Menu">
<svg>...</svg>
</a>
Giờ đây, các nút tương tác chính của chúng ta đã nêu rõ ý định của chúng đối với cả chuột và bàn phím.
:is(:hover, :focus)
Bộ chọn giả lập chức năng CSS tiện dụng này cho phép chúng ta nhanh chóng bao gồm các kiểu di chuột bằng cách chia sẻ các kiểu đó với tiêu điểm.
.hamburger:is(:hover, :focus) svg > line {
stroke: hsl(var(--brandHSL));
}
Thêm JavaScript
Nhấn escape
để đóng
Phím Escape
trên bàn phím sẽ đóng trình đơn phải không? Hãy kết nối các phần đó.
const sidenav = document.querySelector('#sidenav-open');
sidenav.addEventListener('keyup', event => {
if (event.code === 'Escape') document.location.hash = '';
});
Nhật ký duyệt web
Để ngăn hoạt động tương tác mở và đóng xếp chồng nhiều mục vào nhật ký duyệt web, hãy thêm JavaScript nội tuyến sau vào nút đóng:
<a href="#" id="sidenav-close" title="Close Menu" aria-label="Close Menu" onchange="history.go(-1)"></a>
Thao tác này sẽ xoá mục nhập nhật ký URL khi đóng, khiến trình đơn giống như chưa bao giờ được mở.
Trải nghiệm người dùng tập trung
Đoạn mã tiếp theo giúp chúng ta đặt tiêu điểm vào các nút mở và đóng sau khi chúng mở hoặc đóng. Tôi muốn việc bật/tắt trở nên dễ dàng.
sidenav.addEventListener('transitionend', e => {
const isOpen = document.location.hash === '#sidenav-open';
isOpen
? document.querySelector('#sidenav-close').focus()
: document.querySelector('#sidenav-button').focus();
})
Khi thanh điều hướng bên mở ra, hãy đặt tiêu điểm vào nút đóng. Khi thanh điều hướng bên đóng, hãy đặt tiêu điểm vào nút mở. Tôi thực hiện việc này bằng cách gọi focus()
trên phần tử trong JavaScript.
Kết luận
Giờ thì bạn đã biết cách tôi làm, còn bạn thì sao?! Điều này tạo ra một số cấu trúc thành phần thú vị! Ai sẽ tạo phiên bản 1 có khe? 🙂
Hãy đa dạng hoá các phương pháp 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 Glitch, rồi twitt cho tôi phiên bản của bạn. Tôi sẽ thêm phiên bản đó 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
- @_developit với các phần tử tuỳ chỉnh: mã và bản minh hoạ
- @mayeedwin1 với HTML/CSS/JS: mã và bản minh hoạ
- @a_nurella với bản phối lại Glitch: mã và bản minh hoạ
- @EvroMalarkey với HTML/CSS/JS: mã và bản minh hoạ