ResizeObserver: giống như document.onresize cho các phần tử

ResizeObserver cho bạn biết khi kích thước của một phần tử thay đổi.

Trước ResizeObserver, bạn phải đính kèm trình nghe vào sự kiện resize của tài liệu để nhận thông báo về mọi thay đổi về kích thước của khung nhìn. Trong trình xử lý sự kiện, bạn sẽ phải tìm hiểu những phần tử nào đã bị ảnh hưởng bởi sự thay đổi đó và gọi một quy trình cụ thể để phản ứng một cách phù hợp. Nếu cần kích thước mới của một phần tử sau khi đổi kích thước, bạn phải gọi getBoundingClientRect() hoặc getComputedStyle(). Thao tác này có thể khiến bố cục bị đơ nếu bạn không xử lý tất cả lượt đọc và tất cả lượt ghi.

Thao tác này thậm chí không bao gồm các trường hợp các phần tử thay đổi kích thước khi cửa sổ chính không thay đổi kích thước. Ví dụ: việc thêm các phần tử con mới, đặt kiểu display của một phần tử thành none hoặc các thao tác tương tự có thể thay đổi kích thước của một phần tử, thành phần đồng cấp hoặc đối tượng cấp trên của phần tử đó.

Đây là lý do ResizeObserver là một dữ liệu nguyên gốc hữu ích. Nó phản ứng với những thay đổi về kích thước của bất kỳ phần tử nào được quan sát, độc lập với nguyên nhân gây ra thay đổi đó. Trường này cũng cho phép truy cập vào kích thước mới của các phần tử được ghi nhận.

Hỗ trợ trình duyệt

  • 64
  • 79
  • 69
  • 13,1

Nguồn

API

Tất cả các API có hậu tố Observer mà chúng ta đã đề cập ở trên đều có chung một thiết kế API đơn giản. ResizeObserver cũng không phải là ngoại lệ. Bạn tạo một đối tượng ResizeObserver và truyền lệnh gọi lại đến hàm khởi tạo. Lệnh gọi lại được truyền một mảng các đối tượng ResizeObserverEntry (một mục nhập cho mỗi phần tử được quan sát) chứa kích thước mới cho phần tử đó.

var ro = new ResizeObserver(entries => {
  for (let entry of entries) {
    const cr = entry.contentRect;

    console.log('Element:', entry.target);
    console.log(`Element size: ${cr.width}px x ${cr.height}px`);
    console.log(`Element padding: ${cr.top}px ; ${cr.left}px`);
  }
});

// Observe one or multiple elements
ro.observe(someElement);

Một số thông tin chi tiết

Nội dung gì đang được báo cáo?

Nhìn chung, ResizeObserverEntry sẽ báo cáo hộp nội dung của một phần tử thông qua một thuộc tính có tên là contentRect. Thuộc tính này sẽ trả về đối tượng DOMRectReadOnly. Hộp nội dung là ô để đặt nội dung. Đó là ô đường viền trừ đi khoảng đệm.

Sơ đồ về mô hình hộp CSS.

Điều quan trọng bạn cần lưu ý là mặc dù ResizeObserver báo cáo cả kích thước của contentRect và khoảng đệm, nhưng nó chỉ xem contentRect. Đừng nhầm lẫn contentRect với hộp giới hạn của phần tử. Theo báo cáo của getBoundingClientRect(), hộp giới hạn là hộp chứa toàn bộ phần tử và các thành phần con cháu của nó. SVG là một ngoại lệ đối với quy tắc, trong đó ResizeObserver sẽ báo cáo kích thước của hộp giới hạn.

Kể từ Chrome 84, ResizeObserverEntry có ba thuộc tính mới để cung cấp thông tin chi tiết hơn. Mỗi thuộc tính này trả về một đối tượng ResizeObserverSize chứa một thuộc tính blockSize và một thuộc tính inlineSize. Thông tin này là về phần tử được quan sát tại thời điểm lệnh gọi lại được thực hiện.

  • borderBoxSize
  • contentBoxSize
  • devicePixelContentBoxSize

Tất cả các mục này đều trả về các mảng chỉ có thể đọc vì trong tương lai, các mục này có thể hỗ trợ các phần tử có nhiều mảnh xảy ra trong trường hợp nhiều cột. Hiện tại, những mảng này chỉ chứa một phần tử.

Hỗ trợ nền tảng cho các thuộc tính này là hạn chế, nhưng Firefox đã hỗ trợ hai thuộc tính đầu tiên.

Khi nào nội dung sẽ được báo cáo?

Thông số kỹ thuật này quy định rằng ResizeObserver phải xử lý mọi sự kiện đổi kích thước trước khi vẽ và sau khi tạo bố cục. Điều này khiến lệnh gọi lại của ResizeObserver là nơi lý tưởng để thay đổi bố cục của trang. Vì quá trình xử lý ResizeObserver diễn ra giữa bố cục và tô màu, nên việc này sẽ chỉ vô hiệu hoá bố cục chứ không phải vẽ.

Đúng rồi

Có thể bạn đang tự hỏi: điều gì sẽ xảy ra nếu tôi thay đổi kích thước của một phần tử được quan sát bên trong lệnh gọi lại thành ResizeObserver? Câu trả lời là: bạn sẽ kích hoạt một lệnh gọi khác cho lệnh gọi lại ngay lập tức. May mắn là ResizeObserver có cơ chế để tránh vòng lặp gọi lại vô hạn và các phần phụ thuộc tuần hoàn. Các thay đổi sẽ chỉ được xử lý trong cùng một khung nếu phần tử được đổi kích thước nằm sâu trong cây DOM so với phần tử chậm nhất được xử lý trong lệnh gọi lại trước đó. Nếu không, những hình ảnh đó sẽ bị trì hoãn đến khung hình tiếp theo.

Ứng dụng

ResizeObserver cho phép bạn triển khai các truy vấn nội dung nghe nhìn cho mỗi phần tử. Bằng cách quan sát các phần tử, bạn có thể bắt buộc xác định các điểm ngắt thiết kế và thay đổi kiểu của một phần tử. Trong ví dụ sau, hộp thứ hai sẽ thay đổi bán kính đường viền theo chiều rộng.

const ro = new ResizeObserver(entries => {
  for (let entry of entries) {
    entry.target.style.borderRadius =
        Math.max(0, 250 - entry.contentRect.width) + 'px';
  }
});
// Only observe the second box
ro.observe(document.querySelector('.box:nth-child(2)'));

Một ví dụ thú vị khác là cửa sổ trò chuyện. Vấn đề thường gặp trong bố cục cuộc trò chuyện từ trên xuống dưới là vị trí cuộn. Để tránh gây nhầm lẫn cho người dùng, bạn nên đặt cửa sổ nằm ở cuối cuộc trò chuyện, nơi các thông báo mới nhất xuất hiện. Ngoài ra, mọi loại thay đổi về bố cục (hãy nghĩ đến việc điện thoại chuyển từ chế độ ngang sang dọc hoặc ngược lại) đều mang lại kết quả tương tự.

ResizeObserver cho phép bạn viết một đoạn mã duy nhất để xử lý cả hai trường hợp. Đổi kích thước cửa sổ là một sự kiện mà ResizeObserver có thể thu thập theo định nghĩa, nhưng việc gọi appendChild() cũng sẽ đổi kích thước phần tử đó (trừ phi bạn đặt overflow: hidden), vì cần giải phóng không gian cho các phần tử mới. Vì vậy, bạn phải mất rất ít dòng để đạt được hiệu quả mong muốn:

const ro = new ResizeObserver(entries => {
  document.scrollingElement.scrollTop =
    document.scrollingElement.scrollHeight;
});

// Observe the scrollingElement for when the window gets resized
ro.observe(document.scrollingElement);

// Observe the timeline to process new messages
ro.observe(timeline);

Khá gọn gàng phải không?

Từ đây, tôi có thể thêm mã khác để xử lý trường hợp người dùng cuộn lên theo cách thủ công và muốn cuộn để bám sát thông báo đó khi có thông báo mới.

Một trường hợp sử dụng khác là dành cho bất kỳ loại phần tử tuỳ chỉnh nào đang có bố cục riêng. Cho đến ResizeObserver, không có cách đáng tin cậy nào để nhận thông báo khi kích thước của lớp này thay đổi để có thể bố trí lại các thành phần con.

Ảnh hưởng đến lượt tương tác đến nội dung hiển thị tiếp theo (INP)

Lượt tương tác với Hiển thị tiếp theo (INP) là chỉ số đo lường khả năng thích ứng tổng thể của trang đối với các lượt tương tác của người dùng. Nếu INP của một trang ở trong ngưỡng "tốt" (tức là 200 mili giây trở xuống), thì có thể nói rằng một trang có khả năng phản hồi đáng tin cậy với hoạt động tương tác của người dùng với trang đó.

Mặc dù khoảng thời gian cần thiết để lệnh gọi lại sự kiện chạy để phản hồi một lượt tương tác của người dùng có thể đóng góp đáng kể vào tổng độ trễ của một lượt tương tác, nhưng đó không phải là khía cạnh duy nhất của INP cần xem xét. INP cũng xem xét khoảng thời gian cần thiết để lần hiển thị tiếp theo của hoạt động tương tác xảy ra. Đây là khoảng thời gian cần thiết để cập nhật giao diện người dùng nhằm đáp ứng một hoạt động tương tác hoàn tất.

Trong trường hợp ResizeObserver quan tâm, điều này rất quan trọng vì lệnh gọi lại mà một thực thể ResizerObserver chạy xảy ra ngay trước khi kết xuất công việc. Đây là theo thiết kế, vì công việc xảy ra trong lệnh gọi lại phải được xem xét, vì kết quả của công việc đó rất có thể sẽ yêu cầu thay đổi đối với giao diện người dùng.

Hãy chú ý thực hiện ít thao tác kết xuất nhất có thể trong lệnh gọi lại ResizeObserver, vì việc kết xuất quá mức có thể khiến trình duyệt bị trì hoãn thực hiện các công việc quan trọng. Ví dụ: nếu bất kỳ lượt tương tác nào có lệnh gọi lại khiến lệnh gọi lại ResizeObserver chạy, hãy đảm bảo bạn đang thực hiện như sau để mang lại trải nghiệm mượt mà nhất có thể:

  • Hãy đảm bảo bộ chọn CSS của bạn càng đơn giản càng tốt để tránh việc tính toán lại kiểu quá nhiều. Việc tính toán lại kiểu diễn ra ngay trước khi bố cục và các bộ chọn CSS phức tạp có thể làm chậm trễ các thao tác bố cục.
  • Tránh thực hiện bất kỳ thao tác nào trong lệnh gọi lại ResizeObserver có thể kích hoạt quy trình chỉnh lại bắt buộc.
  • Thời gian cần thiết để cập nhật bố cục của một trang thường tăng lên theo số lượng phần tử DOM trên một trang. Mặc dù điều này vẫn đúng cho dù các trang có sử dụng ResizeObserver hay không, nhưng tác vụ được thực hiện trong lệnh gọi lại ResizeObserver có thể trở nên quan trọng khi cấu trúc của trang tăng lên.

Kết luận

ResizeObserver có trên tất cả trình duyệt chính, đồng thời cung cấp một cách hiệu quả để theo dõi việc đổi kích thước của các phần tử ở cấp độ phần tử. Tuy nhiên, bạn chỉ cần thận trọng để không trì hoãn việc kết xuất quá nhiều với API mạnh mẽ này.