انتقال نمای بین اسناد برای برنامه های کاربردی چند صفحه ای

هنگامی که یک انتقال دید بین دو سند مختلف اتفاق می افتد، به آن انتقال نمای متقاطع می گویند. این معمولاً در برنامه های چند صفحه ای (MPA) وجود دارد. انتقال‌های نمای متقابل اسناد در Chrome از Chrome 126 پشتیبانی می‌شوند.

پشتیبانی مرورگر

  • کروم: 126.
  • لبه: 126.
  • فایرفاکس: پشتیبانی نمی شود.
  • پیش نمایش فناوری سافاری: پشتیبانی می شود.

انتقال‌های نمای متقاطع بر اساس همان بلوک‌های ساختمانی و اصولی مانند انتقال‌های نمای همان سند است که بسیار عمدی است:

  1. مرورگر از عناصری که دارای یک view-transition-name منحصر به فرد در هر دو صفحه قدیمی و جدید هستند، عکس فوری می گیرد.
  2. در حالی که رندر متوقف می شود، DOM به روز می شود.
  3. و در نهایت، انتقال ها توسط انیمیشن های CSS طراحی شده اند.

چیزی که در مقایسه با انتقال نمای سند مشابه متفاوت است، این است که با انتقال نمای متقابل اسناد، برای شروع انتقال دید، نیازی به فراخوانی document.startViewTransition ندارید. درعوض، محرک انتقال نمای متقابل اسناد، پیمایش با مبدأ یکسان از یک صفحه به صفحه دیگر است، عملی که معمولاً توسط کاربر وب‌سایت شما با کلیک بر روی پیوند انجام می‌شود.

به عبارت دیگر، هیچ API برای فراخوانی برای شروع انتقال view بین دو سند وجود ندارد. با این حال، دو شرط وجود دارد که باید رعایت شود:

  • هر دو سند باید در یک مبدا وجود داشته باشند.
  • هر دو صفحه باید شرکت کنند تا امکان انتقال نمایش فراهم شود.

هر دو این شرایط بعداً در این سند توضیح داده شده است.


انتقال‌های نمای متقابل اسناد به پیمایش‌های با مبدا یکسان محدود می‌شود

انتقال‌های نمای متقابل اسناد فقط به پیمایش‌های همان مبدأ محدود می‌شود. اگر مبدأ هر دو صفحه شرکت کننده یکسان باشد، ناوبری به عنوان یک منبع در نظر گرفته می شود.

منشا یک صفحه ترکیبی از طرح مورد استفاده، نام میزبان و پورت است که در web.dev توضیح داده شده است .

یک URL مثال با طرح، نام میزبان و پورت برجسته شده است. با هم، آنها منشاء را تشکیل می دهند.
یک URL مثال با طرح، نام میزبان و پورت برجسته شده است. با هم، آنها منشاء را تشکیل می دهند.

به‌عنوان مثال، می‌توانید هنگام پیمایش از developer.chrome.com به developer.chrome.com/blog ، یک انتقال نمای متقابل اسناد داشته باشید، زیرا آن‌ها یک منبع هستند. هنگام پیمایش از developer.chrome.com به www.chrome.com نمی‌توانید این انتقال را داشته باشید، زیرا این موارد دارای منبع متقاطع و یک سایت هستند.


انتقال‌های نمای متقابل اسناد انتخابی هستند

برای داشتن یک انتقال نمای متقاطع بین دو سند، هر دو صفحه شرکت‌کننده باید این امکان را انتخاب کنند. این کار با @view-transition at-rule در CSS انجام می شود.

در @view-transition at-rule، توصیفگر navigation را auto تنظیم کنید تا انتقال‌های مشاهده برای پیمایش‌های اسناد متقاطع و با مبدا یکسان فعال شود.

@view-transition {
  navigation: auto;
}

با تنظیم توصیفگر navigation بر روی auto ، اجازه می‌دهید که انتقال مشاهده برای NavigationType‌ های زیر انجام شود:

  • traverse
  • push یا replace ، اگر فعال سازی توسط کاربر از طریق مکانیسم های UI مرورگر آغاز نشده باشد.

پیمایش‌هایی که از auto حذف می‌شوند، برای مثال، پیمایش با استفاده از نوار آدرس URL یا کلیک کردن روی یک نشانک، و همچنین هر شکلی از بارگیری مجدد توسط کاربر یا اسکریپت است.

اگر پیمایش بیش از حد طول بکشد - در مورد کروم بیش از چهار ثانیه - با یک TimeoutError DOMException از انتقال view صرفنظر می شود.

نسخه ی نمایشی انتقال نمای متقابل

نسخه ی نمایشی زیر را بررسی کنید که از انتقال نمایش برای ایجاد نسخه نمایشی Stack Navigator استفاده می کند. در اینجا هیچ تماسی برای document.startViewTransition() وجود ندارد، انتقال view با پیمایش از یک صفحه به صفحه دیگر آغاز می شود.

ضبط نسخه ی نمایشی Stack Navigator . به Chrome 126+ نیاز دارد.

انتقال نمای متقابل اسناد را سفارشی کنید

برای سفارشی کردن انتقال نمای متقابل اسناد، برخی از ویژگی های پلت فرم وب وجود دارد که می توانید از آنها استفاده کنید.

این ویژگی‌ها بخشی از مشخصات View Transition API نیستند، اما برای استفاده در ارتباط با آن طراحی شده‌اند.

رویدادهای pageswap و pagereveal

پشتیبانی مرورگر

  • کروم: 124.
  • لبه: 124.
  • فایرفاکس: پشتیبانی نمی شود.
  • سافاری: پشتیبانی نمی شود.

منبع

برای اینکه بتوانید انتقال‌های نمای متقابل اسناد را سفارشی کنید، مشخصات HTML شامل دو رویداد جدید است که می‌توانید استفاده کنید: pageswap و pagereveal .

این دو رویداد برای هر پیمایش اسناد متقاطع با منشأ یکسان فعال می شوند، صرف نظر از اینکه انتقال دیدگاه در شرف وقوع است یا خیر. اگر قرار است یک انتقال view بین دو صفحه اتفاق بیفتد، می توانید با استفاده از ویژگی viewTransition در این رویدادها به شی ViewTransition دسترسی پیدا کنید.

  • رویداد pageswap قبل از رندر شدن آخرین فریم صفحه فعال می شود. می‌توانید از این برای انجام برخی تغییرات لحظه آخری در صفحه خروجی، درست قبل از گرفتن عکس‌های فوری قدیمی استفاده کنید.
  • رویداد pagereveal پس از شروع اولیه یا فعال‌سازی مجدد صفحه، اما قبل از اولین فرصت رندر، روی صفحه فعال می‌شود. با آن، می‌توانید صفحه جدید را قبل از گرفتن عکس‌های فوری سفارشی کنید.

به عنوان مثال، می‌توانید از این رویدادها برای تنظیم یا تغییر سریع برخی از مقادیر view-transition-name یا انتقال داده‌ها از یک سند به سند دیگر با نوشتن و خواندن داده‌ها از sessionStorage استفاده کنید تا انتقال view را قبل از اجرای واقعی آن سفارشی کنید.

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

شی ViewTransition در pageswap و pagereveal دو شی متفاوت هستند. آنها همچنین با وعده های مختلف به طور متفاوتی رفتار می کنند:

  • pageswap : هنگامی که سند مخفی می شود، شیء قدیمی ViewTransition حذف می شود. وقتی این اتفاق می افتد، viewTransition.ready رد می کند و viewTransition.finished حل می شود.
  • pagereveal : وعده updateCallBack قبلاً در این مرحله حل شده است. می توانید از وعده های viewTransition.ready و viewTransition.finished استفاده کنید.

پشتیبانی مرورگر

  • کروم: 123.
  • لبه: 123.
  • فایرفاکس: پشتیبانی نمی شود.
  • سافاری: پشتیبانی نمی شود.

منبع

در رویدادهای pageswap و pagereveal ، می‌توانید بر اساس URLهای صفحات قدیمی و جدید نیز اقدام کنید.

به عنوان مثال، در MPA Stack Navigator نوع انیمیشن مورد استفاده به مسیر پیمایش بستگی دارد:

  • هنگام پیمایش از صفحه نمای کلی به یک صفحه جزئیات، محتوای جدید باید از سمت راست به چپ وارد شود.
  • هنگام پیمایش از صفحه جزئیات به صفحه نمای کلی، محتوای قدیمی باید از چپ به راست خارج شود.

برای انجام این کار به اطلاعاتی در مورد پیمایشی نیاز دارید که در مورد pageswap ، در شرف وقوع است یا در مورد pagereveal تازه اتفاق افتاده است.

برای این کار، مرورگرها اکنون می توانند اشیاء NavigationActivation را که اطلاعات مربوط به ناوبری با مبدأ یکسان را در خود نگه می دارند، نشان دهند. این شیء نوع ناوبری استفاده شده، ورودی های تاریخچه مقصد فعلی و نهایی را همانطور که در navigation.entries() از Navigation API یافت می شود، نشان می دهد.

در یک صفحه فعال شده، می توانید از طریق navigation.activation به این شی دسترسی داشته باشید. در رویداد pageswap ، می‌توانید از طریق e.activation به آن دسترسی داشته باشید.

این نسخه ی نمایشی Profiles را بررسی کنید که از اطلاعات NavigationActivation در رویدادهای pageswap و pagereveal استفاده می کند تا مقادیر view-transition-name روی عناصری که باید در انتقال view شرکت کنند، تنظیم کند.

به این ترتیب، شما مجبور نیستید تک تک آیتم های لیست را با یک view-transition-name در جلو تزئین کنید. در عوض، این کار به‌موقع با استفاده از جاوا اسکریپت اتفاق می‌افتد، فقط در عناصری که به آن نیاز دارند.

ضبط نسخه ی نمایشی پروفایل ها . به 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 بعد از خود پاک می شود. به این ترتیب صفحه برای پیمایش های متوالی آماده است و همچنین می تواند پیمایش تاریخ را انجام دهد.

برای کمک به این کار، از این تابع ابزار استفاده کنید که به طور موقت 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);
    }
  }
});

صبر کنید تا محتوا با مسدود کردن رندر بارگیری شود

پشتیبانی مرورگر

  • کروم: 124.
  • لبه: 124.
  • فایرفاکس: پشتیبانی نمی شود.
  • سافاری: پشتیبانی نمی شود.

در برخی موارد، ممکن است بخواهید اولین رندر یک صفحه را تا زمانی که عنصر خاصی در DOM جدید وجود داشته باشد متوقف کنید. این کار از چشمک زدن جلوگیری می کند و از پایداری حالتی که در آن متحرک هستید اطمینان حاصل می کند.

در <head> ، با استفاده از متا تگ زیر، یک یا چند شناسه عنصر را تعریف کنید که باید قبل از دریافت اولین رندر صفحه وجود داشته باشند.

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

این متا تگ به این معنی است که عنصر باید در DOM وجود داشته باشد، نه اینکه محتوا باید بارگذاری شود. برای مثال در مورد تصاویر، صرف وجود تگ <img> با id مشخص شده در درخت DOM کافی است تا شرط به درستی ارزیابی شود. ممکن است خود تصویر همچنان در حال بارگیری باشد.

قبل از اینکه همه چیز را در مورد مسدود کردن رندر انجام دهید، آگاه باشید که رندر افزایشی یک جنبه اساسی وب است، بنابراین در انتخاب مسدود کردن رندر محتاط باشید. تاثیر مسدود کردن رندر باید به صورت موردی ارزیابی شود. به طور پیش فرض، از استفاده blocking=render خودداری کنید، مگر اینکه بتوانید به طور فعال تأثیر آن را بر روی کاربران خود با اندازه گیری تأثیر بر Core Web Vitals خود اندازه گیری و اندازه گیری کنید.


مشاهده انواع انتقال در انتقال های نمای متقابل اسناد

انتقال‌های نمای متقابل اسناد نیز از انواع انتقال نمایش برای سفارشی کردن انیمیشن‌ها و اینکه کدام عناصر گرفته می‌شوند، پشتیبانی می‌کنند.

برای مثال، هنگام رفتن به صفحه بعدی یا قبلی در یک صفحه بندی، ممکن است بخواهید از انیمیشن های مختلف بسته به اینکه به صفحه بالاتر یا صفحه پایین تر از دنباله می روید استفاده کنید.

برای تنظیم این انواع از قبل، انواع را در @view-transition at-rule اضافه کنید:

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

برای تنظیم انواع در پرواز، از رویدادهای pageswap و pagereveal برای دستکاری مقدار 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) . بسته به اینکه به کدام صفحه می روید از انتقال های مختلفی استفاده می کند.

نوع انتقال برای استفاده در رویدادهای pagereveal و pageswap با نگاه کردن به 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';
  }
};

بازخورد

بازخورد توسعه دهندگان همیشه قدردانی می شود. برای اشتراک‌گذاری، یک مشکل را با پیشنهادات و سوالات با کارگروه CSS در GitHub ثبت کنید . پیشوند مشکل خود را با [css-view-transitions] وارد کنید. اگر با یک باگ مواجه شدید، به جای آن یک باگ Chromium را ثبت کنید .