轮播 Snap 精彩集锦

Web 功能: 容器滚动状态查询

借助滚动状态容器查询,您可以根据元素的当前滚动状态(例如元素是否“粘滞”[通过粘性定位] 或“贴靠”[通过滚动贴靠])来设置元素样式。这样一来,在轮播界面或图库体验中,无需依赖 JavaScript 交叉观察器或滚动事件监听器,即可直观地区分活动项。

核心实现

如需突出显示贴靠的项,您必须建立滚动贴靠容器,将贴靠目标定义为滚动状态容器,然后查询该状态以设置后代样式。

1. 建立滚动贴靠容器

父级容器必须启用 scroll-snap-type。

<div class="carousel">
  <div class="carousel-item">
    <div class="card">Product 1 content</div>
  </div>
  <div class="carousel-item">
    <div class="card">Product 2 content</div>
  </div>
</div>
.carousel {
  display: flex;
  overflow-x: auto;
  /* MANDATORY: Enable scroll snapping on the container */
  scroll-snap-type: x mandatory;
}

2. 将贴靠目标定义为滚动状态容器

轮播界面中应跟踪贴靠的每个项都必须声明为 scroll-state 容器。

.carousel-item {
  /* Define where the item snaps within the container */
  scroll-snap-align: center;
  
  /* MANDATORY: Establish this element as a scroll-state query container */
  container-type: scroll-state;
}

3. 查询 snapped 状态

由于容器查询会为后代设置样式,因此您必须将突出显示样式应用于贴靠目标内的元素。由于滚动容器设置为在 x 轴上溢出,因此请使用 scroll-state(snapped: x) 查询。

强制性要求:将样式封装在 @media (prefers-reduced-motion: no-preference) 中,以便仅向未请求减少动画效果的用户显示该效果。根据您的用例,您可能会保留部分效果,但在这种情况下,卡片会以可能对某些用户造成问题的方式从白色闪烁到蓝色,因此我们将其完全停用。

/* Specify transition outside of queries so that it is applied regardless of state.  */
.card {
  transition:
    scale 0.4s cubic-bezier(0.25, 0.8, 0.25, 1),
    background-color 0.4s,
    color 0.4s,
    box-shadow 0.4s;
}
/* 
Only show the effect for users not requesting reduced motion. Disable completely, including the color change, as it causes a flash that may be problematic.
*/
@media (prefers-reduced-motion: no-preference) {
  /* Style the content when its parent .carousel-item is snapped on the x axis */
  @container scroll-state(snapped: x) {
    .card {
      background: #007bff;
      color: white;
      scale: 1.15;
      box-shadow: 0 10px 25px rgba(0, 123, 255, 0.3);
    }
  }
}

/* MANDATORY Copy-Paste Safety: Disable highlight scaling/flashing for motion sensitive users */
@media (prefers-reduced-motion: reduce) {
  .card {
    transition: none !important;
    scale: 1 !important;
  }
}

snapped 描述符可以查询特定轴:x、y、inline、block 或 both。

无障碍

AVOID:将 scroll-state 与互动元素搭配使用。

为吸附项添加视觉突出显示效果可以改善用户体验,但吸附项不会向无障碍树公开。应用于吸附项的视觉主题不应传达该元素处于活动状态或聚焦状态,并且键盘焦点环应清晰可见,并与 snapped 突出显示明显区分开来。如果吸附的项是互动式项,您必须使用其他标准无障碍功能做法,使其可供无障碍功能使用。

吸附是由滚动引起的,滚动不会移动键盘焦点。不过,键盘焦点可能会导致滚动容器移动,从而导致贴靠项发生变化,该项可能是也可能不是焦点项。这可能会让用户感到困惑,因此不建议这样做。

后备策略

对于不支持滚动状态查询的浏览器,您应提供功能性的基本体验,确保所有内容都清晰可辨,即使没有“活跃”突出显示效果也是如此。

功能检测

您可以使用 @supports 仅为受支持的浏览器提供增强功能:

@supports (container-type: scroll-state) {
  /* Enhancement styles here */
}

JavaScript 后备

如果突出显示对用户体验至关重要,请使用 IntersectionObserver 确定贴靠的项。通过提供具有负内嵌值的 rootMargin,将观察到的区域调整为轮播界面中心的薄切片。例如,如果元素位于轮播界面中心 2% 的区域内,则将其视为相交,请将 rootMargin 设置为 "0px -49%"。

// Optional: detect support and apply a JS-based fallback
if (!CSS.supports('container-type', 'scroll-state')) {
  const observer = new IntersectionObserver((entries) => {
    entries.forEach(entry => {
      // Toggle a class based on intersection
      entry.target.classList.toggle('is-snapped', entry.isIntersecting);
    });
  }, {
    root: document.querySelector(".carousel"),
    // Carousel item intersects if any part of the carousel item is in the middle 2% of the carousel.
    rootMargin: "0px -49%"
  });

  document.querySelectorAll('.carousel-item').forEach(item => {
    observer.observe(item);
  });
}