Przejścia widoku między dokumentami w aplikacjach wielostronicowych

Przejście między widokami w 2 różnych dokumentach nazywa się przejściem między widokami dokumentów. Zwykle tak się dzieje w przypadku aplikacji wielostronicowych. Przejścia między widokami dokumentów są obsługiwane w Chrome od wersji 126.

Browser Support

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

Source

Przejścia między widokami w różnych dokumentach są oparte na tych samych elementach i zasadach co przejścia między widokami w tym samym dokumencie:

  1. Przeglądarka wykonuje migawki elementów, które mają unikalne wartości view-transition-name zarówno na starej, jak i nowej stronie.
  2. DOM jest aktualizowany, gdy renderowanie jest blokowane.
  3. I na koniec przejścia są obsługiwane przez animacje CSS.

Różnica w stosunku do przejść widoku w tym samym dokumencie polega na tym, że w przypadku przejść widoku między dokumentami nie trzeba wywoływać funkcji document.startViewTransition, aby rozpocząć przejście widoku. Zamiast tego przejście między widokami w różnych dokumentach jest wywoływane przez nawigację w ramach tego samego źródła z jednej strony na drugą. Jest to działanie, które użytkownik Twojej witryny wykonuje, klikając link.

Innymi słowy, nie ma interfejsu API, który można wywołać, aby rozpocząć przejście między widokami 2 dokumentów. Musisz jednak spełnić 2 warunki:

  • Oba dokumenty muszą pochodzić z tego samego źródła.
  • Aby umożliwić przejście do widoku, obie strony muszą wyrazić na to zgodę.

Oba te warunki są opisane w dalszej części tego dokumentu.


Przejścia między widokami dokumentów są ograniczone do nawigacji w ramach tej samej domeny.

Przejścia między widokami dokumentów są ograniczone tylko do przekierowań w ramach tej samej domeny. Nawigacja jest uznawana za pochodzącą z tej samej domeny, jeśli domena obu stron jest taka sama.

Źródło strony to kombinacja użytego schematu, nazwy hosta i portu, jak opisano na stronie web.dev.

Przykład adresu URL z wyróżnionym schematem, nazwą hosta i portem W połączeniu tworzą one źródło.
Przykład adresu URL ze schematem, nazwą hosta i portem wyróżnionymi na żółto. W połączeniu tworzą one źródło.

Możesz na przykład przełączać widoki w dokumentach podczas przechodzenia z developer.chrome.com do developer.chrome.com/blog, ponieważ te dokumenty mają ten sam element źródła. Nie możesz przejść z poziomu developer.chrome.com do www.chrome.com, ponieważ są to różne źródła i ta sama strona.


Przejścia między dokumentami wymagają zgody

Aby umożliwić przejście między dokumentami, obie strony muszą wyrazić na to zgodę. Służy do tego reguła at @view-transition w CSS.

W atrybucie @view-transition ustaw wartość navigation na auto, aby umożliwić przejścia między widokami w przypadku nawigacji w ramach tej samej domeny.

@view-transition {
  navigation: auto;
}

Ustawiając opis navigation na auto, zezwalasz na przejścia między widokami w przypadku tych typów NavigationType:

  • traverse
  • push lub replace, jeśli aktywacja nie została zainicjowana przez użytkownika za pomocą mechanizmów interfejsu przeglądarki.

Nawigacje wykluczone z użycia w przypadku auto to na przykład nawigacja za pomocą paska adresu URL lub kliknięcie zakładki, a także każda forma ponownego wczytania zainicjowana przez użytkownika lub skrypt.

Jeśli nawigacja trwa zbyt długo (w przypadku Chrome dłużej niż 4 sekundy), przejście między widokami jest pomijane i zastępowane przez TimeoutError DOMException.

Demo przejść między widokami w dokumentach

Zapoznaj się z tym demonstracyjnym filmem, w którym przejścia między widokami służą do tworzenia demonstracji nawigacji w Stack Navigatorze. Nie ma tu wywołań funkcji document.startViewTransition(), a przejścia między widokami są wywoływane przez przechodzenie z jednej strony na drugą.

Nagranie prezentacji Stack Navigator. Wymaga Chrome w wersji 126 lub nowszej.

Dostosowywanie przejść między widokami dokumentów

Aby dostosować przejścia między widokami w różnych dokumentach, możesz użyć niektórych funkcji platformy internetowej.

Te funkcje nie są częścią specyfikacji interfejsu ViewTransition API, ale są przeznaczone do użytku w połączeniu z nim.

Zdarzenia pageswappagereveal

Browser Support

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

Source

Aby umożliwić dostosowywanie przejść między widokami dokumentów, specyfikacja HTML zawiera 2 nowe zdarzenia, których możesz używać: pageswappagereveal.

Te 2 zdarzenia są wywoływane w przypadku każdej nawigacji między dokumentami w ramach tego samego źródła niezależnie od tego, czy ma nastąpić przejście do widoku. Jeśli przejście między widokami ma nastąpić na 2 stronach, możesz uzyskać dostęp do obiektu ViewTransition, używając właściwości viewTransition w przypadku tych zdarzeń.

  • Zdarzenie pageswap jest wywoływane przed wyświetleniem ostatniego obrazu strony. Możesz z niego skorzystać, aby w ostatniej chwili wprowadzić zmiany na stronie wychodzącej tuż przed utworzeniem starych zrzutów ekranu.
  • Zdarzenie pagereveal jest wywoływane na stronie po jej zainicjowaniu lub ponownym zainicjowaniu, ale przed pierwszą możliwością renderowania. Dzięki temu możesz dostosować nową stronę przed utworzeniem nowych migawek.

Możesz na przykład używać tych zdarzeń, aby szybko ustawiać lub zmieniać niektóre wartości view-transition-name lub przekazywać dane z jednego dokumentu do drugiego przez zapisywanie i odczytywanie danych z sessionStorage, aby dostosować przejście między widokami przed jego wykonaniem.

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

Jeśli chcesz, możesz pominąć przejście w obu zdarzeniach.

window.addEventListener("pagereveal", async (e) => {
  if (e.viewTransition) {
    if (goodReasonToSkipTheViewTransition()) {
      e.viewTransition.skipTransition();
    }
  }
}

Obiekt ViewTransition w obiektach pageswappagereveal to 2 różne obiekty. Różne obietnice są też traktowane inaczej:

  • pageswap: gdy dokument jest ukryty, stary obiekt ViewTransition jest pomijany. W takim przypadku viewTransition.ready odrzuca, a viewTransition.finished rozwiązuje.
  • pagereveal: updateCallBack obietnica została już spełniona. Możesz skorzystać z obietnic viewTransition.readyviewTransition.finished.

Browser Support

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

Source

W przypadku zdarzeń pageswappagereveal możesz też podjąć działanie na podstawie adresów URL starych i nowych stron.

Na przykład w nawigacji zbioru MPA typ animacji zależy od ścieżki nawigacji:

  • Gdy przechodzisz ze strony Przegląd na stronę Szczegóły, nowe treści muszą przesuwać się od prawej do lewej.
  • Gdy przechodzisz ze strony z informacjami na stronę z przeglądem, stara zawartość powinna przesunąć się z poziomu lewego do prawego.

Aby to zrobić, potrzebujesz informacji o nawigacji, która w przypadku pageswap ma się dopiero rozpocząć, a w przypadku pagereveal właśnie się zakończyła.

W tym celu przeglądarki mogą teraz udostępniać obiekty NavigationActivation, które zawierają informacje o nawigacji w ramach tego samego źródła. Obiekt ten udostępnia użyty typ nawigacji, bieżące i ostateczne wpisy historii miejsca docelowego, które można znaleźć w navigation.entries() z interfejsu Navigation API.

Na aktywowanej stronie możesz uzyskać dostęp do tego obiektu za pomocą elementu navigation.activation. W przypadku pageswap możesz uzyskać do niego dostęp za pomocą e.activation.

Obejrzyj demo funkcji Profile, która używa informacji NavigationActivation w zdarzeniach pageswappagereveal do ustawiania wartości view-transition-name w elementach, które muszą uczestniczyć w przechodzeniu między widokami.

Dzięki temu nie musisz ozdabiać każdego elementu na liście za pomocą znaku view-transition-name. Zamiast tego, używając JavaScript, wykonujemy to w samym momencie tylko w przypadku elementów, które tego wymagają.

Nagranie demonstracji profili. Wymaga Chrome w wersji 126 lub nowszej.

Kod wygląda tak:

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

Kod usuwa też wartości view-transition-name po wykonaniu przejścia widoku. W ten sposób strona jest gotowa do kolejnych przejść i może też obsługiwać przechodzenie przez historię.

Aby ułatwić sobie pracę, użyj tej funkcji pomocniczej, która tymczasowo ustawia zmienne 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 = '';
  }
}

Poprzedni kod można uprościć w ten sposób:

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

Czekaj na wczytanie treści z blokowaniem renderowania

Browser Support

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

W niektórych przypadkach możesz chcieć wstrzymać się z pierwszym wyrenderowaniem strony, dopóki w nowym DOM nie pojawi się określony element. Zapobiega to miganiu i zapewnia stabilność stanu, który animujesz.

W sekcji <head> użyj tego metatagu, aby zdefiniować co najmniej 1 identyfikator elementu, który musi być obecny przed pierwszym wyrenderowaniem strony.

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

Ten tag meta oznacza, że element powinien być obecny w DOM, a nie że zawartość powinna być wczytana. Na przykład w przypadku obrazów samo wystąpienie tagu <img> z określonym id w drzewie DOM powoduje, że warunek przyjmuje wartość true (prawda). Obraz może się nadal wczytywać.

Zanim całkowicie zablokujesz renderowanie, pamiętaj, że renderowanie stopniowe jest podstawowym aspektem sieci, dlatego zachowaj ostrożność, gdy decydujesz się na blokowanie renderowania. Wpływ blokowania renderowania należy oceniać indywidualnie w każdej sytuacji. Domyślnie unikaj używania blocking=render, chyba że możesz aktywnie mierzyć i ocenić wpływ tej funkcji na użytkowników, mierząc wpływ na podstawowe wskaźniki internetowe.


Wyświetlanie typów przejść w przejściach między dokumentami

Przejścia między dokumentami obsługują też typy przejść, które umożliwiają dostosowywanie animacji i określanie, które elementy mają być rejestrowane.

Na przykład podczas przechodzenia do następnej lub poprzedniej strony w sekwencji możesz użyć różnych animacji w zależności od tego, czy przechodzisz do strony wyższej czy niższej w sekwencji.

Aby ustawić te typy z wyprzedzeniem, dodaj je w regułach at-rule:@view-transition

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

Aby ustawiać typy w biegu działania, użyj zdarzeń pageswappagereveal do manipulowania wartością parametru 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);
  }
});

Typy nie są automatycznie przenoszone z obiektu ViewTransition na starej stronie do obiektu ViewTransition na nowej stronie. Aby animacje działały zgodnie z oczekiwaniami, musisz określić typy, których chcesz użyć na co najmniej nowej stronie.

Aby odpowiedzieć na te typy, użyj selektora pseudoklasy :active-view-transition-type() w taki sam sposób jak w przypadku przejść w ramach tego samego dokumentu.

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

Ponieważ typy dotyczą tylko aktywnego przejścia widoku, są automatycznie usuwane po zakończeniu przejścia. Dzięki temu typy dobrze współpracują z funkcjami takimi jak BFCache.

Prezentacja

W tym demo podziału na strony zawartość strony przesuwa się do przodu lub do tyłu w zależności od numeru strony, do której się przemieszczasz.

Nagranie demonstracji strony z przewijaniem (MPA). Używa różnych przejść w zależności od tego, na którą stronę się przenosisz.

Typ przejścia do użycia jest określany w zdarzeniach pagerevealpageswap na podstawie adresów URL „do” i „z”.

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';
  }
};

Prześlij opinię

Zawsze chętnie przyjmujemy opinie deweloperów. Aby podzielić się opinią, prześlij zgłoszenie do grupy roboczej ds. usług porównywania cen na GitHub, dodając sugestie i pytania. Dodaj do problemu prefiks [css-view-transitions]. Jeśli napotkasz błąd, zgłoś go w Chromium.