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

두 개의 다른 문서 간에 보기 전환이 발생하는 경우 이를 문서 간 보기 전환이라고 합니다. 일반적으로 다중 페이지 애플리케이션 (MPA)에서 이러한 상황이 발생합니다. 문서 간 뷰 전환은 Chrome 126부터 Chrome에서 지원됩니다.

브라우저 지원

  • Chrome: 126 <ph type="x-smartling-placeholder">
  • Edge: 126. <ph type="x-smartling-placeholder">
  • Firefox: 지원되지 않음 <ph type="x-smartling-placeholder">
  • Safari: 지원되지 않음 <ph type="x-smartling-placeholder">

문서 간 뷰 전환은 동일한 문서 뷰 전환과 매우 동일한 빌딩 블록과 원칙에 의존합니다. 이는 매우 의도적인 전환입니다.

  1. 브라우저는 이전 페이지와 새 페이지 모두에서 고유한 view-transition-name가 있는 요소의 스냅샷을 찍습니다.
  2. 렌더링이 억제되는 동안 DOM이 업데이트됩니다.
  3. 마지막으로, 전환은 CSS 애니메이션에 의해 구동됩니다.
를 통해 개인정보처리방침을 정의할 수 있습니다.

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

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

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

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


문서 간 뷰 전환이 동일 출처 탐색으로 제한됩니다.

문서 간 뷰 전환은 동일 출처 탐색으로만 제한됩니다. 참여 중인 두 페이지의 출처가 동일한 경우 탐색은 동일한 출처로 간주됩니다.

페이지 출처는 web.dev에 자세히 설명된 것처럼 사용된 스키마, 호스트 이름, 포트의 조합입니다.

<ph type="x-smartling-placeholder">
</ph> 스키마, 호스트 이름, 포트가 강조표시된 예시 URL 이러한 요소가 결합되어 원천을 형성합니다.
스키마, 호스트 이름, 포트가 강조표시된 URL의 예시 종합적으로는 원천을 형성합니다.

예를 들어 developer.chrome.com에서 developer.chrome.com/blog로 이동할 때 동일한 출처이므로 문서 간 뷰 전환이 발생할 수 있습니다. developer.chrome.com에서 www.chrome.com로 이동할 때는 교차 출처 및 동일 사이트이므로 이러한 전환을 사용할 수 없습니다.


문서 간 뷰 전환 선택

두 문서 간에 문서 간 보기 전환이 이루어지도록 하려면 두 참여 페이지 모두 이 전환을 허용하도록 선택해야 합니다. 이 작업은 CSS에서 @view-transition at-rule을 사용하여 수행됩니다.

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

@view-transition {
  navigation: auto;
}

navigation 설명어를 auto로 설정하면 다음 NavigationType에서 뷰 전환이 발생하도록 허용하게 됩니다.

  • traverse
  • push 또는 replace: 사용자가 브라우저 UI 메커니즘을 통해 활성화를 시작하지 않은 경우

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

탐색이 너무 오래 걸리는 경우(Chrome의 경우 4초 이상) TimeoutError DOMException으로 뷰 전환을 건너뜁니다.

문서 간 뷰 전환 데모

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

<ph type="x-smartling-placeholder">
</ph>
Stack Navigator 데모 녹화 Chrome 126 이상이 필요합니다.
를 통해 개인정보처리방침을 정의할 수 있습니다.

문서 간 뷰 전환 맞춤설정

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

이러한 기능은 View Transition API 사양 자체의 일부는 아니지만, 함께 사용하도록 설계되었습니다.

pageswappagereveal 이벤트

브라우저 지원

  • Chrome: 124 <ph type="x-smartling-placeholder">
  • Edge: 124. <ph type="x-smartling-placeholder">
  • Firefox: 지원되지 않음 <ph type="x-smartling-placeholder">
  • Safari: 지원되지 않음 <ph type="x-smartling-placeholder">

소스

문서 간 뷰 전환을 맞춤설정할 수 있도록 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 프로미스를 사용할 수 있습니다.

브라우저 지원

  • Chrome: 123 <ph type="x-smartling-placeholder">
  • Edge: 123. <ph type="x-smartling-placeholder">
  • Firefox: 지원되지 않음 <ph type="x-smartling-placeholder">
  • Safari: 지원되지 않음 <ph type="x-smartling-placeholder">

소스

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

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

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

이렇게 하려면 pageswap의 경우 발생하려고 하거나 pagereveal의 경우 방금 발생한 탐색에 관한 정보가 필요합니다.

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

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

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

이렇게 하면 view-transition-name를 사용하여 목록의 모든 항목을 데코레이션할 필요가 없습니다. 대신 JavaScript를 사용하여 필요한 요소에서만 적시에 수행됩니다.

<ph type="x-smartling-placeholder">
</ph>
프로필 데모 녹화 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);
    }
  }
});

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

브라우저 지원

  • Chrome: 124 <ph type="x-smartling-placeholder">
  • Edge: 124. <ph type="x-smartling-placeholder">
  • Firefox: 지원되지 않음 <ph type="x-smartling-placeholder">
  • Safari: 지원되지 않음 <ph type="x-smartling-placeholder">

경우에 따라 새 DOM에 특정 요소가 나타날 때까지 페이지의 첫 번째 렌더링을 보류할 수 있습니다. 이렇게 하면 플래시를 방지하고 애니메이션을 적용할 상태가 안정적인지 확인할 수 있습니다.

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

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

이 메타 태그는 콘텐츠가 로드되어야 하는 것이 아니라 요소가 DOM에 있어야 함을 의미합니다. 예를 들어 이미지의 경우 DOM 트리에 지정된 id가 있는 <img> 태그만 있어도 조건이 true로 평가될 수 있습니다. 이미지 자체가 아직 로드 중일 수 있습니다.

렌더링 차단에 집중하기 전에 증분 렌더링이 웹의 기본적인 요소이므로 렌더링 차단을 선택할 때 주의해야 합니다. 렌더링 차단의 영향은 사례별로 평가해야 합니다. Core Web Vitals에 미치는 영향을 측정하여 사용자에게 미치는 영향을 적극적으로 측정하고 측정할 수 없다면 기본적으로 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;
  }
}

유형은 Active View 전환에만 적용되므로 뷰 전환이 완료되면 유형이 자동으로 정리됩니다. 따라서 유형은 BFCache와 같은 기능과 원활하게 작동합니다.

데모

다음 페이지로 나누기 데모에서는 탐색 중인 페이지 번호에 따라 페이지 콘텐츠가 앞뒤로 움직입니다.

<ph type="x-smartling-placeholder">
</ph>
페이지로 나누기 데모 (MPA) 녹화 이동하려는 페이지에 따라 다른 전환을 사용합니다.

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