다중 페이지 애플리케이션의 문서 간 보기 전환

두 개의 서로 다른 문서 간에 뷰 전환이 발생하면 이를 교차 문서 뷰 전환이라고 합니다. 일반적으로 다중 페이지 애플리케이션 (MPA)의 경우에 해당합니다. 교차 문서 뷰 전환은 Chrome 126부터 Chrome에서 지원됩니다.

Browser Support

  • Chrome: 126.
  • Edge: 126.
  • Firefox: not supported.
  • Safari: 18.2.

Source

크로스 문서 뷰 전환은 동일 문서 뷰 전환과 동일한 빌딩 블록과 원칙을 사용합니다. 이는 의도적인 것입니다.

  1. 브라우저가 이전 페이지와 새 페이지 모두에서 고유한 view-transition-name가 있는 요소의 스냅샷을 찍습니다.
  2. 렌더링이 억제되는 동안 DOM이 업데이트됩니다.
  3. 마지막으로 전환은 CSS 애니메이션으로 작동합니다.

동일한 문서 뷰 전환과 비교했을 때 다른 점은 교차 문서 뷰 전환을 사용하면 document.startViewTransition를 호출하여 뷰 전환을 시작할 필요가 없다는 것입니다. 대신 교차 문서 보기 전환의 트리거는 한 페이지에서 다른 페이지로의 동일 출처 탐색입니다. 이는 일반적으로 웹사이트 사용자가 링크를 클릭하여 실행하는 작업입니다.

즉, 두 문서 간에 뷰 전환을 시작하기 위해 호출할 API가 없습니다. 하지만 충족해야 하는 두 가지 조건이 있습니다.

  • 두 문서가 모두 동일한 출처에 있어야 합니다.
  • 뷰 전환을 허용하려면 두 페이지 모두 선택해야 합니다.

이 두 조건은 이 문서의 뒷부분에서 설명합니다.


교차 문서 뷰 전환은 동일 출처 탐색으로 제한됩니다.

교차 문서 보기 전환은 동일 출처 탐색으로만 제한됩니다. 참여하는 두 페이지의 출처가 동일하면 탐색이 동일 출처로 간주됩니다.

페이지의 출처는 사용된 스키마, 호스트 이름, 포트의 조합이며, web.dev에 자세히 설명되어 있습니다.

스키마, 호스트 이름, 포트가 강조 표시된 URL의 예 이 두 가지를 합쳐 원본을 구성합니다.
스킴, 호스트 이름, 포트가 강조 표시된 URL의 예 이러한 요소가 결합되어 원본을 형성합니다.

예를 들어 developer.chrome.com에서 developer.chrome.com/blog로 이동할 때 교차 문서 뷰 전환이 있을 수 있습니다. 이는 동일한 출처이기 때문입니다. developer.chrome.com에서 www.chrome.com로 이동할 때는 이러한 전환이 허용되지 않습니다. 이는 교차 출처 및 동일 사이트이기 때문입니다.


교차 문서 보기 전환은 선택사항입니다.

두 문서 간에 교차 문서 보기 전환을 사용하려면 참여하는 두 페이지 모두 이 기능을 허용하도록 선택해야 합니다. 이 작업은 CSS의 @view-transition 규칙을 사용하여 실행됩니다.

@view-transition 규칙에서 navigation 설명자를 auto로 설정하여 교차 문서, 동일 출처 탐색의 뷰 전환을 사용 설정합니다.

@view-transition {
  navigation: auto;
}

navigation 설명자를 auto로 설정하면 다음 NavigationType에 뷰 전환이 발생하도록 허용하는 데 동의하는 것입니다.

  • traverse
  • push 또는 replace(활성화가 브라우저 UI 메커니즘을 통해 사용자에 의해 시작되지 않은 경우)

auto에서 제외되는 탐색은 예를 들어 URL 주소 표시줄을 사용하여 탐색하거나 북마크를 클릭하는 것, 모든 형태의 사용자 또는 스크립트 시작 새로고침입니다.

탐색이 너무 오래 걸리면(Chrome의 경우 4초 이상) TimeoutError DOMException로 뷰 전환이 건너뛰어집니다.

교차 문서 보기 전환 데모

뷰 전환을 사용하여 스택 탐색기 데모를 만드는 다음 데모를 확인하세요. 여기서는 document.startViewTransition() 호출이 없습니다. 뷰 전환은 한 페이지에서 다른 페이지로 이동하여 트리거됩니다.

스택 탐색기 데모 녹화 영상 Chrome 126 이상이 필요합니다.

문서 간 보기 전환 맞춤설정

문서 간 뷰 전환을 맞춤설정하는 데 사용할 수 있는 웹 플랫폼 기능이 있습니다.

이러한 기능은 뷰 전환 API 사양 자체에 포함되어 있지 않지만 사양과 함께 사용하도록 설계되었습니다.

pageswappagereveal 이벤트

Browser Support

  • Chrome: 124.
  • Edge: 124.
  • Firefox: not supported.
  • Safari: 18.2.

Source

문서 간 뷰 전환을 맞춤설정할 수 있도록 HTML 사양에는 pageswappagereveal의 두 가지 새로운 이벤트가 포함되어 있습니다.

이 두 이벤트는 뷰 전환이 발생하려고 하는지 여부와 관계없이 모든 동일 출처 교차 문서 탐색에 대해 발생합니다. 두 페이지 간에 뷰 전환이 발생하려고 하면 이러한 이벤트에서 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();
    }
  }
}

pageswappagerevealViewTransition 객체는 서로 다른 두 객체입니다. 또한 다양한 약속을 다르게 처리합니다.

  • pageswap: 문서가 숨겨지면 이전 ViewTransition 객체가 건너뛰어집니다. 이 경우 viewTransition.ready는 거부되고 viewTransition.finished는 해결됩니다.
  • pagereveal: 이 시점에서 updateCallBack 프로미스가 이미 해결되었습니다. viewTransition.readyviewTransition.finished 약속을 사용할 수 있습니다.

Browser Support

  • Chrome: 123.
  • Edge: 123.
  • Firefox: 147.
  • Safari: 26.2.

Source

pageswappagereveal 이벤트 모두에서 이전 페이지와 새 페이지의 URL을 기반으로 조치를 취할 수도 있습니다.

예를 들어 MPA 스택 탐색기에서 사용할 애니메이션 유형은 탐색 경로에 따라 달라집니다.

  • 개요 페이지에서 세부정보 페이지로 이동할 때 새 콘텐츠가 오른쪽에서 왼쪽으로 슬라이드되어야 합니다.
  • 세부정보 페이지에서 개요 페이지로 이동할 때 이전 콘텐츠가 왼쪽에서 오른쪽으로 슬라이드되어야 합니다.

이를 위해서는 pageswap의 경우 곧 발생할 탐색에 관한 정보가 필요하고 pagereveal의 경우 방금 발생한 탐색에 관한 정보가 필요합니다.

이를 위해 브라우저는 이제 동일 출처 탐색에 관한 정보를 보유하는 NavigationActivation 객체를 노출할 수 있습니다. 이 객체는 사용된 탐색 유형, 현재 대상, 최종 대상 기록 항목을 Navigation API의 navigation.entries()에서 찾은 대로 노출합니다.

활성화된 페이지에서 navigation.activation를 통해 이 객체에 액세스할 수 있습니다. pageswap 이벤트에서 e.activation을 통해 이 값에 액세스할 수 있습니다.

pageswappagereveal 이벤트의 NavigationActivation 정보를 사용하여 뷰 전환에 참여해야 하는 요소에 view-transition-name 값을 설정하는 이 프로필 데모를 확인하세요.

이렇게 하면 목록의 모든 항목을 미리 view-transition-name로 장식하지 않아도 됩니다. 대신 JavaScript를 사용하여 필요한 요소에만 적시에 발생합니다.

프로필 데모 녹화. Chrome 126 이상이 필요합니다.

코드는 다음과 같습니다.

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

렌더링 차단으로 콘텐츠가 로드될 때까지 기다리기

Browser Support

  • Chrome: 124.
  • Edge: 124.
  • Firefox: not supported.
  • Safari: not supported.

경우에 따라 특정 요소가 새 DOM에 표시될 때까지 페이지의 첫 번째 렌더링을 지연할 수 있습니다. 이렇게 하면 깜박임을 방지하고 애니메이션을 적용할 상태가 안정적인지 확인할 수 있습니다.

<head>에서 다음 메타 태그를 사용하여 페이지가 처음 렌더링되기 전에 있어야 하는 하나 이상의 요소 ID를 정의합니다.

<link rel="expect" blocking="render" href="#section1">

이 메타 태그는 콘텐츠를 로드해야 한다는 의미가 아니라 요소가 DOM에 있어야 한다는 의미입니다. 예를 들어 이미지의 경우 DOM 트리에서 지정된 id이 있는 <img> 태그가 있기만 하면 조건이 true로 평가됩니다. 이미지가 아직 로드 중일 수 있습니다.

렌더링 차단에 전념하기 전에 점진적 렌더링이 웹의 기본 측면이므로 렌더링 차단을 선택할 때 주의해야 합니다. 렌더링 차단의 영향은 사례별로 평가해야 합니다. 기본적으로 코어 웹 바이탈에 미치는 영향을 측정하여 사용자에게 미치는 영향을 적극적으로 측정하고 평가할 수 있는 경우가 아니라면 blocking=render를 사용하지 마세요.


교차 문서 보기 전환에서 전환 유형 보기

교차 문서 뷰 전환은 뷰 전환 유형도 지원하여 애니메이션과 캡처되는 요소를 맞춤설정할 수 있습니다.

예를 들어 페이지로 나누기에서 다음 페이지나 이전 페이지로 이동할 때 시퀀스에서 더 높은 페이지로 이동하는지 더 낮은 페이지로 이동하는지에 따라 다른 애니메이션을 사용할 수 있습니다.

이러한 유형을 미리 설정하려면 @view-transition at-rule에 유형을 추가하세요.

@view-transition {
  navigation: auto;
  types: slide, forwards;
}

즉석에서 유형을 설정하려면 pageswappagereveal 이벤트를 사용하여 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와 같은 기능과 잘 작동합니다.

데모

다음 페이지로 나누기 데모에서 페이지 콘텐츠는 탐색하는 페이지 번호에 따라 앞뒤로 슬라이드됩니다.

페이지로 나누기 데모 (MPA) 녹화본 이전 페이지에 따라 다른 전환을 사용합니다.

사용할 전환 유형은 pagerevealpageswap 이벤트에서 to 및 from 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 버그를 신고하세요.