Tổng quan cơ bản về cách tạo thanh điều hướng bên ngoài dạng trang trình bày 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 nguyên mẫu thành phần Sidenav cho web. Thành phần này phản hồi, có trạng thái, hỗ trợ điều hướng bằng bàn phím, hoạt động với và không có JavaScript, đồng thời hoạt động trên nhiều trình duyệt. Xem bản minh hoạ.
Nếu bạn thích xem video hơn, sau đây là phiên bản của bài đăng này trên YouTube:
Tổng quan
Thật khó khăn khi xây dựng một hệ thống điều hướng thích ứng. Một số người dùng sử dụng bàn phím, một số người sẽ sử dụng máy tính để bàn mạnh mẽ và một số người sẽ truy cập từ một thiết bị di động nhỏ. Tất cả những 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 kết hợp một số tính năng quan trọng của nền tảng web:
- Dịch vụ so sánh giá (CSS)
:target
- Lưới CSS
- transforms CSS
- Truy vấn 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 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 nhìn "thiết bị di động" từ 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 cho máy tính để bàn.
Lớp giả :target
của CSS
Một đường liên kết <a>
sẽ đặt hàm băm URL thành #sidenav-open
và thành phần 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>
Việc nhấp vào từng đường liên kết này sẽ thay đổi trạng thái băm của URL trang, sau đó với một lớp giả, tôi sẽ 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 sidenav theo vị trí tuyệt đối hoặc cố định. Tuy nhiên, với cú pháp grid-area
, lưới 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 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ẽ 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ả 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 trình đơn
<aside>
là phần tử ảnh động chứa thanh điều hướng bên. Khung này có 2 phần tử con: vùng chứa điều hướng <nav>
tên là [nav]
và một 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ệ mà bạn muốn cho lớp phủ trình đơn và nút đóng không gian âm của lớp phủ đó.
Chuyển đổi và chuyển đổi CSS 3D
Bố cục của chúng ta hiện được xếp chồng theo 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, nội dung đó sẽ chồng lên bài viết của chúng tôi theo mặc định. Dưới đây là một số trải nghiệm người dùng mà tôi sẽ nhắm đến trong phần tiếp theo này:
- Tạo ảnh động cho thao tác mở và đóng
- Chỉ tạo ảnh động bằng chuyển động nếu người dùng đồng ý với điều đó
- Tạo ảnh động cho
visibility
để tiêu điểm bằng bàn phím không nhập vào phần tử ngoài màn hình
Khi bắt đầu triển khai hoạt ảnh chuyển động, tôi muốn ưu tiên khả năng hỗ trợ tiếp cận.
Xe có hỗ trợ tiếp cận
Không phải ai cũng muốn 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 biến CSS --duration
bên trong truy vấn nội dung đa phương tiệ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;
}
}
Khi thanh điều hướng bên ở trạng thái trượt mở và đóng, nếu người dùng muốn giảm chuyển động, tôi sẽ ngay lập tức di chuyển phần tử vào khung hiển thị, duy trì trạng thái không chuyển động.
Chuyển đổi, biến đổi, dịch
Điều hướng bên ngoài (mặc định)
Để đặt trạng thái mặc định của đ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 điều hướng bên không xuất hiện trong khung nhìn 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);
}
}
Điều hướng bên trong
Khi phần tử #sidenav
khớp với giá trị :target
, hãy đặt vị trí translateX()
thành homebase 0
và xem khi CSS trượt phần tử từ vị trí ra ngoài -110vw
sang vị trí "ở" 0
trên var(--duration)
khi hàm băm URL được 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 đó được mở ra để các hệ thống không đưa 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 thiết lập 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 chuyển ra ngoài, chế độ hiển thị hiệu ứng chuyển đổi sẽ hiển thị nhưng trì hoãn để chuyển sang
hidden
ở cuối quá trình chuyển đổi.
Các tính năng nâng cao cho trải nghiệm người dùng hỗ trợ tiếp cận
Liên kết
Giải pháp này dựa trên việc thay đổi URL để quản lý trạng thái.
Đương nhiên là 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 hữu ích miễn phí. Hãy tô điểm cho 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 đã 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 cho phép chúng tôi nhanh chóng bao gồm 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 phải không? Hãy kết nối vào đó.
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 nhập vào nhật ký duyệt web, hãy thêm nội tuyến JavaScript sau đây 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ật ký URL khi đóng, khiến trình đơn như thể chưa từng mở trình đơn.
Tập trung vào trải nghiệm người dùng
Đ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 các nút này mở hoặc đóng. Tôi muốn bật/tắt một cách 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 bảng điều hướng bên mở ra, hãy lấy tiêu điểm vào nút đóng. Khi điều hướng bên đóng, hãy lấy 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ờ bạn đã biết cách tôi thực hiện điều đó, bạn sẽ làm thế nào?! Điều này tạo ra một số cấu trúc thành phần thú vị! Ai sẽ thiết kế phiên bản đầu tiên có khung giờ nhận đặt quảng cáo? 🙂
Hãy đa dạng hoá các phương pháp tiếp cận của chúng tôi và tìm hiểu tất cả các cách xây dựng ứng dụng trên web. Tạo một Bản phối lại, tweet cho tôi phiên bản của bạn, và tôi sẽ thêm nó vào phần Bản phối lại trong 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 cùng bản phối lại lộn xộn: bản minh hoạ và mã
- @EvroMalarkey với HTML/CSS/JS: bản minh hoạ và mã