Przejścia widoku między dokumentami w aplikacjach wielostronicowych

Przejście między widokami 2 różnych dokumentów jest nazywane przejściem między dokumentami. Zwykle dzieje się tak w przypadku aplikacji wielostronicowych (MPA). Przejścia widoków między dokumentami są obsługiwane w Chrome od wersji 126.

Obsługa przeglądarek

  • 126
  • 126
  • x
  • x

Źródło

Przejścia widoku między dokumentami opierają się na tych samych elementach składowych i zasadach co przejścia widoków tego samego dokumentu, co jest bardzo celowe:

  1. Przeglądarka wykonuje migawki elementów z unikalnym parametrem view-transition-name zarówno na starej, jak i nowej stronie.
  2. DOM jest aktualizowany, gdy renderowanie jest wstrzymane.
  3. I wreszcie, przejścia są generowane przez animacje CSS.

Od przejścia między widokami tego samego dokumentu różni się to, że w przypadku przejść między dokumentami nie trzeba wywoływać funkcji document.startViewTransition, aby rozpocząć takie przejście. Zamiast tego procesem przenoszenia widoków między dokumentami jest przejście z jednej strony na drugą z tej samej domeny. Działanie to zwykle wykonuje użytkownik witryny, który klika link.

Inaczej mówiąc, nie ma żadnego interfejsu API, który można wywołać w celu rozpoczęcia przejścia między dwoma dokumentami. Musisz jednak spełnić 2 warunki:

  • Oba dokumenty muszą znajdować się w tym samym źródle.
  • Aby można było przełączyć widok, na obu stronach trzeba wyrazić na to zgodę.

Obydwa warunki zostały objaśnione w dalszej części tego dokumentu.


Przejścia widoku między dokumentami są ograniczone do nawigacji w tej samej domenie

Przejścia widoków między dokumentami są ograniczone tylko do nawigacji w tej samej domenie. Nawigacja jest uznawana za tę samą pochodzenie, jeśli pochodzenie obu stron wchodzących w skład jest takie samo.

Pochodzenie strony to połączenie używanego schematu, nazwy hosta i portu, zgodnie z informacjami na stronie web.dev.

Przykładowy adres URL z wyróżnionymi schematem, nazwą hosta i portem. Razem stanowią pochodzenie.
Przykładowy adres URL z wyróżnionymi schematem, nazwą hosta i portem. Razem stanowią pochodzenie.

Na przykład podczas przechodzenia z developer.chrome.com do developer.chrome.com/blog możesz użyć przejścia między widokami danych w różnych dokumentach, ponieważ te źródła są z tego samego źródła. Przejście z developer.chrome.com do www.chrome.com jest niemożliwe, ponieważ są to konwersje z innych domen i z tej samej witryny.


Przejścia widoku między dokumentami są włączone

Aby można było przełączać widok między 2 dokumentami, obie strony muszą wyrazić na to zgodę. Odbywa się to za pomocą reguły @view-transition w kodzie CSS.

W regule @view-transition „at” ustaw deskryptor navigation na wartość auto, aby umożliwić przełączanie widoków w przypadku nawigowania w obrębie tego samego źródła w różnych dokumentach.

@view-transition {
  navigation: auto;
}

Ustawienie deskryptora navigation na auto oznacza, że zezwalasz na przejścia między widokami w przypadku tych elementó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 np. nawigacja za pomocą paska adresu URL lub klikanie zakładki, a także dowolna forma ponownego ładowania zainicjowanego przez użytkownika lub skrypt.

Jeśli nawigacja trwa zbyt długo (w przypadku Chrome przekracza 4 sekundy), przejście widoku jest pomijane w funkcji TimeoutError DOMException.

Wersja demonstracyjna przejścia widoku między dokumentami

Zapoznaj się z poniższą wersją demonstracyjną, która wykorzystuje przejścia widoków, aby utworzyć prezentację Stack Navigator. Brak wywołań funkcji document.startViewTransition(). Przejścia widoku są wywoływane przez przejście z jednej strony na drugą.

Nagranie demonstracji usługi Stack Navigator. Wymaga przeglądarki Chrome 126 lub nowszej.

Dostosowywanie przejść między widokami

Aby dostosować przejścia między widokami danych, możesz użyć kilku funkcji platformy internetowej.

Te funkcje nie wchodzą w skład specyfikacji interfejsu View Transition API, ale są przeznaczone do użytku z nimi.

Zdarzenia pageswap i pagereveal

Obsługa przeglądarek

  • 124
  • 124
  • x
  • x

Źródło

Aby umożliwić dostosowanie przejść między widokami danych, specyfikacja HTML zawiera 2 nowe zdarzenia, których możesz użyć: pageswap i pagereveal.

Te 2 zdarzenia są wywoływane przy każdej nawigacji między dokumentami w tej samej domenie, niezależnie od tego, czy nastąpi przejście w ramach wyświetlenia. Jeśli między 2 stronami nastąpi przejście w widoku danych, możesz uzyskać dostęp do obiektu ViewTransition za pomocą właściwości viewTransition w tych zdarzeniach.

  • Zdarzenie pageswap jest uruchamiane przed wyrenderowaniem ostatniej ramki strony. Pozwala on wprowadzić w ostatniej chwili zmiany na stronie wychodzącej bezpośrednio przed wykonaniem starych zrzutów.
  • Zdarzenie pagereveal uruchamia się na stronie po jej zainicjowaniu lub reaktywacji, ale przed pierwszą możliwością renderowania. Możesz za jej pomocą dostosować nową stronę, zanim zostaną utworzone nowe zrzuty.

Za pomocą tych zdarzeń możesz na przykład szybko ustawić lub zmienić niektóre wartości view-transition-name albo przekazywać dane z jednego dokumentu do innego przez zapisywanie i odczytywanie danych z sessionStorage w celu dostosowania przejścia przed jego uruchomieniem.

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 tych wydarzeniach.

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

Obiekt ViewTransition w pageswap i pagereveal to 2 różne obiekty. Inaczej postępują też z różnymi obietnicami:

  • pageswap: gdy dokument zostanie ukryty, stary obiekt ViewTransition zostanie pominięty. W takim przypadku viewTransition.ready odrzuca wiadomość, a viewTransition.finished zostaje zakończona.
  • pagereveal: zobowiązanie (updateCallBack) zostało już zrealizowane. Możesz skorzystać z obietnic: viewTransition.ready i viewTransition.finished.

Obsługa przeglądarek

  • 123
  • 123
  • x
  • x

Źródło

Zarówno w zdarzeniach pageswap, jak i pagereveal możesz też podejmować działania na podstawie adresów URL starej i nowej strony.

Na przykład w Nawigatorze stosu MPA typ animacji, który ma zostać użyty, zależy od ścieżki nawigacji:

  • Podczas przechodzenia ze strony Przegląd na stronę z informacjami nowa zawartość musi przesunąć się z prawej do lewej strony.
  • Po przejściu ze strony szczegółów na stronę przeglądu stara zawartość musi przesunąć się z lewej strony na prawą.

Aby to zrobić, potrzebujesz informacji o nawigacji, która w przypadku aplikacji pageswap wkrótce się wydarzy lub właśnie tak.pagereveal

W tym celu przeglądarki mogą teraz udostępniać obiekty NavigationActivation, które zawierają informacje o nawigacji w tej samej domenie. Ten obiekt ujawnia używany typ nawigacji, bieżący i końcowy wpis historii miejsc docelowych znaleziony w navigation.entries() z interfejsu API nawigacji.

Na aktywowanej stronie możesz uzyskać dostęp do tego obiektu przez navigation.activation. W wydarzeniu pageswap dostęp do tych ustawień możesz uzyskać, używając aplikacji e.activation.

Zapoznaj się z tą wersją demonstracyjną profili, która wykorzystuje informacje NavigationActivation w zdarzeniach pageswap i pagereveal do ustawiania wartości view-transition-name elementów, które muszą zostać uwzględnione przy przejściu widoku.

Dzięki temu nie musisz dekorować każdego elementu listy view-transition-name z góry. Odbywa się to na bieżąco, w przypadku elementów, które go 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 oczyszcza się po sobie, usuwając wartości view-transition-name po uruchomieniu przejścia widoku. W ten sposób strona jest gotowa do kolejnych nawigacji i umożliwia przeglądanie historii.

Aby ułatwić sobie to zadanie, użyj tej funkcji narzędziowej, która tymczasowo ustawia parametry 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 teraz uprościć w następujący 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);
    }
  }
});

Poczekaj na wczytanie treści z włączonym blokowaniem renderowania

Obsługa przeglądarek

  • 124
  • 124
  • x
  • x

Źródło

W niektórych przypadkach możesz chcieć wstrzymać pierwsze wyrenderowanie strony, dopóki określony element nie znajdzie się w nowym modelu DOM. Dzięki temu unikniesz błysków i upewnisz się, że stan animacji jest stabilny.

W polu <head> określ co najmniej 1 identyfikator elementu, który musi występować przed pierwszym wyrenderowaniem strony, korzystając z tego metatagu.

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

Ten metatag oznacza, że element powinien znajdować się w elemencie DOM, a nie wczytać treść. Na przykład w przypadku obrazów sama obecność tagu <img> z określonym atrybutem id w drzewie DOM wystarczy, aby warunek został uznany za prawdziwy. Nadal może się ładować obraz.

Zanim przejdziemy do wszystkich kwestii związanych z blokowaniem renderowania, pamiętaj, że renderowanie przyrostowe jest podstawowym aspektem internetu, więc ostrożnie korzystaj z blokowania renderowania. Wpływ blokowania renderowania należy oceniać indywidualnie. Domyślnie unikaj używania usługi blocking=render, chyba że możesz aktywnie mierzyć i mierzyć jej wpływ na użytkowników przez pomiar jej wpływu na podstawowe wskaźniki internetowe.


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

Przejścia widoku między dokumentami obsługują też typy przejść widoku, aby dostosowywać animacje i przechwytywane elementy.

Na przykład przejście do następnej lub poprzedniej strony w podziale na strony może Ci się przydać w zależności od tego, czy z sekwencji przejdziesz na wyższą czy na niższą stronę.

Aby ustawić te typy z góry, dodaj je w regule @view-transition:

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

Aby na bieżąco ustawiać typy typów, używaj zdarzeń pageswap i pagereveal do zmiany wartości 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 ze starej strony do obiektu ViewTransition nowej strony. Aby animacje działały zgodnie z oczekiwaniami, musisz określić typy, których chcesz użyć na przynajmniej nowej stronie.

Aby odpowiedzieć na te typy, użyj pseudoklasy :active-view-transition-type() w taki sam sposób jak w przypadku przejść w widoku 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;
  }
}

Typy mają zastosowanie tylko do przejścia w Widoku aktywnym, więc po jego zakończeniu są one automatycznie czyszczone. Z tego powodu typy dobrze współpracują z takimi funkcjami jak BFCache.

Pokaz

W tej prezentacji podziału na strony zawartość strony przesuwa się do przodu lub do tyłu w zależności od numeru strony, którą przeglądasz.

Nagrywanie demonstracji podziału na strony (MPA). Wykorzystuje różne przejścia w zależności od strony, na którą wchodzisz.

Typ przejścia, którego chcesz użyć, jest określany w zdarzeniach pagereveal i pageswap na podstawie adresów URL do i z adresów 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';
  }
};

Prześlij opinię

Opinie deweloperów są dla nas bardzo ważne. Aby udostępnić, zgłoś problem grupie roboczej usługi porównywania cen na GitHubie, podając sugestie i pytania. Dodaj prefiks [css-view-transitions] do problemu. Jeśli natrafisz na błąd, zgłoś błąd Chromium.