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.

Obsługa przeglądarek

  • Chrome: 126.
  • Edge: 126.
  • Firefox: nieobsługiwane.
  • Safari Technology Preview: obsługiwane.

Przejścia między widokami dokumentów korzystają z tych samych elementów i zasad co przejścia między widokami tego samego dokumentu. Jest to celowe działanie:

  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, musisz wyrazić zgodę na wyświetlanie na obu stronach.

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 połączenie 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. 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 dokumentach 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 auto to na przykład nawigacja za pomocą paska adresu URL lub kliknięcie zakładki, a także każda forma odświeżania inicjowana przez użytkownika lub skrypt.

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

Demonstracja przełączania widoków w dokumentach

Zapoznaj się z tym demonstracyjnym filmem, w którym przejścia między widokami służą do tworzenia demonstracji nawigacji w Stacku. Nie ma tu wywołań funkcji document.startViewTransition(), 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 dokumentów, 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

Obsługa przeglądarek

  • Chrome: 124.
  • Edge: 124.
  • Firefox: nieobsługiwane.
  • Safari: nieobsługiwane.

Źródło

Aby umożliwić dostosowywanie przejść między widokami dokumentów, specyfikacja HTML zawiera 2 nowe zdarzenia, których możesz użyć: 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 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 tych zdarzeniach.

  • 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, zapisując i czytając dane 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.

Obsługa przeglądarek

  • Chrome: 123.
  • Edge: 123.
  • Firefox: nieobsługiwane.
  • Safari: nieobsługiwane.

Źródło

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

Na przykład w Nawigatorze architektury 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 stronie aktywowanej 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, aby stosować JavaScript, używamy go 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 sam się też oczyszcza, usuwając wartości view-transition-name po przejściu 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

Obsługa przeglądarek

  • Chrome: 124.
  • Edge: 124.
  • Firefox: nieobsługiwane.
  • Safari: nieobsługiwane.

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 tagu <head> za pomocą tego metatagu zdefiniuj 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 znajdować się w DOM, a nie że zawartość powinna być wczytana. Na przykład w przypadku obrazów wystarczy, że w drzewie DOM znajdzie się tag <img> z określonym atrybutem id, aby warunek zwrócił 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.