두 개의 서로 다른 문서 간에 뷰 전환이 발생하면 이를 교차 문서 뷰 전환이라고 합니다. 이는 일반적으로 멀티페이지 애플리케이션 (MPA)에서 발생합니다. 교차 문서 뷰 전환은 Chrome 126부터 Chrome에서 지원됩니다.
브라우저 지원
교차 문서 뷰 전환은 동일 문서 뷰 전환과 동일한 구성요소와 원칙을 사용합니다. 이는 의도적인 설계입니다.
- 브라우저는 이전 페이지와 새 페이지 모두에 고유한
view-transition-name
가 있는 요소의 스냅샷을 찍습니다. - 렌더링이 억제되는 동안 DOM이 업데이트됩니다.
- 마지막으로 전환은 CSS 애니메이션을 기반으로 합니다.
동일한 문서 뷰 전환과 비교할 때 다른 점은 교차 문서 뷰 전환에서는 document.startViewTransition
를 호출하여 뷰 전환을 시작할 필요가 없다는 것입니다. 대신 교차 문서 뷰 전환의 트리거는 한 페이지에서 다른 페이지로의 동일 출처 탐색으로, 이는 일반적으로 웹사이트 사용자가 링크를 클릭할 때 실행하는 작업입니다.
즉, 두 문서 간에 뷰 전환을 시작하기 위해 호출할 API가 없습니다. 하지만 다음 두 가지 조건을 충족해야 합니다.
- 두 문서가 동일한 출처에 있어야 합니다.
- 뷰 전환을 허용하려면 두 페이지 모두에서 선택해야 합니다.
이 두 가지 조건은 이 문서의 뒷부분에서 설명합니다.
교차 문서 뷰 전환은 동일 출처 탐색으로 제한됩니다.
교차 문서 뷰 전환은 동일 출처 탐색으로 제한됩니다. 참여하는 두 페이지의 출처가 동일하면 탐색이 동일 출처로 간주됩니다.
페이지의 출처는 web.dev에 자세히 설명된 대로 사용된 스키마, 호스트 이름, 포트의 조합입니다.
예를 들어 developer.chrome.com
에서 developer.chrome.com/blog
로 이동할 때 교차 문서 뷰 전환을 사용할 수 있습니다. 두 페이지가 동일한 출처이기 때문입니다.
developer.chrome.com
에서 www.chrome.com
로 이동할 때는 교차 출처 및 동일 사이트이므로 이러한 전환을 사용할 수 없습니다.
교차 문서 뷰 전환은 선택사항입니다.
두 문서 간에 교차 문서 뷰 전환을 사용하려면 참여하는 두 페이지 모두 이를 허용하도록 선택해야 합니다. 이 작업은 CSS의 @view-transition
at-rule을 통해 실행됩니다.
@view-transition
at-rule에서 navigation
설명자를 auto
로 설정하여 교차 문서 동일 출처 탐색의 뷰 전환을 사용 설정합니다.
@view-transition {
navigation: auto;
}
navigation
설명자를 auto
로 설정하면 다음 NavigationType에 대해 뷰 전환이 발생하도록 허용하게 됩니다.
traverse
- 활성화가 브라우저 UI 메커니즘을 통해 사용자가 시작하지 않은 경우
push
또는replace
auto
에서 제외되는 탐색은 URL 주소 표시줄을 사용하거나 북마크를 클릭하는 탐색, 그리고 모든 형태의 사용자 또는 스크립트가 시작한 새로고침입니다.
탐색이 너무 오래 걸리는 경우(Chrome의 경우 4초 이상) TimeoutError
DOMException
로 뷰 전환이 건너뜁니다.
교차 문서 뷰 전환 데모
뷰 전환을 사용하여 스택 네비게이터 데모를 만드는 다음 데모를 확인하세요. 여기서는 document.startViewTransition()
를 호출하지 않습니다. 뷰 전환은 한 페이지에서 다른 페이지로 이동하여 트리거됩니다.
교차 문서 뷰 전환 맞춤설정
교차 문서 뷰 전환을 맞춤설정하려면 사용할 수 있는 몇 가지 웹 플랫폼 기능이 있습니다.
이러한 기능은 View Transition API 사양 자체의 일부가 아니지만 View Transition API 사양과 함께 사용하도록 설계되었습니다.
pageswap
및 pagereveal
이벤트
교차 문서 뷰 전환을 맞춤설정할 수 있도록 HTML 사양에는 사용할 수 있는 두 가지 새 이벤트인 pageswap
및 pagereveal
가 포함되어 있습니다.
이 두 이벤트는 뷰 전환이 발생할지 여부와 관계없이 동일한 출처의 교차 문서 탐색마다 실행됩니다. 두 페이지 간에 뷰 전환이 발생하려는 경우 이러한 이벤트의 viewTransition
속성을 사용하여 ViewTransition
객체에 액세스할 수 있습니다.
pageswap
이벤트는 페이지의 마지막 프레임이 렌더링되기 전에 발생합니다. 이 기능을 사용하면 이전 스냅샷이 찍히기 직전에 발신 페이지를 마지막으로 변경할 수 있습니다.pagereveal
이벤트는 페이지가 초기화되거나 재활성화된 후 첫 번째 렌더링 기회 전에 페이지에서 발생합니다. 이를 통해 새 스냅샷을 찍기 전에 새 페이지를 맞춤설정할 수 있습니다.
예를 들어 이러한 이벤트를 사용하여 일부 view-transition-name
값을 빠르게 설정하거나 변경하거나 sessionStorage
에서 데이터를 쓰고 읽음으로써 한 문서에서 다른 문서로 데이터를 전달하여 뷰 전환이 실제로 실행되기 전에 이를 맞춤설정할 수 있습니다.
let lastClickX, lastClickY;
document.addEventListener('click', (event) => {
if (event.target.tagName.toLowerCase() === 'a') return;
lastClickX = event.clientX;
lastClickY = event.clientY;
});
// Write position to storage on old page
window.addEventListener('pageswap', (event) => {
if (event.viewTransition && lastClick) {
sessionStorage.setItem('lastClickX', lastClickX);
sessionStorage.setItem('lastClickY', lastClickY);
}
});
// Read position from storage on new page
window.addEventListener('pagereveal', (event) => {
if (event.viewTransition) {
lastClickX = sessionStorage.getItem('lastClickX');
lastClickY = sessionStorage.getItem('lastClickY');
}
});
원하는 경우 두 이벤트 모두에서 전환을 건너뛸 수 있습니다.
window.addEventListener("pagereveal", async (e) => {
if (e.viewTransition) {
if (goodReasonToSkipTheViewTransition()) {
e.viewTransition.skipTransition();
}
}
}
pageswap
의 ViewTransition
객체와 pagereveal
의 ViewTransition
객체는 서로 다른 객체입니다. 또한 다양한 약속을 다르게 처리합니다.
pageswap
: 문서가 숨겨지면 이전ViewTransition
객체가 건너뜁니다. 이 경우viewTransition.ready
가 거부되고viewTransition.finished
가 확인됩니다.pagereveal
: 이 시점에서updateCallBack
프로미스가 이미 해결되었습니다.viewTransition.ready
및viewTransition.finished
약속을 사용할 수 있습니다.
내비게이션 활성화 정보
pageswap
및 pagereveal
이벤트 모두에서 이전 페이지와 새 페이지의 URL을 기반으로 조치를 취할 수도 있습니다.
예를 들어 MPA 스택 탐색기에서 사용할 애니메이션 유형은 탐색 경로에 따라 다릅니다.
- 개요 페이지에서 세부정보 페이지로 이동할 때 새 콘텐츠가 오른쪽에서 왼쪽으로 슬라이드되어야 합니다.
- 세부정보 페이지에서 개요 페이지로 이동할 때 이전 콘텐츠가 왼쪽에서 오른쪽으로 슬라이드 아웃되어야 합니다.
이렇게 하려면 pageswap
의 경우 곧 실행될 탐색에 관한 정보 또는 pagereveal
의 경우 방금 실행된 탐색에 관한 정보가 필요합니다.
이를 위해 이제 브라우저는 동일 출처 탐색에 관한 정보를 보유한 NavigationActivation
객체를 노출할 수 있습니다. 이 객체는 사용된 탐색 유형, 현재, 최종 대상 기록 항목을 노출하며, 이는 Navigation API의 navigation.entries()
에 있는 항목과 같습니다.
활성화된 페이지에서는 navigation.activation
를 통해 이 객체에 액세스할 수 있습니다. pageswap
이벤트에서는 e.activation
를 통해 액세스할 수 있습니다.
pageswap
및 pagereveal
이벤트의 NavigationActivation
정보를 사용하여 뷰 전환에 참여해야 하는 요소의 view-transition-name
값을 설정하는 이 프로필 데모를 확인하세요.
이렇게 하면 목록의 모든 항목을 미리 view-transition-name
로 장식할 필요가 없습니다. 대신 JavaScript를 사용하여 필요한 요소에서만 적시에 실행됩니다.
코드는 다음과 같습니다.
// OLD PAGE LOGIC
window.addEventListener('pageswap', async (e) => {
if (e.viewTransition) {
const targetUrl = new URL(e.activation.entry.url);
// Navigating to a profile page
if (isProfilePage(targetUrl)) {
const profile = extractProfileNameFromUrl(targetUrl);
// Set view-transition-name values on the clicked row
document.querySelector(`#${profile} span`).style.viewTransitionName = 'name';
document.querySelector(`#${profile} img`).style.viewTransitionName = 'avatar';
// Remove view-transition-names after snapshots have been taken
// (this to deal with BFCache)
await e.viewTransition.finished;
document.querySelector(`#${profile} span`).style.viewTransitionName = 'none';
document.querySelector(`#${profile} img`).style.viewTransitionName = 'none';
}
}
});
// NEW PAGE LOGIC
window.addEventListener('pagereveal', async (e) => {
if (e.viewTransition) {
const fromURL = new URL(navigation.activation.from.url);
const currentURL = new URL(navigation.activation.entry.url);
// Navigating from a profile page back to the homepage
if (isProfilePage(fromURL) && isHomePage(currentURL)) {
const profile = extractProfileNameFromUrl(currentURL);
// Set view-transition-name values on the elements in the list
document.querySelector(`#${profile} span`).style.viewTransitionName = 'name';
document.querySelector(`#${profile} img`).style.viewTransitionName = 'avatar';
// Remove names after snapshots have been taken
// so that we're ready for the next navigation
await e.viewTransition.ready;
document.querySelector(`#${profile} span`).style.viewTransitionName = 'none';
document.querySelector(`#${profile} img`).style.viewTransitionName = 'none';
}
}
});
또한 이 코드는 뷰 전환이 실행된 후 view-transition-name
값을 삭제하여 자체적으로 정리합니다. 이렇게 하면 페이지가 연속적인 탐색을 할 준비가 되며 기록 탐색도 처리할 수 있습니다.
이를 지원하려면 일시적으로 view-transition-name
를 설정하는 이 유틸리티 함수를 사용하세요.
const setTemporaryViewTransitionNames = async (entries, vtPromise) => {
for (const [$el, name] of entries) {
$el.style.viewTransitionName = name;
}
await vtPromise;
for (const [$el, name] of entries) {
$el.style.viewTransitionName = '';
}
}
이제 이전 코드를 다음과 같이 간소화할 수 있습니다.
// OLD PAGE LOGIC
window.addEventListener('pageswap', async (e) => {
if (e.viewTransition) {
const targetUrl = new URL(e.activation.entry.url);
// Navigating to a profile page
if (isProfilePage(targetUrl)) {
const profile = extractProfileNameFromUrl(targetUrl);
// Set view-transition-name values on the clicked row
// Clean up after the page got replaced
setTemporaryViewTransitionNames([
[document.querySelector(`#${profile} span`), 'name'],
[document.querySelector(`#${profile} img`), 'avatar'],
], e.viewTransition.finished);
}
}
});
// NEW PAGE LOGIC
window.addEventListener('pagereveal', async (e) => {
if (e.viewTransition) {
const fromURL = new URL(navigation.activation.from.url);
const currentURL = new URL(navigation.activation.entry.url);
// Navigating from a profile page back to the homepage
if (isProfilePage(fromURL) && isHomePage(currentURL)) {
const profile = extractProfileNameFromUrl(currentURL);
// Set view-transition-name values on the elements in the list
// Clean up after the snapshots have been taken
setTemporaryViewTransitionNames([
[document.querySelector(`#${profile} span`), 'name'],
[document.querySelector(`#${profile} img`), 'avatar'],
], e.viewTransition.ready);
}
}
});
렌더링 차단으로 콘텐츠가 로드될 때까지 기다리기
브라우저 지원
경우에 따라 특정 요소가 새 DOM에 표시될 때까지 페이지의 첫 번째 렌더링을 보류하는 것이 좋습니다. 이렇게 하면 플래시 현상이 방지되고 애니메이션을 적용할 상태가 안정적입니다.
<head>
에서 다음 메타 태그를 사용하여 페이지가 처음 렌더링되기 전에 있어야 하는 요소 ID를 하나 이상 정의합니다.
<link rel="expect" blocking="render" href="#section1">
이 메타 태그는 콘텐츠를 로드해야 한다는 의미가 아니라 요소가 DOM에 있어야 한다는 의미입니다. 예를 들어 이미지의 경우 DOM 트리에 지정된 id
가 있는 <img>
태그가 있으면 조건이 true로 평가되기에 충분합니다. 이미지 자체는 여전히 로드 중일 수 있습니다.
렌더링 차단에 올인하기 전에 증분 렌더링이 웹의 기본적인 측면이므로 렌더링 차단을 선택할 때는 주의하세요. 렌더링 차단의 영향은 사례별로 평가해야 합니다. 기본적으로 Core Web Vitals에 미치는 영향을 측정하여 blocking=render
가 사용자에게 미치는 영향을 적극적으로 측정하고 평가할 수 없는 한 blocking=render
를 사용하지 않는 것이 좋습니다.
교차 문서 뷰 전환에서 전환 유형 보기
교차 문서 뷰 전환은 뷰 전환 유형도 지원하므로 애니메이션과 캡처되는 요소를 맞춤설정할 수 있습니다.
예를 들어 페이지로 나누기에서 다음 페이지 또는 이전 페이지로 이동할 때 시퀀스에서 더 높은 페이지로 이동하는지 더 낮은 페이지로 이동하는지에 따라 다른 애니메이션을 사용하고자 할 수 있습니다.
이러한 유형을 미리 설정하려면 @view-transition
at-rule에 유형을 추가합니다.
@view-transition {
navigation: auto;
types: slide, forwards;
}
유형을 즉시 설정하려면 pageswap
및 pagereveal
이벤트를 사용하여 e.viewTransition.types
의 값을 조작합니다.
window.addEventListener("pagereveal", async (e) => {
if (e.viewTransition) {
const transitionType = determineTransitionType(navigation.activation.from, navigation.activation.entry);
e.viewTransition.types.add(transitionType);
}
});
유형은 이전 페이지의 ViewTransition
객체에서 새 페이지의 ViewTransition
객체로 자동으로 이전되지 않습니다. 애니메이션이 예상대로 실행되려면 적어도 새 페이지에서 사용할 유형을 결정해야 합니다.
이러한 유형에 응답하려면 동일 문서 뷰 전환과 동일한 방식으로 :active-view-transition-type()
가상 클래스 선택기를 사용하세요.
/* Determine what gets captured when the type is forwards or backwards */
html:active-view-transition-type(forwards, backwards) {
:root {
view-transition-name: none;
}
article {
view-transition-name: content;
}
.pagination {
view-transition-name: pagination;
}
}
/* Animation styles for forwards type only */
html:active-view-transition-type(forwards) {
&::view-transition-old(content) {
animation-name: slide-out-to-left;
}
&::view-transition-new(content) {
animation-name: slide-in-from-right;
}
}
/* Animation styles for backwards type only */
html:active-view-transition-type(backwards) {
&::view-transition-old(content) {
animation-name: slide-out-to-right;
}
&::view-transition-new(content) {
animation-name: slide-in-from-left;
}
}
/* Animation styles for reload type only */
html:active-view-transition-type(reload) {
&::view-transition-old(root) {
animation-name: fade-out, scale-down;
}
&::view-transition-new(root) {
animation-delay: 0.25s;
animation-name: fade-in, scale-up;
}
}
유형은 활성 뷰 전환에만 적용되므로 뷰 전환이 완료되면 유형이 자동으로 정리됩니다. 따라서 유형은 BFCache와 같은 기능과 잘 작동합니다.
데모
다음 페이지 분류 데모에서는 이동하는 페이지 번호에 따라 페이지 콘텐츠가 앞뒤로 슬라이드합니다.
사용할 전환 유형은 pagereveal
및 pageswap
이벤트에서 출발 URL과 도착 URL을 확인하여 결정됩니다.
const determineTransitionType = (fromNavigationEntry, toNavigationEntry) => {
const currentURL = new URL(fromNavigationEntry.url);
const destinationURL = new URL(toNavigationEntry.url);
const currentPathname = currentURL.pathname;
const destinationPathname = destinationURL.pathname;
if (currentPathname === destinationPathname) {
return "reload";
} else {
const currentPageIndex = extractPageIndexFromPath(currentPathname);
const destinationPageIndex = extractPageIndexFromPath(destinationPathname);
if (currentPageIndex > destinationPageIndex) {
return 'backwards';
}
if (currentPageIndex < destinationPageIndex) {
return 'forwards';
}
return 'unknown';
}
};
의견
개발자 의견은 언제나 환영합니다. 공유하려면 제안사항과 질문을 포함하여 GitHub에서 CSS 워킹 그룹에 문제를 제출하세요. 문제 앞에 [css-view-transitions]
를 붙입니다.
버그가 발생하면 대신 Chromium 버그를 신고하세요.