Tạo thanh điều hướng “Moving Highlight” bằng JavaScript và CSS

theanh

Administrator
Nhân viên
Gần đây tôi tình cờ thấy một hướng dẫn jQuery cũ trình bày về thanh điều hướng "di chuyển điểm nổi bật" và quyết định khái niệm này cần được nâng cấp hiện đại. Với mẫu này, đường viền xung quanh mục điều hướng đang hoạt động sẽ hoạt hình trực tiếp từ phần tử này sang phần tử khác khi người dùng nhấp vào các mục menu. Vào năm 2025, chúng ta có nhiều công cụ tốt hơn để thao tác DOM thông qua JavaScript thuần túy. Các tính năng mới như View Transition API giúp cải tiến dần dần dễ dàng hơn và xử lý nhiều chi tiết nhỏ của hoạt ảnh.

1-moving-highlight-navigation-bar.gif


Trong hướng dẫn này, tôi sẽ trình bày hai phương pháp tạo thanh điều hướng "điểm nổi bật di chuyển" bằng JavaScript và CSS thuần túy. Ví dụ đầu tiên sử dụng phương thức getBoundingClientRect để tạo hiệu ứng động rõ ràng cho đường viền giữa các mục trên thanh điều hướng khi chúng được nhấp vào. Ví dụ thứ hai đạt được chức năng tương tự bằng cách sử dụng API Chuyển đổi chế độ xem mới.

Đánh dấu ban đầu​

Giả sử chúng ta có một ứng dụng một trang trong đó nội dung thay đổi mà không cần tải lại trang. HTML và CSS ban đầu là thanh điều hướng tiêu chuẩn của bạn với phần tử div bổ sung chứa phần tử id #highlight. Chúng tôi cung cấp cho mục điều hướng đầu tiên một lớp là .active.

Xem Bút [Di chuyển Thanh điều hướng Đánh dấu Bắt đầu Đánh dấu [phân nhánh]](https://codepen.io/smashingmag/pen/EajQyBW) của Blake Lundquist.
Xem Bút Di chuyển thanh điều hướng nổi bật bắt đầu đánh dấu [phân nhánh] của Blake Lundquist.
Đối với phiên bản này, chúng tôi sẽ định vị phần tử #highlight xung quanh phần tử có .active class để tạo đường viền. Chúng ta có thể sử dụng absolute định vị và hoạt hình hóa phần tử trên thanh điều hướng để tạo hiệu ứng mong muốn. Ban đầu, chúng tôi sẽ ẩn nó khỏi màn hình bằng cách thêm left: -200px và bao gồm transition kiểu cho tất cả các thuộc tính để bất kỳ thay đổi nào về vị trí và kích thước của phần tử sẽ xảy ra dần dần.
Mã:
#highlight { z-index: 0; position: absolute; height: 100%; width: 100px; left: -200px; border: 2px solid green; box-sizing: border-box; transition: all 0.2s ease;
}

Thêm trình xử lý sự kiện mẫu cho tương tác nhấp chuột​

Chúng tôi muốn phần tử nổi bật hoạt hình khi người dùng thay đổi mục điều hướng .active. Hãy thêm trình xử lý sự kiện click vào phần tử nav, sau đó lọc các sự kiện chỉ gây ra bởi các phần tử khớp với bộ chọn mong muốn của chúng ta. Trong trường hợp này, chúng ta chỉ muốn thay đổi mục điều hướng .active nếu người dùng nhấp vào liên kết chưa có lớp .active.

Ban đầu, chúng ta có thể gọi console.log để đảm bảo trình xử lý chỉ kích hoạt khi mong đợi:
Mã:
const navbar = document.querySelector('nav');
navbar.addEventListener('click', function (event) { // return if the clicked element doesn't have the correct selector if (!event.target.matches('nav a:not(active)')) { return; } console.log('click');
});
Mở bảng điều khiển trình duyệt của bạn và thử nhấp vào các mục khác nhau trên thanh điều hướng. Bạn chỉ nên thấy "click" được ghi lại khi bạn chọn một mục mới trên thanh điều hướng.

Bây giờ chúng ta đã biết trình xử lý sự kiện của mình đang hoạt động trên các phần tử chính xác, hãy thêm mã để di chuyển lớp .active đến mục điều hướng đã được nhấp vào. Chúng ta có thể sử dụng đối tượng được truyền vào trình xử lý sự kiện để tìm phần tử đã khởi tạo sự kiện và gán cho phần tử đó một lớp .active sau khi xóa nó khỏi mục đang hoạt động trước đó.
Mã:
const navbar = document.querySelector('nav');
navbar.addEventListener('click', function (event) { // return if the clicked element doesn't have the correct selector if (!event.target.matches('nav a:not(active)')) { return; }
- console.log('click');
+ document.querySelector('nav a.active').classList.remove('active');
+ event.target.classList.add('active');
});
Phần tử #highlight của chúng ta cần di chuyển qua thanh điều hướng và định vị xung quanh mục đang hoạt động. Hãy viết một hàm để tính toán vị trí và chiều rộng mới. Vì bộ chọn #highlight đã áp dụng các kiểu transition, nên nó sẽ di chuyển dần dần khi vị trí của nó thay đổi.

Sử dụng getBoundingClientRect, chúng ta có thể lấy thông tin về vị trí và kích thước của một phần tử. Chúng ta tính toán chiều rộng của mục điều hướng đang hoạt động và độ lệch của nó so với ranh giới bên trái của phần tử cha. Sau đó, chúng ta gán các kiểu cho phần tử nổi bật sao cho kích thước và vị trí của nó khớp với nhau.
Mã:
// handler for moving the highlight
const moveHighlight = () => { const activeNavItem = document.querySelector('a.active'); const highlighterElement = document.querySelector('#highlight'); const width = activeNavItem.offsetWidth; const itemPos = activeNavItem.getBoundingClientRect(); const navbarPos = navbar.getBoundingClientRect() const relativePosX = itemPos.left - navbarPos.left; const styles = { left: `${relativePosX}px`, width: `${width}px`, }; Object.assign(highlighterElement.style, styles);
}
Hãy gọi hàm mới của chúng ta khi sự kiện nhấp chuột kích hoạt:
Mã:
navbar.addEventListener('click', function (event) { // return if the clicked element doesn't have the correct selector if (!event.target.matches('nav a:not(active)')) { return; } document.querySelector('nav a.active').classList.remove('active'); event.target.classList.add('active');
+ moveHighlight();
});
Cuối cùng, chúng ta cũng hãy gọi hàm ngay lập tức để đường viền di chuyển đằng sau mục hoạt động ban đầu của chúng ta khi trang tải lần đầu:
Mã:
// handler for moving the highlight
const moveHighlight = () => { // ...
}
// display the highlight when the page loads
moveHighlight();
Bây giờ, đường viền di chuyển trên thanh điều hướng khi một mục mới được chọn. Hãy thử nhấp vào các liên kết điều hướng khác nhau để làm hoạt hình thanh điều hướng.

Xem Bút [Di chuyển Thanh điều hướng nổi bật [phân nhánh]](https://codepen.io/smashingmag/pen/WbvMxqV) của Blake Lundquist.
Xem Bút Di chuyển Thanh điều hướng nổi bật [phân nhánh] của Blake Lundquist.
Chỉ mất vài dòng JavaScript thuần túy và có thể dễ dàng được mở rộng để tính đến các tương tác khác, như các sự kiện mouseover. Trong phần tiếp theo, chúng ta sẽ khám phá cách tái cấu trúc tính năng này bằng API Chuyển đổi dạng xem.

Sử dụng API Chuyển đổi dạng xem​

API Chuyển đổi dạng xem cung cấp chức năng tạo chuyển đổi động giữa các chế độ xem trang web. Ở bên trong, API tạo ảnh chụp nhanh của chế độ xem "trước" và "sau" rồi xử lý chuyển đổi giữa chúng. Chuyển đổi chế độ xem hữu ích khi tạo hoạt ảnh giữa các tài liệu, cung cấp trải nghiệm người dùng giống như ứng dụng gốc trong các khung như Astro. Tuy nhiên, API cũng cung cấp trình xử lý dành cho các ứng dụng theo kiểu SPA. Chúng tôi sẽ sử dụng API này để giảm JavaScript cần thiết trong quá trình triển khai và dễ dàng tạo chức năng dự phòng hơn.

Đối với phương pháp này, chúng tôi không còn cần phần tử #highlight riêng nữa. Thay vào đó, chúng ta có thể định kiểu cho mục điều hướng .active trực tiếp bằng cách sử dụng bộ chọn giả và để API Chuyển đổi chế độ xem xử lý hoạt ảnh giữa các trạng thái giao diện người dùng trước và sau khi nhấp vào mục điều hướng mới.

Chúng ta sẽ bắt đầu bằng cách loại bỏ phần tử #highlight và CSS liên quan của nó, sau đó thay thế bằng các kiểu cho nav a::after bộ chọn giả:
Mã:
 -  [URL=#]Home[/URL] [URL=#services]Services[/URL] [URL=#about]About[/URL] [URL=#contact]Contact[/URL]
Mã:
- #highlight {
- z-index: 0;
- position: absolute;
- height: 100%;
- width: 0;
- left: 0;
- box-sizing: border-box;
- transition: all 0.2s ease;
- }
+ nav a::after {
+ content: " ";
+ position: absolute;
+ left: 0;
+ top: 0;
+ width: 100%;
+ height: 100%;
+ border: none;
+ box-sizing: border-box;
+ }
Đối với lớp .active, chúng tôi bao gồm Thuộc tính view-transition-name, do đó mở khóa sự kỳ diệu của View Transition API. Khi chúng ta kích hoạt chuyển đổi chế độ xem và thay đổi vị trí của mục điều hướng .active trong DOM, ảnh chụp nhanh "trước" và "sau" sẽ được chụp và trình duyệt sẽ tạo hiệu ứng hoạt hình cho đường viền trên thanh. Chúng ta sẽ đặt tên cho quá trình chuyển đổi chế độ xem của mình là highlight, nhưng về mặt lý thuyết, chúng ta có thể đặt cho nó bất kỳ tên nào.
Mã:
nav a.active::after { border: 2px solid green; view-transition-name: highlight;
}
Khi chúng ta có một bộ chọn chứa view-transition-name thuộc tính, bước duy nhất còn lại là kích hoạt quá trình chuyển đổi bằng phương thức startViewTransition và truyền vào một hàm gọi lại.
Mã:
const navbar = document.querySelector('nav');
// Change the active nav item on click
navbar.addEventListener('click', async function (event) { if (!event.target.matches('nav a:not(.active)')) { return; } document.startViewTransition(() => { document.querySelector('nav a.active').classList.remove('active'); event.target.classList.add('active'); });
});
Phía trên là phiên bản đã sửa đổi của trình xử lý click. Thay vì tự mình thực hiện tất cả các phép tính về kích thước và vị trí của đường viền di chuyển, API Chuyển đổi dạng xem sẽ xử lý tất cả cho chúng ta. Chúng ta chỉ cần gọi document.startViewTransition và truyền vào một hàm gọi lại để thay đổi mục có lớp .active!

Điều chỉnh chuyển đổi chế độ xem​

Tại thời điểm này, khi nhấp vào liên kết điều hướng, bạn sẽ nhận thấy rằng quá trình chuyển đổi hoạt động, nhưng một số vấn đề kỳ lạ về kích thước vẫn có thể nhìn thấy.


(Xem trước lớn)
Sự không nhất quán về kích thước này là do tỷ lệ khung hình thay đổi trong quá trình chuyển đổi chế độ xem. Chúng tôi sẽ không đi sâu vào chi tiết ở đây, nhưng Jake Archibald có lời giải thích chi tiết mà bạn có thể đọc để biết thêm thông tin. Tóm lại, để đảm bảo chiều cao của đường viền luôn đồng nhất trong suốt quá trình chuyển đổi, chúng ta cần khai báo height rõ ràng cho ::view-transition-old::view-transition-new bộ chọn giả đại diện cho ảnh chụp nhanh tĩnh của chế độ xem cũ và mới tương ứng.
Mã:
::view-transition-old(highlight) { height: 100%;
}
::view-transition-new(highlight) { height: 100%;
}
Chúng ta hãy thực hiện một số cải tiến cuối cùng để sắp xếp lại mã của mình bằng cách di chuyển lệnh gọi lại sang một hàm riêng biệt và thêm một phương án dự phòng khi không hỗ trợ chuyển đổi chế độ xem:
Mã:
const navbar = document.querySelector('nav');
// change the item that has the .active class applied
const setActiveElement = (elem) => { document.querySelector('nav a.active').classList.remove('active'); elem.classList.add('active');
}
// Start view transition and pass in a callback on click
navbar.addEventListener('click', async function (event) { if (!event.target.matches('nav a:not(.active)')) { return; } // Fallback for browsers that don't support View Transitions: if (!document.startViewTransition) { setActiveElement(event.target); return; } document.startViewTransition(() => setActiveElement(event.target));
});
Đây là thanh điều hướng chuyển đổi chế độ xem của chúng tôi! Hãy quan sát chuyển đổi mượt mà khi bạn nhấp vào các liên kết khác nhau.

Xem Bút [Di chuyển Thanh điều hướng nổi bật với Chuyển đổi chế độ xem [phân nhánh]](https://codepen.io/smashingmag/pen/ogXELKE) của Blake Lundquist.
Xem Bút Di chuyển Thanh điều hướng nổi bật với Chuyển đổi chế độ xem [phân nhánh] của Blake Lundquist.

Kết luận​

Hoạt ảnh và chuyển đổi giữa các trạng thái giao diện người dùng của trang web từng yêu cầu nhiều kilobyte thư viện bên ngoài, cùng với mã dài dòng, khó hiểu và dễ lỗi, nhưng JavaScript và CSS thuần túy kể từ đó đã kết hợp các tính năng để đạt được tương tác giống như ứng dụng gốc mà không tốn kém. Chúng tôi đã chứng minh điều này bằng cách triển khai mẫu điều hướng "điểm nổi bật di chuyển" bằng hai cách tiếp cận: Chuyển đổi CSS kết hợp với phương thức getBoundingClientRect() và API Chuyển đổi chế độ xem.

Tài nguyên​

 
Back
Bên trên