Tiêu đề thu nhỏ khi cuộn là một mẫu giao diện người dùng phổ biến, trong đó phần tử tiêu đề cố định ở đầu trang chuyển đổi mượt mà sang kích thước nhỏ hơn khi người dùng cuộn xuống. Hiệu ứng này thường được dùng để tối đa hoá không gian màn hình cho nội dung chính, đồng thời vẫn cho phép người dùng truy cập vào các thành phần điều hướng hoặc thành phần thương hiệu thiết yếu. Với ảnh động dựa trên thao tác cuộn CSS, bạn có thể đạt được hiệu ứng này theo cách khai báo và hiệu quả bằng cách liên kết ảnh động với vị trí cuộn của tài liệu.
Cách triển khai
Sau đây là cách tạo tiêu đề thu nhỏ khi cuộn:
Tạo tiêu đề cố định: Bắt đầu bằng một phần tử tiêu đề được cố định ở đầu trang và có chiều cao được xác định trước.
<header>HEADER</header>header { position: fixed; height: 200px; top: 0; left: 0; right: 0; }Xác định ảnh động thu nhỏ: Tạo ảnh động CSS để thay đổi chiều cao của tiêu đề.
@keyframes shrink { to { height: 50px; } }Áp dụng ảnh động và dòng thời gian cuộn: Đính kèm ảnh động vào tiêu đề và sử dụng hàm
scroll()để liên kết ảnh động đó với vị trí cuộn của tài liệu.header { animation: shrink auto linear both; animation-timeline: scroll(block root); }Đặt
animation-range: Sử dụng thuộc tínhanimation-rangeđể chỉ định khoảng cách cuộn mà ảnh động sẽ diễn ra. Ví dụ: để thu nhỏ tiêu đề trong 150 pixel đầu tiên của thao tác cuộn, bạn sẽ sử dụnganimation-range: 0px 150px;.header { animation-range: 0px 150px; }
Mẹo: Để ngăn nội dung theo sau tiêu đề bị tiêu đề che khuất, hãy thêm padding-top vào body (hoặc vùng chứa nội dung chính) bằng với chiều cao ban đầu của tiêu đề.
Mẹo: Để đảm bảo nội dung của trang cuộn đồng bộ với tiêu đề thu nhỏ, hãy đặt animation-range-end thành mức chênh lệch giữa kích thước bắt đầu và kích thước kết thúc. Điều này đảm bảo ảnh động hoàn tất chính xác khi tiêu đề đạt đến kích thước cuối cùng. Trong bản minh hoạ này, tiêu đề thu nhỏ từ 200px xuống 50px, nên animation-range-end được đặt thành 150px.
Mã mẫu
@keyframes shrink {
to {
height: 50px;
}
}
header {
animation: shrink auto linear both;
animation-timeline: scroll(block root);
animation-range: 0px 150px;
}
Các phương pháp hay nhất
Khi sử dụng ảnh động dựa trên thao tác cuộn, bạn cần tuân thủ một số phương pháp hay nhất để đảm bảo trải nghiệm mượt mà và dễ tiếp cận:
- NÊN thêm tính năng phát hiện: Không phải trình duyệt nào cũng hỗ trợ ảnh động dựa trên thao tác cuộn. Sử dụng
@supports ((animation-timeline: scroll()) and (animation-range: 0% 100%))để kiểm tra khả năng hỗ trợ và cung cấp phương án dự phòng cho các trình duyệt không hỗ trợ.- BẮT BUỘC phải đưa quy trình kiểm tra
(animation-range: 0% 100%)vào đây để lọc ra những trình duyệt chỉ hỗ trợ một phần. - KHÔNG sử dụng gói
scroll-timeline-polyfillcho chiến lược dự phòng vì gói này chưa hoàn thiện tính năng và có nhiều vấn đề đã biết. - Nếu ảnh động chỉ được coi là để trang trí, hãy chọn Progressive Enhancement (Cải tiến tăng dần) và KHÔNG cung cấp phương án dự phòng.
- BẮT BUỘC phải đưa quy trình kiểm tra
- NÊN tôn trọng lựa chọn ưu tiên của người dùng: Một số người dùng muốn có ít chuyển động hơn trên web. Sử dụng truy vấn nội dung nghe nhìn
prefers-reduced-motionđể tắt hoặc giảm ảnh động cho những người dùng này. - NÊN cố gắng chỉ tạo ảnh động cho các thuộc tính CSS hiệu suất cao: Để có ảnh động mượt mà nhất, hãy chỉ tạo ảnh động cho các thuộc tính có thể được xử lý bằng luồng trình tổng hợp của trình duyệt, chẳng hạn như
transformvàopacity. Việc tạo ảnh động cho các thuộc tính khác nhưwidthhoặcheightcó thể dẫn đến các vấn đề về hiệu suất. - NÊN sử dụng đúng thứ tự khai báo: Khi sử dụng thuộc tính viết tắt
animation, hãy khai báoanimation-timelinevàanimation-rangesau thuộc tính đó để ngăn thuộc tính viết tắt đặt lại dòng thời gian.
Khi sử dụng hàm scroll() để tạo ảnh động dựa trên thao tác cuộn:
- KHÔNG BẮT BUỘC nêu rõ về trình cuộn: Khi không nhắm đến trình cuộn cấp trên gần nhất, hãy nêu rõ trình cuộn mà bạn muốn sử dụng bằng
scroll(root)hoặcscroll(self).- Khi
root,nearesthoặcselfkhông đủ, hãy sử dụng dòng thời gian cuộn được đặt tên.
- Khi
- KHÔNG BẮT BUỘC nêu rõ trục cần theo dõi: Khi không nhắm đến trục
blockmặc định (chẳng hạn như trong trình cuộn ngang), hãy nêu rõ trục cần theo dõi bằngscroll(block)hoặcscroll(inline).
Đối với trường hợp sử dụng cụ thể này:
- Phần tử mà bạn tạo ảnh động KHÔNG ĐƯỢC là
position: statichoặcposition: relativekhi sử dụng tỷ lệ phần trăm tronganimation-range.- Điều này là do các phần tử đó được coi là "trong luồng". Việc thu nhỏ các phần tử đó khi bạn cuộn sẽ làm giảm tổng khoảng cách cuộn, từ đó ảnh hưởng đến giá trị được tính toán (ví dụ:
10%) vào thao tác cuộn.
- Điều này là do các phần tử đó được coi là "trong luồng". Việc thu nhỏ các phần tử đó khi bạn cuộn sẽ làm giảm tổng khoảng cách cuộn, từ đó ảnh hưởng đến giá trị được tính toán (ví dụ:
Hỗ trợ trình duyệt và chiến lược dự phòng
Do đó, bạn thường cần có một chiến lược dự phòng.
Đối với những trình duyệt không hỗ trợ ảnh động dựa trên thao tác cuộn, bạn có thể sử dụng tính năng dự phòng để tạo lại hiệu ứng hình ảnh. Các phương án dự phòng thường được xây dựng bằng trình nghe cuộn (cho hiệu ứng ScrollTimeline) hoặc API IntersectionObserver (cho hiệu ứng ViewTimeline).
Trong các trình duyệt có hỗ trợ tích hợp cho ảnh động dựa trên thao tác cuộn, hãy LUÔN sử dụng phương thức triển khai CSS gốc vì các trình duyệt đó có hiệu suất cao hơn.
Xin lưu ý rằng không phải hiệu ứng nào cũng có thể được tạo lại bằng phương pháp dự phòng.
Cụ thể cho trường hợp sử dụng này, tập lệnh sau đây áp dụng phương án dự phòng cho các trình duyệt không hỗ trợ ảnh động dựa trên thao tác cuộn. Mã này sử dụng trình nghe cuộn để theo dõi vị trí cuộn của tài liệu trong khoảng cách 150px và cập nhật chiều cao của tiêu đề cho phù hợp.
// Fallback for browsers that don't support scroll-driven animations
if (!CSS.supports('(animation-timeline: scroll()) and (animation-range: 0% 100%)')) {
const header = document.querySelector('header');
const initialHeight = 200;
const finalHeight = 50;
const scrollDistance = 150;
window.addEventListener('scroll', () => {
const scrollY = window.scrollY;
const scrollPercent = Math.min(1, scrollY / scrollDistance);
const newHeight = initialHeight - (initialHeight - finalHeight) * scrollPercent;
header.style.height = `${newHeight}px`;
});
}