VisualViewport 소개

Jake Archibald
Jake Archibald

뷰포트가 두 개 이상 있다고 가정해 보겠습니다.

BRRRRAAAAAAAMMMMMMMMMM

현재 사용 중인 뷰포트는 실제로 뷰포트 내의 뷰포트입니다.

BRRRRAAAAAAAMMMMMMMMMM

DOM에서 제공하는 데이터가 이러한 뷰포트 중 하나를 참조하고 다른 뷰포트는 참조하지 않는 경우도 있습니다.

BRRRRAAAAM… 잠깐, 뭐라고요?

맞습니다. 다음을 참고하세요.

레이아웃 표시 영역과 시각적 표시 영역

위의 동영상에는 스크롤되고 핀치 확대/축소되는 웹페이지와 페이지 내 뷰포트의 위치를 보여주는 오른쪽의 미니 맵이 나와 있습니다.

일반 스크롤 시에는 매우 간단합니다. 녹색 영역은 position: fixed 항목이 고정되는 레이아웃 뷰포트를 나타냅니다.

핀치 확대/축소가 도입되면 이상한 일이 발생합니다. 빨간색 상자는 실제로 볼 수 있는 페이지의 일부인 시각적 표시 영역을 나타냅니다. 이 뷰포트는 position: fixed 요소가 레이아웃 뷰포트에 연결된 상태로 원래 위치에 유지된 채로 이동할 수 있습니다. 레이아웃 뷰포트의 경계에서 화면 이동을 하면 레이아웃 뷰포트가 함께 드래그됩니다.

호환성 개선

안타깝게도 웹 API는 참조하는 뷰포인트 측면에서 일관되지 않으며 브라우저 간에 일관되지도 않습니다.

예를 들어 element.getBoundingClientRect().y레이아웃 표시 영역 내의 오프셋을 반환합니다. 좋습니다. 하지만 페이지 내 위치가 필요한 경우가 많으므로 다음과 같이 작성합니다.

element.getBoundingClientRect().y + window.scrollY

그러나 많은 브라우저는 window.scrollY시각적 표시 영역을 사용하므로 사용자가 핀치 줌하면 위 코드가 중단됩니다.

Chrome 61에서는 window.scrollY를 변경하여 대신 레이아웃 뷰포트를 참조합니다. 즉, 위 코드는 핀치 확대/축소 시에도 작동합니다. 실제로 브라우저는 레이아웃 표시 영역을 참조하도록 모든 위치 속성을 서서히 변경하고 있습니다.

단 하나의 새 속성을 제외하고…

시각적 표시 영역을 스크립트에 노출

새 API는 시각적 표시 영역을 window.visualViewport로 노출합니다. 교차 브라우저 승인을 받은 초안 사양으로, Chrome 61에 출시됩니다.

console.log(window.visualViewport.width);

window.visualViewport의 기능은 다음과 같습니다.

숙박 시설 visualViewport
offsetLeft 시각적 표시 영역의 왼쪽 가장자리와 레이아웃 표시 영역 사이의 거리(CSS 픽셀)입니다.
offsetTop 시각적 뷰포트의 상단 가장자리와 레이아웃 뷰포트 사이의 거리(CSS 픽셀)입니다.
pageLeft 시각적 표시 영역의 왼쪽 가장자리와 문서의 왼쪽 경계 사이의 거리(CSS 픽셀)입니다.
pageTop 시각적 표시 영역의 상단 가장자리와 문서의 상단 경계 사이의 거리(CSS 픽셀)입니다.
width 시각적 표시 영역의 너비입니다(단위: CSS 픽셀).
height 시각적 표시 영역의 높이입니다(단위: CSS 픽셀).
scale 핀치 확대/축소로 적용된 배율입니다. 확대로 인해 콘텐츠 크기가 두 배가 되면 2가 반환됩니다. 이는 devicePixelRatio의 영향을 받지 않습니다.

몇 가지 이벤트도 있습니다.

window.visualViewport.addEventListener('resize', listener);
이벤트 visualViewport
resize width, height 또는 scale가 변경될 때 실행됩니다.
scroll offsetLeft 또는 offsetTop가 변경될 때 실행됩니다.

데모

이 도움말의 시작 부분에 있는 동영상은 visualViewport를 사용하여 제작되었습니다. Chrome 61 이상에서 확인해 보세요. 이 동영상에서는 visualViewport를 사용하여 미니맵을 시각적 뷰포트의 오른쪽 상단에 고정하고, 손가락을 벌려 확대/축소해도 항상 동일한 크기로 표시되도록 역 크기 조정을 적용합니다.

주의할 점

시각적 표시 영역이 변경될 때만 이벤트가 실행됨

당연한 말처럼 들리지만, visualViewport를 처음 사용했을 때는 이 점을 놓쳤습니다.

레이아웃 뷰포트의 크기가 조절되지만 시각적 뷰포트의 크기는 조절되지 않으면 resize 이벤트가 수신되지 않습니다. 그러나 시각적 표시 영역의 너비/높이가 변경되지 않고 레이아웃 표시 영역의 크기가 조절되는 경우는 드뭅니다.

스크롤이 문제입니다. 스크롤이 발생하지만 시각적 표시 영역이 레이아웃 표시 영역을 기준으로 정적 상태로 유지되면 visualViewport에서 scroll 이벤트가 수신되지 않습니다. 이는 매우 일반적인 상황입니다. 일반 문서 스크롤 중에 시각적 뷰포트는 레이아웃 뷰포트의 왼쪽 상단에 고정된 상태로 유지되므로 visualViewport에서 scroll실행되지 않습니다.

pageToppageLeft를 비롯한 시각적 뷰포트의 모든 변경사항을 수신하려면 창의 스크롤 이벤트도 수신 대기해야 합니다.

visualViewport.addEventListener('scroll', update);
visualViewport.addEventListener('resize', update);
window.addEventListener('scroll', update);

여러 리스너로 인한 작업 중복 방지

창에서 scrollresize를 리슨하는 것과 마찬가지로 결과적으로 일종의 '업데이트' 함수를 호출할 수 있습니다. 그러나 이러한 이벤트가 동시에 발생하는 경우가 많습니다. 사용자가 창 크기를 조절하면 resize가 트리거되지만 scroll도 자주 트리거됩니다. 성능을 개선하려면 변경사항을 여러 번 처리하지 마세요.

// Add listeners
visualViewport.addEventListener('scroll', update);
visualViewport.addEventListener('resize', update);
addEventListener('scroll', update);

let pendingUpdate = false;

function update() {
    // If we're already going to handle an update, return
    if (pendingUpdate) return;

    pendingUpdate = true;

    // Use requestAnimationFrame so the update happens before next render
    requestAnimationFrame(() => {
    pendingUpdate = false;

    // Handle update here
    });
}

단일 update 이벤트와 같은 더 나은 방법이 있을 수 있다고 생각하여 이 문제에 관한 사양 문제를 제출했습니다.

이벤트 핸들러가 작동하지 않음

Chrome 버그로 인해 다음 작업은 작동하지 않습니다.

금지사항

버그가 있음 - 이벤트 핸들러를 사용함

visualViewport.onscroll = () => console.log('scroll!');

다음을 수행합니다.

권장사항

작동함 - 이벤트 리스너 사용

visualViewport.addEventListener('scroll', () => console.log('scroll'));

오프셋 값이 반올림됩니다.

또 다른 Chrome 버그라고 생각합니다.

offsetLeftoffsetTop은 반올림되므로 사용자가 확대하면 상당히 부정확합니다. 데모에서 이 문제의 원인을 확인할 수 있습니다. 사용자가 확대하고 천천히 화면을 이동하면 미니 맵이 확대되지 않은 픽셀 간에 스냅됩니다.

이벤트 비율이 느림

다른 resizescroll 이벤트와 마찬가지로 특히 모바일에서는 프레임마다 실행되지 않습니다. 데모에서 확인할 수 있습니다. 핀치 확대/축소를 하면 미니맵이 표시 영역에 고정된 상태로 유지되지 않습니다.

접근성

데모에서는 visualViewport를 사용하여 사용자의 핀치 줌을 상쇄했습니다. 이 특정 데모에서는 적절하지만 사용자의 확대 의도를 무시하는 작업을 하기 전에 신중하게 생각해야 합니다.

visualViewport은 접근성을 개선하는 데 사용할 수 있습니다. 예를 들어 사용자가 확대하는 경우 장식용 position: fixed 항목을 숨겨 사용자가 방해받지 않도록 할 수 있습니다. 하지만 사용자가 자세히 살펴보려고 하는 항목을 숨기지 않도록 주의하세요.

사용자가 확대할 때 분석 서비스에 게시하는 것이 좋습니다. 이를 통해 사용자가 기본 확대/축소 수준에서 어려움을 겪는 페이지를 파악할 수 있습니다.

visualViewport.addEventListener('resize', () => {
    if (visualViewport.scale > 1) {
    // Post data to analytics service
    }
});

이상입니다 visualViewport는 호환성 문제를 해결하는 유용한 소형 API입니다.