ResizeObserver: 요소의 document.onresize와 유사

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

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

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

이것이 ResizeObserver이 유용한 원시 유형인 이유입니다. 변경의 원인과 관계없이 관찰된 요소의 크기 변경에 반응합니다. 관찰된 요소의 새 크기에도 액세스할 수 있습니다.

브라우저 지원

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

소스

API

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

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

세부정보

무엇이 보고되나요?

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

CSS 박스 모델의 다이어그램

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

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

  • borderBoxSize
  • contentBoxSize
  • devicePixelContentBoxSize

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

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

언제 신고되나요?

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

Gotcha

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로 렌더링을 너무 지연시키지 않도록 주의하세요.