Tổng quan cơ bản về cách tạo một sidenav 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 mẫu một thành phần Sidenav cho web có khả năng thích ứng, có trạng thái, hỗ trợ thao tác 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. Dùng thử bản minh hoạ.
Nếu bạn thích xem video, thì đây là phiên bản video của bài đăng này trên YouTube:
Tổng quan
Việc xây dựng một hệ thống điều hướng thích ứng là một việc khó khăn. 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ừ một thiết bị di động nhỏ. Mọi người truy cập đều có thể mở và đóng trình đơn.
Web Tactics
Trong quá trình khám phá thành phần này, tôi đã có cơ hội 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
- Biến đổi CSS
- Truy vấn phương tiện CSS cho khung hiển thị và lựa chọn ưu tiên của người dùng
- JS cho
focus
Các điểm cải tiến về 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 hiển thị "thiết bị di động" có kích thước 540px
trở xuống.
540px
sẽ là điểm ngắt của chúng ta để 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ả :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 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 đó tôi sẽ hiện và ẩn sidenav bằng một lớp giả:
@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 bố cục và thành phần sidenav có vị trí tuyệt đối hoặc cố định. Tuy nhiên, lưới có cú pháp grid-area
cho phép chúng ta chỉ định 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, mỗi cột có tên là stack
. Khi không gian bị hạn chế, CSS sẽ chỉ định tất cả các phần tử con của phần tử <main>
cho cùng một tên lưới, đặt tất cả các phần tử vào cùng một không gian, tạo ra 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 của trình đơn
<aside>
là phần tử tạo ảnh động chứa chế độ đ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>
có tên là [nav]
và một phông nền <a>
có 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 khoảng trống của lớp phủ đó.
Hiệu ứng chuyển đổi và chuyển tiếp 3D trong CSS
Giờ đây, bố cục của chúng ta được xếp chồng lên nhau ở kích thước khung hiển thị trên thiết bị di động. Cho đến khi tôi thêm một số kiểu mới, theo mặc định, phần này sẽ phủ lên bài viết của chúng ta. Sau đây là một số trải nghiệm người dùng mà tôi muốn hướng đến trong phần tiếp theo:
- Tạo hiệu ứng mở và đóng
- Chỉ tạo ảnh động khi người dùng đồng ý
- Tạo hiệu ứng độ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 cách ưu tiên 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 có trải nghiệm chuyển động trượt ra. Trong giải pháp của chúng tôi, lựa chọn ưu tiên này được áp dụng bằng cách điều chỉnh một biến CSS --duration
bên trong một 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ề chuyển động trong hệ điều hành (nếu có).
#sidenav-open {
--duration: .6s;
}
@media (prefers-reduced-motion: reduce) {
#sidenav-open {
--duration: 1ms;
}
}
Giờ đây, khi sidenav của chúng tôi đang trượt mở và đóng, nếu người dùng muốn giảm chuyển động, tôi sẽ di chuyển ngay phần tử vào chế độ xem, duy trì trạng thái mà không có chuyển động.
Chuyển đổi, biến đổi, dịch
Thanh điều hướng bên (mặc định)
Để đặt trạng thái mặc định của sidenav trên thiết bị di động thành trạng thái ngoài màn hình, tôi đặt vị trí của 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 sidenav không xuất hiện trong khung hiển thị chính khi 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 0
homebase và xem CSS trượt phần tử từ vị trí -110vw
ra ngoài đến vị trí "vào trong" 0
trong 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ị hiệu ứng 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 không xuất hiện, để hệ thống không đặt tiêu điểm vào một trình đơn ngoài màn hình. Tôi thực hiện việc này bằng cách đặt hiệu ứng 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 đi ra ngoài, hãy chuyển đổi chế độ hiển thị nhưng trì hoãn để chế độ này chuyển sang
hidden
ở cuối quá trình chuyển đổi.
Cải thiệ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ẽ có sẵn một số tính năng hỗ trợ tiếp cận hữu ích. Hãy trang trí các phần tử tương tác bằng những 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 tôi 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ả chức năng CSS tiện dụng này giúp chúng ta nhanh chóng bao gồm cả các kiểu di chuột bằng cách chia sẻ chúng 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, đúng không? Hãy kết nối các thiết bị đó.
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 chặn tương tác mở và đóng xếp chồng nhiều mục vào nhật ký trình duyệt, 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 URL trong nhật ký khi đóng, khiến cho trình đơn như thể chưa từng được mở.
Trải nghiệm người dùng khi tập trung
Đoạn mã tiếp theo giúp chúng ta tập trung vào các nút mở và đóng sau khi chúng mở hoặc đóng. Tôi muốn việc chuyển đổi 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 sidenav mở ra, hãy tập trung vào nút đóng. Khi sidenav đóng, hãy tập trung 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ờ bạn đã biết cách tôi làm, vậy bạn sẽ làm như thế nào?! Điều này tạo nên một cấu trúc thành phần thú vị! Ai sẽ tạo phiên bản đầu tiên có các vị trí? 🙂
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. Tạo một Glitch, gửi cho tôi một tweet về phiên bản của bạn và 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: bản minh hoạ và mã
- @mayeedwin1 với HTML/CSS/JS: bản minh hoạ và mã
- @a_nurella với một Bản phối lại bị lỗi: bản minh hoạ và mã
- @EvroMalarkey với HTML/CSS/JS: bản minh hoạ và mã