TL;DR
Sau đây là một bí mật: Bạn có thể không cần sự kiện scroll
trong ứng dụng tiếp theo. Bằng cách sử dụng IntersectionObserver
, tôi sẽ hướng dẫn bạn cách kích hoạt một sự kiện tuỳ chỉnh khi các phần tử position:sticky
được cố định hoặc khi các phần tử đó ngừng dính. Tất cả đều không sử dụng trình nghe cuộn. Thậm chí còn có một bản minh hoạ tuyệt vời để chứng minh điều đó:
Giới thiệu sự kiện sticky-change
Một trong những hạn chế thực tế của việc sử dụng vị trí cố định CSS là vị trí này không cung cấp tín hiệu nền tảng để biết thời điểm thuộc tính đang hoạt động. Nói cách khác, không có sự kiện nào để biết thời điểm một phần tử trở thành phần tử cố định hoặc thời điểm phần tử đó không còn là phần tử cố định.
Hãy xem ví dụ sau đây để khắc phục Sẽ thật tuyệt nếu trình duyệt cho biết thời điểm các phần tử chạm vào điểm đánh dấu đó phải không?
Rõ ràng là không chỉ mình tôi nghĩ như vậy. Một tín hiệu cho Với những trường hợp sử dụng này, chúng tôi đã tạo ra một mục tiêu cuối cùng: tạo một sự kiện kích hoạt khi một phần tử Bản minh hoạ sử dụng sự kiện này để tạo bóng khi tiêu đề được cố định. Thao tác này cũng sẽ cập nhật tiêu đề mới ở đầu trang. Hãy cùng tìm hiểu một số thuật ngữ để tôi có thể tham khảo các tên này trong phần còn lại của bài viết: Để biết tiêu đề nào chuyển sang "chế độ cố định", chúng ta cần xác định độ dời cuộn của vùng chứa cuộn. Điều đó sẽ giúp chúng ta có cách tính toán tiêu đề đang hiển thị. Tuy nhiên, việc này khá khó khăn nếu không có sự kiện Vì vậy, nếu không có sự kiện cuộn, chúng ta sẽ không thể thực hiện các phép tính liên quan đến bố cục trên tiêu đề. Thay vì sự kiện Chúng ta cần hai trình bảo vệ để xử lý 4 trường hợp cuộn lên và xuống: Bạn nên xem bản ghi màn hình từ 1 đến 4 theo thứ tự xảy ra: Các sentinel được đặt ở đầu và cuối mỗi phần.
Trình quan sát giao nhau (Intersection Observer) quan sát không đồng bộ các thay đổi trong giao điểm của một phần tử mục tiêu và khung nhìn tài liệu hoặc vùng chứa mẹ. Trong trường hợp này, chúng ta sẽ quan sát các giao điểm với vùng chứa mẹ.
Thành phần bí mật là Trước tiên, tôi thiết lập trình quan sát cho các trình cảnh báo đầu trang và chân trang: Sau đó, tôi thêm một trình quan sát để kích hoạt khi các phần tử Trình quan sát được định cấu hình bằng Quá trình này cũng tương tự như đối với trình quan sát dưới cùng ( Trình quan sát được định cấu hình bằng Cuối cùng, tôi có hai tiện ích để kích hoạt sự kiện tuỳ chỉnh Vậy là xong! Chúng tôi đã tạo một sự kiện tuỳ chỉnh khi các phần tử có Tôi thường tự hỏi liệu Thực ra là không. Chúng ta cần một cách để quan sát các thay đổi về kiểu trên một phần tử DOM.
Rất tiếc, không có API nền tảng web nào cho phép bạn theo dõi các thay đổi về kiểu. Trong tương lai, tiện ích "Style Mutation Observer" (Trình quan sát thay đổi kiểu) cho trình quan sát thay đổi có thể hữu ích để quan sát các thay đổi đối với kiểu được tính toán của một phần tử.
Trừ phi có lưu ý khác, nội dung của trang này được cấp phép theo Giấy phép ghi nhận tác giả 4.0 của Creative Commons và các mẫu mã lập trình được cấp phép theo Giấy phép Apache 2.0. Để biết thông tin chi tiết, vui lòng tham khảo Chính sách trang web của Google Developers. Java là nhãn hiệu đã đăng ký của Oracle và/hoặc các đơn vị liên kết với Oracle. Cập nhật lần gần đây nhất: 2017-09-19 UTC..sticky {
position: sticky;
top: 10px;
}
position:sticky
có thể mở ra một số trường hợp sử dụng:
position:sticky
được cố định. Hãy gọi sự kiện này là sticky-change
:document.addEventListener('sticky-change', e => {
const header = e.detail.target; // header became sticky or stopped sticking.
const sticking = e.detail.stuck; // true when header is sticky.
header.classList.toggle('shadow', sticking); // add drop shadow when sticking.
document.querySelector('.who-is-sticking').textContent = header.textContent;
});
Hiệu ứng cuộn mà không có sự kiện cuộn?
position:sticky
.position:sticky
đang áp dụng cho phần tử.scroll
:) Vấn đề khác là position:sticky
sẽ xoá phần tử khỏi bố cục khi phần tử đó trở thành cố định.Thêm DOM giả để xác định vị trí cuộn
scroll
, chúng ta sẽ sử dụng IntersectionObserver
để xác định thời điểm tiêu đề chuyển sang và thoát khỏi chế độ cố định. Việc thêm hai nút (còn gọi là sentinel) trong mỗi phần cố định, một ở trên cùng và một ở dưới cùng, sẽ đóng vai trò là điểm trung gian để xác định vị trí cuộn. Khi các điểm đánh dấu này đi vào và rời khỏi vùng chứa, chế độ hiển thị của chúng sẽ thay đổi và Intersection Observer sẽ kích hoạt lệnh gọi lại.
CSS
.sticky_sentinel--top
nằm ở đầu tiêu đề trong khi .sticky_sentinel--bottom
nằm ở cuối mục:
:root {
--default-padding: 16px;
--header-height: 80px;
}
.sticky {
position: sticky;
top: 10px; /* adjust sentinel height/positioning based on this position. */
height: var(--header-height);
padding: 0 var(--default-padding);
}
.sticky_sentinel {
position: absolute;
left: 0;
right: 0; /* needs dimensions */
visibility: hidden;
}
.sticky_sentinel--top {
/* Adjust the height and top values based on your on your sticky top position.
e.g. make the height bigger and adjust the top so observeHeaders()'s
IntersectionObserver fires as soon as the bottom of the sentinel crosses the
top of the intersection container. */
height: 40px;
top: -24px;
}
.sticky_sentinel--bottom {
/* Height should match the top of the header when it's at the bottom of the
intersection container. */
height: calc(var(--header-height) + var(--default-padding));
bottom: 0;
}
Thiết lập trình quan sát giao nhau
IntersectionObserver
. Mỗi sentinel nhận được một IntersectionObserver
để quan sát chế độ hiển thị giao lộ của nó trong vùng chứa cuộn. Khi một trình quan sát cuộn vào khung nhìn hiển thị, chúng ta biết rằng tiêu đề đã được cố định hoặc không còn dính. Tương tự, khi một sentinel thoát khỏi khung nhìn./**
* Notifies when elements w/ the `sticky` class begin to stick or stop sticking.
* Note: the elements should be children of `container`.
* @param {!Element} container
*/
function observeStickyHeaderChanges(container) {
observeHeaders(container);
observeFooters(container);
}
observeStickyHeaderChanges(document.querySelector('#scroll-container'));
.sticky_sentinel--top
đi qua đầu vùng chứa cuộn (theo một trong hai hướng).
Hàm observeHeaders
tạo các sentinel trên cùng và thêm các sentinel đó vào từng phần. Trình quan sát tính toán giao điểm của sentinel với đầu vùng chứa và quyết định xem sentinel có đang vào hay rời khỏi khung nhìn hay không. Thông tin đó xác định xem tiêu đề phần có dính hay không./**
* Sets up an intersection observer to notify when elements with the class
* `.sticky_sentinel--top` become visible/invisible at the top of the container.
* @param {!Element} container
*/
function observeHeaders(container) {
const observer = new IntersectionObserver((records, observer) => {
for (const record of records) {
const targetInfo = record.boundingClientRect;
const stickyTarget = record.target.parentElement.querySelector('.sticky');
const rootBoundsInfo = record.rootBounds;
// Started sticking.
if (targetInfo.bottom < rootBoundsInfo.top) {
fireEvent(true, stickyTarget);
}
// Stopped sticking.
if (targetInfo.bottom >= rootBoundsInfo.top &&
targetInfo.bottom < rootBoundsInfo.bottom) {
fireEvent(false, stickyTarget);
}
}
}, {threshold: [0], root: container});
// Add the top sentinels to each section and attach an observer.
const sentinels = addSentinels(container, 'sticky_sentinel--top');
sentinels.forEach(el => observer.observe(el));
}
threshold: [0]
để lệnh gọi lại của trình quan sát này kích hoạt ngay khi trình quan sát xuất hiện..sticky_sentinel--bottom
). Một trình quan sát thứ hai được tạo để kích hoạt khi chân trang đi qua phần dưới cùng của vùng chứa cuộn. Hàm observeFooters
tạo các nút sentinel và đính kèm các nút đó vào từng phần. Trình quan sát tính toán giao điểm của sentinel với đáy của vùng chứa và quyết định xem sentinel đó có đang đi vào hay rời khỏi vùng chứa hay không. Thông tin đó xác định xem tiêu đề phần có cố định hay không./**
* Sets up an intersection observer to notify when elements with the class
* `.sticky_sentinel--bottom` become visible/invisible at the bottom of the
* container.
* @param {!Element} container
*/
function observeFooters(container) {
const observer = new IntersectionObserver((records, observer) => {
for (const record of records) {
const targetInfo = record.boundingClientRect;
const stickyTarget = record.target.parentElement.querySelector('.sticky');
const rootBoundsInfo = record.rootBounds;
const ratio = record.intersectionRatio;
// Started sticking.
if (targetInfo.bottom > rootBoundsInfo.top && ratio === 1) {
fireEvent(true, stickyTarget);
}
// Stopped sticking.
if (targetInfo.top < rootBoundsInfo.top &&
targetInfo.bottom < rootBoundsInfo.bottom) {
fireEvent(false, stickyTarget);
}
}
}, {threshold: [1], root: container});
// Add the bottom sentinels to each section and attach an observer.
const sentinels = addSentinels(container, 'sticky_sentinel--bottom');
sentinels.forEach(el => observer.observe(el));
}
threshold: [1]
để lệnh gọi lại của trình quan sát này kích hoạt khi toàn bộ nút nằm trong chế độ xem.sticky-change
và tạo các sentinel:/**
* @param {!Element} container
* @param {string} className
*/
function addSentinels(container, className) {
return Array.from(container.querySelectorAll('.sticky')).map(el => {
const sentinel = document.createElement('div');
sentinel.classList.add('sticky_sentinel', className);
return el.parentElement.appendChild(sentinel);
});
}
/**
* Dispatches the `sticky-event` custom event on the target element.
* @param {boolean} stuck True if `target` is sticky.
* @param {!Element} target Element to fire the event on.
*/
function fireEvent(stuck, target) {
const e = new CustomEvent('sticky-change', {detail: {stuck, target}});
document.dispatchEvent(e);
}
Bản minh hoạ cuối cùng
position:sticky
trở thành cố định và thêm hiệu ứng cuộn mà không sử dụng sự kiện scroll
.Kết luận
IntersectionObserver
có phải là một công cụ hữu ích để thay thế một số mẫu giao diện người dùng dựa trên sự kiện scroll
đã phát triển trong những năm qua hay không. Hóa ra câu trả lời là có và không. Ngữ nghĩa của API IntersectionObserver
khiến bạn khó sử dụng cho mọi thứ. Tuy nhiên, như đã trình bày ở đây, bạn có thể sử dụng lớp này cho một số kỹ thuật thú vị.Có cách nào khác để phát hiện thay đổi về kiểu không?
MutationObserver
sẽ là lựa chọn đầu tiên hợp lý nhưng không hiệu quả trong hầu hết các trường hợp. Ví dụ: trong bản minh hoạ, chúng ta sẽ nhận được lệnh gọi lại khi lớp sticky
được thêm vào một phần tử, nhưng không phải khi kiểu được tính toán của phần tử đó thay đổi.
Hãy nhớ rằng lớp sticky
đã được khai báo khi tải trang.position: sticky
.