群组元素过渡

当列表中的内容项被添加、移除或重新排列时,过渡效果可以帮助用户保持上下文。视图过渡提供了一种在元素的两种状态之间进行过渡的方法,方法是为元素指定唯一的 view-transition-name。当页面上的多个元素具有相同的过渡行为时,view-transition-class 允许您在 CSS 中定义一次该逻辑,而无需为每个唯一的 view-transition-name 重复该逻辑。这样可以使样式表易于维护,同时确保一组元素具有一致的动画。

实现步骤

  1. 分配唯一名称和共享类

    在过渡期间需要单独跟踪的每个元素都必须具有唯一的 view-transition-name。

    <!-- Mandatory: Each element must have a unique view-transition-name -->
    <li style="view-transition-name: item-1" class="item">Item 1</li>
    <li style="view-transition-name: item-2" class="item">Item 2</li>
    

    如需应用共享样式,还需分配 view-transition-class。

    .item {
      view-transition-class: list-item;
    }
    
  2. 定义共享过渡逻辑

    将 ::view-transition-group() 伪元素与类选择器结合使用,可将样式应用于该组的所有成员。

    /* Targets any view transition group that has the 'list-item' class */
    ::view-transition-group(.list-item) {
      animation-duration: 0.5s;
      animation-timing-function: ease-in-out;
    }
    
    /* Handle accessibility by respecting motion preferences */
    @media (prefers-reduced-motion: reduce) {
      /* Disable all group transitions, including the default `root` group. */
      ::view-transition-group(*),
      ::view-transition-old(*),
      ::view-transition-new(*) {
        animation: none !important;
      }
    }
    
  3. 可选:定义进入动画和退出动画

    使用 :only-child 选择器为添加或移除的元素添加特定过渡效果。::view-transition-new() 和 ::view-transition-old() 伪元素是 ::view-transition-image-pair() 伪元素的子元素,因此如果它是唯一的子元素,我们可以确定它是添加或移除的元素。

    /* A `::view-transition-new()` element is the only child if it wasn't present before the view transition, so it is an added element. */
    ::view-transition-new(.list-item):only-child {
      animation-name: slide-in;
      /* Specify an animation duration if you want something different than the UA default of 0.5s */
      animation-duration: 1s;
    }
    /* A `::view-transition-old()` element is the only child if it isn't present after the view transition, so it is a removed element. */
    ::view-transition-old(.list-item):only-child {
      animation-name: slide-out;
      /* Specify an animation duration if you want something different than the UA default of 0.5s */
      animation-duration: 1s;
    }
    
    @keyframes slide-in {
      from {
        translate: -100vw 0;
      }
    }
    @keyframes slide-out {
      to {
        translate: -100vw 0;
      }
    }
    
  4. 触发过渡

    将 DOM 更新封装在 document.startViewTransition() 中。浏览器会捕获旧状态,执行更新,然后以动画形式过渡到新状态。

    function updateList(newData) {
      document.startViewTransition(() => {
        // All DOM changes inside this callback will be transitioned
        render(newData);
      });
    }
    
  5. 保持非过渡元素的互动性

视图过渡的工作原理是叠加 DOM 元素的快照,然后对快照进行过渡。这意味着在过渡期间,元素无法互动。如果存在未过渡的互动元素,您可以通过在视图过渡中停用触摸事件来使其可互动。

::view-transition {
  /* Non-transitioned elements below the view transitions remain interactive */
  pointer-events: none;
}

此外,默认情况下,:root 元素具有名为 root 的视图过渡,可实现默认的全页过渡。如果根元素没有变化,则会是两个相同快照之间的过渡,这些快照不具有互动性。由于我们只对特定元素(而非整个屏幕)进行转场,因此可以停用 root 转场。

:root {
  /* Disable the root transition because we are only transitioning specific elements. */
  view-transition-name: none;
}

后备策略

视图过渡是一种渐进增强。如果浏览器不支持 document.startViewTransition,DOM 更新仍应立即进行,从而提供功能正常但没有动画效果的体验。

if (document.startViewTransition) {
  document.startViewTransition(() => updateDOM());
} else {
  // Fallback: Perform the update without animation
  updateDOM();
}

对于 CSS,无法识别 view-transition-class 或 ::view-transition-group() 类选择器的浏览器会直接忽略这些规则,并且不会应用任何动画。