Resize Observer
in TIL / 2024
30 minutes per day
개발 내용: 화면 회전에 따라 에디터 패널 크기 조절하기
요구사항:
- 가로 -> 세로 회전: bottom, height 유지
- 세로 -> 가로 회전:
- Bottom: 캔버스 넘어가면 그만큼 내려주기 (top:0 가 아니라, Bottom을 계산해주어야 해서 복잡함)
- Height: 캔버스 넘어가면 캔버스 최대높이로 맞추기
=> 캔버스 높이를 알아야 한다 => ResizeObserver 사용
소개
Element의 크기 변화를 감지하는 API
사용법
생성
const resizeObserver = useMemo(
() =>
new ResizeObserver((entries: ResizeObserverEntry[]) => {
entries.forEach((entry: ResizeObserverEntry) => {
console.log("@@ entry", entry);
});
}),
[]
);
위 entry 찍어보면
borderBoxSize: [ResizeObserverSize] // { blockSize: 1124, inlineSize: 820 } contentBoxSize: [ResizeObserverSize] // { blockSize: 1124, inlineSize: 820 } contentRect: DOMRectReadOnly {x: 0, y: 0, width: 820, height: 1124, top: 0, bottom: 1124, left: 0, right: 820} devicePixelContentBoxSize: [ResizeObserverSize] // { blockSize: 1124, inlineSize: 820 } target: div#playground.EditorPlaygroundView__Playground-sc-2f1bc596-0.kwtSLG
메소드
ResizeObserver.disconnect()
: 특정 옵저버가 관찰중이던 요소들을 관찰하지 않도록 설정ResizeObserver.observe()
: 관찰할 요소 추가ResizeObserver.unobserve()
: 관찰할 요소 제거
useEffect(() => {
const editorElem = document.querySelector("#playground");
if (editorElem) {
resizeObserver.observe(editorElem);
}
}, [resizeObserver]);
Observation Errors
여기 잘 이해 안가니 다시 읽어보기
- paint 단계 전 (유저에게 보여지기 전) 변화를 감지한다
따라서 Resize 이벤트 -> style, layout 재계산 => resize event 재발생 => 무한루프 가능
cyclic dependency 대응법: process eleements deeper in the DOM during each iteration (?????)
- 이거 안되면
ResizeObserver loop completed with undelivered notifications
윈도우 에러 발생
- 이거 안되면
- 에러 발생 코드 : infinte loop 발생함 (div 사이즈 무한 확장) 에러 “repeating every frame”
const divElem = document.querySelector("body > div");
const resizeObserver = new ResizeObserver((entries) => {
for (const entry of entries) {
entry.target.style.width = entry.contentBoxSize[0].inlineSize + 10 + "px";
}
});
resizeObserver.observe(divElem);
window.addEventListener("error", (e) => {
console.error(e.message);
});
- 종국에는 resize observer은 적절히 그럴싸한 레이아웃으로 마무리 될 것임 (??????)
- 단, 사용자는 잠시 broken layout을 보게 될 수 있음 (1 frame에 끝나야 하는게 여러 frame에 걸쳐서 끝날 수 있음)
requestAnimationFrame
- 위 이슈 해결을 위해 사용
requestAnimationFrame() 메서드는 브라우저에게 수행하기를 원하는 애니메이션을 알리고 다음 리페인트 이전에 해당 애니메이션을 업데이트하는 함수를 호출하게 된다
- resizeobserver callback을 requestAnimationFrame callback으로 넣어주면 해결됨
- browser repaint 후에 resizeobserver callback이 실행됨
- resize 옵저버 콜백이 resize를 다시 발생시키지 않도록 함
- ex)
expected size
같은 제한을 두어서 무한루프 방지
const divElem = document.querySelector("body > div");
const expectedSizes = new WeakMap();
const resizeObserver = new ResizeObserver((entries) => {
requestAnimationFrame(() => {
for (const entry of entries) {
const expectedSize = expectedSizes.get(entry.target);
if (entry.contentBoxSize[0].inlineSize === expectedSize) {
continue;
}
const newSize = entry.contentBoxSize[0].inlineSize + 10;
entry.target.style.width = `${newSize}px`;
expectedSizes.set(entry.target, newSize);
}
});
});
resizeObserver.observe(divElem);
window.addEventListener("error", (e) => {
console.error(e.message);
});