انتقال‌های سند یکسان

ویژگی‌های وب: مشاهده انتقال‌ها

مشکل

وب‌سایت‌ها اغلب نماهای متعددی از یک شیء ارائه می‌دهند، برای مثال فهرستی از محصولات، و سپس صفحه جزئیات برای هر محصول. پیمایش بین دو نما اغلب حس قطع ارتباط را القا می‌کند. وقتی کاربر روی ریزعکس محصولی کلیک می‌کند تا جزئیات آن را ببیند، ریزعکس ناپدید می‌شود و تصویر جدید و بزرگ‌تری فوراً در جای دیگری از صفحه ظاهر می‌شود. این عدم تداوم، ردیابی روابط بین عناصر را برای کاربران دشوارتر می‌کند.

راه‌حل

View Transitions API به شما امکان می‌دهد جفت‌های عنصری را که در حالت‌های مختلف قبل و بعداز گذار وجود دارند مشخص کنید. هنگام راه‌اندازی انتقال با document.startViewTransition() در «پیمایش تک‌صفحه‌ای» (SPA)، مرورگر این عناصر هم‌رسانی‌شده را با view-transition-name یکتای هم‌رسانی‌شده‌شان شناسایی می‌کند. سپس به‌طور خودکار تفاوت در موقعیت، اندازه، و سبک آن‌ها را محاسبه می‌کند و آن‌ها را به‌طور روان از حالت قدیمی به حالت جدید پویانمایی می‌کند. این انتقال در لایه بالایی رخ می‌دهد، حتی بالاتر از عناصری با مقادیر z-index بالا.

راهنمای پیاده‌سازی

مرحله ۱: پیچیدن تغییرات وضعیت در startViewTransition

برای «برنامه‌های تک‌صفحه‌ای» (SPA) یا تغییرات وضعیت ساده، منطقی را که DOM را به‌روز می‌کند در document.startViewTransition قرار دهید. مرورگر از وضعیت فعلی عکس آنی می‌گیرد، به‌روزرسانی را اجرا می‌کند، و سپس از وضعیت جدید عکس آنی می‌گیرد.

function navigate(view) {
  // MANDATORY: Wrap the update in startViewTransition
  document.startViewTransition(() => updateDOM(view));
}

مرحله ۲: اختصاص دادن نام‌های انتقال مشترک

از ویژگی CSS view-transition-name استفاده کنید تا به مرورگر بگویید کدام عناصر باید دگرگون شوند. نام می‌تواند هر چیزی باشد (به‌جز none). اجباری: نباید بیشتر از ۱ عنصر قبل و بعداز view-transition-name داده‌شده وجود داشته باشد. اگر ۲ عنصر یا بیشتر با view-transition-name داده‌شده وجود داشته باشد، DOM بلافاصله و بدون انتقال به حالت جدید به‌روز می‌شود.

می‌توانید از چند view-transition-name برای دگردیسی چند جفت عنصر استفاده کنید. برای مثال، ممکن است بخواهید هم تصویر محصول و هم عنوان را با انتقال‌های جداگانه انتقال دهید.

چون چند مورد در نمای فهرستی وجود دارد، نمی‌توانید به همه آن‌ها view-transition-name یکسانی بدهید. این مشکل را می‌توان به دو روش در «برنامه تک‌صفحه‌ای» حل کرد.

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

    /* In the list view, give each */
    #product-1 { view-transition-name: p1 }
    #product-2 { view-transition-name: p2 }
    #product-3 { view-transition-name: p3 }
    
    function updateDOM(clickedTransitionName){
      const hero = document.getElementById("hero");
      hero.style.viewTransitionName = clickedTransitionName;
    }
    
  2. مورد فهرست پویا: به عنصر موجود در صفحه جزئیات یک view-transition-name اختصاص دهید و آن نام را به مورد موجود در صفحه فهرست وقتی انتخاب می‌شود اعمال کنید. هنگام بازگشت به صفحه فهرست، view-transition-name را از مورد موجود در صفحه فهرست بردارید.

عنصر #hero در صفحه جزئیات و عنصر .thumbnail انتخاب‌شده در صفحه فهرست، view-transition-name مشترکی دارند.

#hero{
  view-transition-name: hero;
}
.thumbnail.selected {
  view-transition-name: hero;
}

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

سپس می‌توانید document.startViewTransition() را فراخوانی کنید و تغییرات را اعمال کنید تا صفحه از نمای جزئیات به نمای فهرستی منتقل شود.

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

// Function called when a thumbnail is clicked
function goFromListToDetail(e){
  e.currentTarget.classList.add("selected");
  const hero = document.getElementById("hero");
  const bgColor = getComputedStyle(e.currentTarget).backgroundColor;
  hero.style.background = bgColor;

  // Trigger the transition, checking for support
  if (!document.startViewTransition) {
    document.body.classList.add("detail");
    // MANDATORY Accessibility Routing: Route focus to the newly revealed heading to announce context and preserve logical tab flow
    document.getElementById("detail-heading")?.focus();
    return; // MANDATORY: End function execution if view transitions are not supported.  
  }
  const transition = document.startViewTransition(() => {
    document.body.classList.add("detail");
  });
  // MANDATORY Accessibility Routing: Route focus after the view transition resolves
  transition.finished.finally(() => {
    document.getElementById("detail-heading")?.focus();
  });
}

// Function called when navigating from detail back to list view
function goFromDetailToList() {
  if (!document.startViewTransition) {
    document.body.classList.remove("detail");
    document.getElementById("list-heading")?.focus();
    return;
  }
  const transition = document.startViewTransition(() => {
    document.body.classList.remove("detail");
  });
  // Clean up the list view and route focus
  transition.finished.finally(() => {
    // Route focus back to list view
    document.getElementById("list-heading")?.focus();
    // Remove selected classList to remove view-transition-names
    document.querySelectorAll(".selected").forEach(
      (element) => {
        element.classList.remove("selected");
      },
    );
  });
}

روشی که انتخاب می‌کنید به مورد استفاده بستگی دارد. مورد فهرست پویا به CSS تکراری کمتر، اما پاک‌سازی دستی جاوا اسکریپت بیشتری نیاز دارد.

مرحله ۳: رفع مشکل «کشیدگی» نسبت ابعادی

به‌طور پیش‌فرض، مرورگر عکس‌های آنی قدیمی و جدید را در گروهی که برای جا دادن هر دو کشیده می‌شود، درهم محو می‌کند. اگر درحال انتقال نوشتار هستید، پهنای عنصر نوشتاری را در هر دو نمای قدیمی و جدید روی fit-content تنظیم کنید تا نسبت ابعادی عنصر انتقال‌یافته ثابت باشد.

#list-page .title {
  width: fit-content;
}

#detail-page #title {
  width: fit-content;
}

اگر درحال انتقال عناصری هستید که نسبت ابعادی را تغییر می‌دهند، ممکن است لازم باشد ارتفاع شبه‌عناصر قدیمی و جدید را روی ۱۰۰٪ شبه‌عنصر ::view-transition-pair() تنظیم کنید.

::view-transition-old(hero),
::view-transition-new(hero){
  height: 100%;
}

شبه‌عنصرها عکس‌های آنی از عناصر زنده هستند، بنابراین می‌توانید از اعلان‌های object-fit و object-position نیز برای کنترل بیشتر بر جلوه انتقال استفاده کنید.

روال‌های مطلوب

  • نباید انتقال‌های زیادی را مشخص کنید. از عناصر هم‌رسانی‌شده فقط برای محتوای اصلی که کاربر به‌طور فعال دنبال می‌کند (مانند تصاویر اصلی، عنوان‌ها) استفاده کنید.
  • باید مقادیر موقت view-transition-name را پس‌از پایان گذار بردارید تا از عوارض جانبی در گذارهای آینده جلوگیری شود.
  • عناصر دارای پویانمایی فعال را انتقال ندهید. گذار نمایشی روی عکس‌های آنی عمل می‌کند، بنابراین هر پویانمایی درطول گذار نمایشی متوقف‌شده به‌نظر می‌رسد.
  • باید بااستفاده از پُرسمان رسانه‌ای prefers-reduced-motion، به اولویت‌های کاربر برای کاهش حرکت احترام بگذارید.
  • مسیریابی دسترس‌پذیری اجباری: انتقال‌های نما، چیدمان‌های صفحه را به‌طور پویا تغییر می‌دهند اما تمرکز برنامه‌ای را مدیریت نمی‌کنند. اگر تمرکز روی عنصری باقی بماند که درطول انتقال پنهان یا برداشته شده است، تمرکز رها می‌شود و کاربران صفحه‌کلید و فناوری کمکی بدون بافت باقی می‌مانند. بلافاصله پس‌از به‌روزرسانی DOM یا زمانی که وعده finished انتقال نما حل شد، تمرکز را به‌صورت برنامه‌نویسی‌شده به سرصفحه به‌روزشده یا محفظه نما (بااستفاده از tabindex="-1") منتقل کنید.
@media (prefers-reduced-motion: reduce) {
  ::view-transition-group(*),
  ::view-transition-old(*),
  ::view-transition-new(*) {
    animation: none !important;
  }
}

راهبردهای جایگزین

View Transitions API برای ارتقای پیش‌رو طراحی شده است. مرورگرهایی که از آن پشتیبانی نمی‌کنند، به‌روزرسانی DOM را بلافاصله و بدون پویانمایی اجرا می‌کنند.

function navigate(){
  if (!document.startViewTransition) {
    // Fallback: Just update the DOM
    updateDOM();
  } else {
    document.startViewTransition(() => updateDOM());
  }
}