ResizeObserver: 요소의 document.onresize와 유사

ResizeObserver를 사용하면 요소의 크기가 변경될 때 알 수 있습니다.

ResizeObserver 이전에는 뷰포트 크기의 변경사항에 관한 알림을 받으려면 문서의 resize 이벤트에 리스너를 연결해야 했습니다. 이벤트 핸들러에서는 변경사항의 영향을 받은 요소를 파악하고 적절하게 반응하는 특정 루틴을 호출해야 합니다. 크기 조절 후 요소의 새 크기가 필요한 경우 getBoundingClientRect() 또는 getComputedStyle()를 호출해야 했으며, 읽기 모두와 쓰기 모두를 일괄 처리하지 않으면 레이아웃 스래싱이 발생할 수 있습니다.

이는 기본 창의 크기가 조정되지 않은 상태에서 요소의 크기가 변경되는 경우를 다루지 않습니다. 예를 들어 새 하위 요소를 추가하거나 요소의 display 스타일을 none로 설정하는 등의 작업은 요소, 형제 요소 또는 상위 요소의 크기를 변경할 수 있습니다.

이러한 이유로 ResizeObserver은 유용한 기본 요소입니다. 변경의 원인과 관계없이 관찰된 요소의 크기 변화에 반응합니다. 관찰된 요소의 새로운 크기에 대한 액세스 권한도 제공합니다.

Browser Support

  • Chrome: 64.
  • Edge: 79.
  • Firefox: 69.
  • Safari: 13.1.

Source

API

위에서 언급한 Observer 접미사가 있는 모든 API는 간단한 API 디자인을 공유합니다. ResizeObserver도 예외는 아닙니다. ResizeObserver 객체를 만들고 생성자에 콜백을 전달합니다. 콜백에는 관찰된 요소당 하나의 항목이 있는 ResizeObserverEntry 객체 배열이 전달되며, 여기에는 요소의 새 크기가 포함됩니다.

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);

세부정보

무엇을 신고하시겠습니까?

일반적으로 ResizeObserverEntrycontentRect라는 속성을 통해 요소의 콘텐츠 상자를 보고하며, 이 속성은 DOMRectReadOnly 객체를 반환합니다. 콘텐츠 상자는 콘텐츠를 배치할 수 있는 상자입니다. 패딩을 뺀 테두리 상자입니다.

CSS 박스 모델의 다이어그램

ResizeObservercontentRect의 측정기준과 패딩을 모두 보고하지만 contentRect감시합니다. contentRect를 요소의 경계 상자와 혼동하지 마세요. getBoundingClientRect()에서 보고한 경계 상자는 전체 요소와 그 하위 요소를 포함하는 상자입니다. SVG는 예외로, ResizeObserver는 경계 상자의 크기를 보고합니다.

Chrome 84부터 ResizeObserverEntry에는 더 자세한 정보를 제공하는 세 가지 새로운 속성이 있습니다. 이러한 속성은 각각 blockSize 속성과 inlineSize 속성이 포함된 ResizeObserverSize 객체를 반환합니다. 이 정보는 콜백이 호출된 시점에 관찰된 요소에 관한 것입니다.

  • borderBoxSize
  • contentBoxSize
  • devicePixelContentBoxSize

이러한 모든 항목은 읽기 전용 배열을 반환합니다. 향후 다중 열 시나리오에서 발생하는 여러 프래그먼트가 있는 요소를 지원할 수 있기를 바라기 때문입니다. 현재 이러한 배열에는 하나의 요소만 포함됩니다.

이러한 속성에 대한 플랫폼 지원은 제한적이지만 Firefox는 이미 처음 두 속성을 지원합니다.

언제 신고되나요?

사양에서는 ResizeObserver가 페인트 전, 레이아웃 후에 모든 크기 조절 이벤트를 처리해야 한다고 규정합니다. 따라서 ResizeObserver의 콜백은 페이지 레이아웃을 변경하는 데 이상적인 위치입니다. ResizeObserver 처리는 레이아웃과 페인트 사이에 발생하므로 이렇게 하면 페인트가 아닌 레이아웃만 무효화됩니다.

알겠습니다.

ResizeObserver 콜백 내에서 관찰된 요소의 크기를 변경하면 어떻게 되는지 궁금할 수 있습니다. 정답은 콜백에 대한 또 다른 호출을 즉시 트리거한다는 것입니다. 다행히 ResizeObserver에는 무한 콜백 루프와 순환 종속 항목을 방지하는 메커니즘이 있습니다. 크기가 조절된 요소가 이전 콜백에서 처리된 가장 얕은 요소보다 DOM 트리에서 더 깊은 경우에만 동일한 프레임에서 변경사항이 처리됩니다. 그렇지 않으면 다음 프레임으로 연기됩니다.

애플리케이션

ResizeObserver를 사용하면 요소별 미디어 쿼리를 구현할 수 있습니다. 요소를 관찰하면 명령형으로 디자인 중단점을 정의하고 요소의 스타일을 변경할 수 있습니다. 다음 에서 두 번째 상자는 너비에 따라 테두리 반경이 변경됩니다.

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)'));

살펴볼 만한 또 다른 흥미로운 예는 채팅 창입니다. 일반적인 위에서 아래로 대화 레이아웃에서 발생하는 문제는 스크롤 위치입니다. 사용자에게 혼동을 주지 않으려면 창이 최신 메시지가 표시되는 대화 하단에 고정되는 것이 좋습니다. 또한 레이아웃 변경 (예: 휴대전화가 가로 모드에서 세로 모드로 또는 그 반대로 전환)도 동일한 결과를 달성해야 합니다.

ResizeObserver를 사용하면 두 시나리오를 모두 처리하는 코드를 하나 작성할 수 있습니다. 창 크기 조절은 ResizeObserver가 정의에 따라 캡처할 수 있는 이벤트이지만 appendChild()를 호출하면 새 요소를 위한 공간을 만들어야 하므로 해당 요소의 크기도 조절됩니다(overflow: hidden가 설정되지 않은 경우). 이를 염두에 두고 원하는 효과를 얻기 위해 필요한 줄 수는 매우 적습니다.

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);

정말 멋지죠?

여기에서 사용자가 수동으로 위로 스크롤했으며 새 메시지가 도착할 때 스크롤이 해당 메시지에 고정되기를 원하는 경우를 처리하는 코드를 추가할 수 있습니다.

또 다른 사용 사례는 자체 레이아웃을 실행하는 모든 종류의 맞춤 요소입니다. ResizeObserver 이전에는 크기가 변경될 때 알림을 받아 하위 요소를 다시 배치할 수 있는 신뢰할 수 있는 방법이 없었습니다.

다음 페인트에 대한 상호작용 (INP)에 미치는 영향

다음 페인트에 대한 상호작용 (INP)은 사용자 상호작용에 대한 페이지의 전반적인 응답성을 측정하는 측정항목입니다. 페이지의 INP가 '양호' 기준점(200밀리초 이하)에 속하면 페이지가 사용자와의 상호작용에 안정적으로 반응한다고 할 수 있습니다.

사용자 상호작용에 대한 응답으로 이벤트 콜백이 실행되는 데 걸리는 시간이 상호작용의 총 지연 시간에 크게 기여할 수 있지만, INP에서 고려해야 하는 유일한 측면은 아닙니다. INP는 상호작용의 다음 페인트가 발생하는 데 걸리는 시간도 고려합니다. 상호작용에 대한 응답으로 사용자 인터페이스를 업데이트하는 데 필요한 렌더링 작업이 완료되는 데 걸리는 시간입니다.

ResizeObserver의 경우 ResizerObserver 인스턴스가 실행하는 콜백이 렌더링 작업 바로 전에 발생하므로 이는 중요합니다. 콜백에서 발생하는 작업은 고려해야 하므로 이는 설계에 따른 것입니다. 해당 작업의 결과로 사용자 인터페이스를 변경해야 할 가능성이 매우 높기 때문입니다.

ResizeObserver 콜백에서 필요한 만큼만 렌더링 작업을 실행하세요. 렌더링 작업이 과도하면 브라우저가 중요한 작업을 지연하는 상황이 발생할 수 있습니다. 예를 들어 상호작용에 ResizeObserver 콜백이 실행되도록 하는 콜백이 있는 경우 가능한 한 원활한 환경을 지원하기 위해 다음을 실행해야 합니다.

  • 과도한 스타일 재계산 작업을 방지하려면 CSS 선택자를 최대한 간단하게 유지하세요. 스타일 재계산은 레이아웃 바로 전에 발생하며 복잡한 CSS 선택자는 레이아웃 작업을 지연시킬 수 있습니다.
  • ResizeObserver 콜백에서 강제 리플로우를 트리거할 수 있는 작업을 실행하지 마세요.
  • 페이지 레이아웃을 업데이트하는 데 필요한 시간은 일반적으로 페이지의 DOM 요소 수에 따라 증가합니다. 페이지에서 ResizeObserver를 사용하는지 여부와 관계없이 이는 사실이지만 ResizeObserver 콜백에서 실행되는 작업은 페이지의 구조적 복잡성이 증가함에 따라 중요해질 수 있습니다.

결론

ResizeObserver모든 주요 브라우저에서 사용할 수 있으며 요소 수준에서 요소 크기 조정을 모니터링하는 효율적인 방법을 제공합니다. 이 강력한 API로 렌더링이 너무 지연되지 않도록 주의하세요.