Car News

ResizeSensor và ResizeObserver: Giải pháp theo dõi thay đổi kích thước phần tử DOM tối ưu cho web hiện đại

Theo dõi thay đổi kích thước phần tử (element resize) là nền tảng cho giao diện responsive, biểu đồ tự co giãn, layout thích nghi, bảng dữ liệu ảo hóa và trình soạn thảo phức tạp. Bài viết này giúp bạn hiểu sâu cơ chế của ResizeSensor (giải pháp dựa trên DOM/scroll) và cách áp dụng ResizeObserver (API gốc của trình duyệt), kèm hướng dẫn, tối ưu hiệu năng và mẫu mã dùng thực chiến.

ResizeSensor là gì?

ResizeSensor là một kỹ thuật/libraries JavaScript chèn “cảm biến” vô hình vào trong phần tử để phát hiện thay đổi chiều rộng/chiều cao. Giải pháp này ra đời trước khi ResizeObserver xuất hiện, nhằm thay thế cho polling tốn CPU hoặc bắt sự kiện resize của window vốn không phản ánh kích thước của từng phần tử con.

Điểm mạnh của ResizeSensor:

  • Hoạt động cả trên các trình duyệt cũ không có ResizeObserver.
  • Không cần setInterval/polling.

Điểm hạn chế:

  • Chèn thêm DOM con, có thể gây chi phí reflow/paint nhẹ.
  • Sử dụng sự kiện scroll nội bộ để kích hoạt callback.
  • Cần đảm bảo phần tử có position phù hợp (thường là relative).

Cơ chế hoạt động của ResizeSensor

  • Chèn hai khối con (expand/shrink) vô hình với overflow: scroll vào trong phần tử cần theo dõi.
  • Khi kích thước phần tử thay đổi, các khối con thay đổi kích thước khiến scroll thay đổi, từ đó kích hoạt sự kiện và so sánh offsetWidth/offsetHeight để gọi callback.
  • Thường đặt element.style.position = ‘relative’ nếu phần tử chưa có position phù hợp, đảm bảo các cảm biến định vị chính xác.

Khi nào dùng ResizeSensor?

  • Cần hỗ trợ trình duyệt cũ (legacy) hoặc môi trường đặc thù.
  • Dự án đã tích hợp css-element-queries/ResizeSensor và muốn tiếp tục dùng nhất quán.
  • Cấu trúc UI phức tạp khó dựa hoàn toàn vào ResizeObserver do yêu cầu fallback.

Khuyến nghị: Trong web hiện đại, ưu tiên ResizeObserver nếu có thể, dùng ResizeSensor như fallback.

Hướng dẫn dùng ResizeSensor cơ bản

Cài đặt (ví dụ với css-element-queries):

  • NPM: npm i css-element-queries
  • Hoặc tải trực tiếp từ repository của thư viện

Ví dụ khởi tạo:

<div id="box"></div>
<script src="path/to/ResizeSensor.js"></script>
<script>
  const box = document.getElementById('box');
  const sensor = new ResizeSensor(box, () => {
    const { offsetWidth: w, offsetHeight: h } = box;
    console.log('Kích thước thay đổi:', w, h);
  });

  // Hủy khi không cần
  // ResizeSensor.detach(box);
</script>

Theo dõi nhiều phần tử:

const items = document.querySelectorAll('.card');
new ResizeSensor(items, (elem) => {
  console.log('Card thay đổi kích thước:', elem.offsetWidth, elem.offsetHeight);
});

Lưu ý:

  • Đảm bảo phần tử có thể đo được kích thước (không display: none).
  • Kiểm tra và dọn dẹp (detach) khi tháo component để tránh rò rỉ bộ nhớ.

Tối ưu hiệu năng khi dùng ResizeSensor

  • Gói callback bằng requestAnimationFrame hoặc debounce/throttle:
    let raf;
    new ResizeSensor(box, () => {
    cancelAnimationFrame(raf);
    raf = requestAnimationFrame(() => {
      // Xử lý layout/biểu đồ nhẹ nhàng trong rAF
    });
    });
  • Chỉ theo dõi những phần tử thực sự cần thiết, bỏ theo dõi khi phần tử ẩn hoặc không còn trong DOM.
  • Tránh thao tác DOM nặng trong callback; tách tính toán và cập nhật UI.

ResizeObserver: Lựa chọn hiện đại

ResizeObserver là API chuẩn, hỗ trợ phần lớn trình duyệt hiện nay:

  • Ưu điểm: Không chèn DOM phụ, callback theo lô (batch), chính xác và ít ảnh hưởng layout.
  • Hỗ trợ: Chrome 64+, Edge 79+, Firefox 69+, Safari 13.1+. Với môi trường cũ, dùng polyfill hoặc fallback ResizeSensor.

Ví dụ ResizeObserver:

const el = document.querySelector('#box');
const ro = new ResizeObserver((entries) => {
  for (const entry of entries) {
    const cr = entry.contentRect;
    console.log('Kích thước content:', cr.width, cr.height);
  }
});
ro.observe(el);

// Dừng theo dõi
// ro.disconnect();

Mẫu fallback: ResizeObserver trước, ResizeSensor sau

function onResize(el, handler) {
  if ('ResizeObserver' in window) {
    const ro = new ResizeObserver((entries) => {
      const entry = entries[0];
      handler(entry.contentRect, entry.target);
    });
    ro.observe(el);
    return () => ro.disconnect();
  } else {
    const sensor = new ResizeSensor(el, () => {
      handler(el.getBoundingClientRect(), el);
    });
    return () => ResizeSensor.detach(el);
  }
}

// Sử dụng:
const stop = onResize(document.getElementById('box'), (rect) => {
  console.log(rect.width, rect.height);
});
// Gọi stop() khi unmount

Tích hợp với framework

  • React (hook đơn giản):
    
    import { useEffect } from 'react';

export function useElementResize(ref, onSize) {
useEffect(() => {
const el = ref.current;
if (!el) return;

let stop = () => {};
if ('ResizeObserver' in window) {
  const ro = new ResizeObserver((entries) => onSize(entries[0].contentRect));
  ro.observe(el);
  stop = () => ro.disconnect();
} else {
  const sensor = new ResizeSensor(el, () => onSize(el.getBoundingClientRect()));
  stop = () => ResizeSensor.detach(el);
}
return stop;

}, [ref, onSize]);
}


- Vue (directive tối giản):
```js
const vResize = {
  mounted(el, binding) {
    const handler = binding.value || (() => {});
    el.__stopResize__ = (function() {
      if ('ResizeObserver' in window) {
        const ro = new ResizeObserver((entries) => handler(entries[0].contentRect));
        ro.observe(el);
        return () => ro.disconnect();
      } else {
        const sensor = new ResizeSensor(el, () => handler(el.getBoundingClientRect()));
        return () => ResizeSensor.detach(el);
      }
    })();
  },
  unmounted(el) {
    el.__stopResize__ && el.__stopResize__();
  }
};
  • Angular: tạo Directive dùng Renderer2; trong ngOnInit khởi tạo ResizeObserver/ResizeSensor, trong ngOnDestroy gọi disconnect/detach.

Lỗi thường gặp và cách khắc phục

  • Phần tử display: none: Kích thước trả về 0, callback không có ý nghĩa. Cần quan sát sau khi phần tử hiển thị hoặc dùng IntersectionObserver để đồng bộ thời điểm gắn theo dõi.
  • Thiếu position: relative: Với ResizeSensor, nếu phần tử là static, thư viện có thể tự set relative. Kiểm tra xung đột layout/z-index.
  • Overflow/cắt nội dung: Cảm biến vô hình thường không ảnh hưởng layout, nhưng hãy kiểm tra các rule overflow, transform, contain có thể tác động phép đo.
  • Gọi detach muộn: Dẫn đến rò rỉ bộ nhớ hoặc callback kích hoạt trên phần tử đã hủy. Luôn dọn dẹp trong lifecycle unmount/destroy.

Best practices

  • Ưu tiên ResizeObserver trên trình duyệt hiện đại; fallback ResizeSensor khi cần.
  • Gói cập nhật UI trong requestAnimationFrame để giảm giật lag.
  • Quan sát ở cấp phần tử gần nhất thay vì toàn bộ container lớn để tránh bão sự kiện.
  • Kết hợp CSS contain, content-visibility khi phù hợp để tối ưu layout.

Câu hỏi thường gặp

  • Có nên lắng nghe window.resize thay cho element resize? Không. window.resize không biết khi một phần tử con đổi kích thước do CSS hoặc nội dung.
  • Dùng polling có ổn không? Polling gây tốn CPU, không khuyến nghị khi đã có ResizeObserver/ResizeSensor.
  • Có cần debounce? Nên. Nó giúp giảm số lần cập nhật DOM/biểu đồ.
  • ResizeObserver có trả kích thước border-box/content-box không? contentRect là content box; có thể cấu hình box options trong một số trình duyệt hiện đại.
  • ResizeSensor có hoạt động khi phần tử detached khỏi DOM? Không đáng tin cậy; hãy detach ngay khi phần tử bị hủy.
  • Có xung đột với transforms/animations? Thông thường không, nhưng cần kiểm thử vì reflow/paint có thể thay đổi thời điểm callback.

Kết luận

  • Dùng ResizeObserver làm mặc định cho ứng dụng web hiện đại vì gọn nhẹ và hiệu năng tốt.
  • Bổ sung ResizeSensor làm phương án dự phòng để đảm bảo tính tương thích cho trình duyệt cũ.
  • Quản trị vòng đời quan sát, tối ưu callback và tránh thao tác DOM nặng để đạt trải nghiệm mượt mà.

Related posts

Vô lăng đột nhiên bị nặng: Nguyên nhân, cách xử lý nhanh và mẹo phòng tránh an toàn

admin

Tối ưu giao diện đánh giá sản phẩm trên website: noUiSlider, Dropzone và hệ thống Review hiện đại

admin

Dán decal xe SH: Màu nào đẹp, giá bao nhiêu, kinh nghiệm chọn tem và địa chỉ uy tín

admin

Leave a Comment