輪轉介面精選 Snap

網頁功能: 容器捲動狀態查詢

捲動狀態容器查詢可讓您根據元素目前的捲動狀態設定樣式,例如元素是否「停滯」(透過黏性定位) 或「貼齊」(透過捲動貼齊)。這樣一來,您就能在輪轉介面或圖片庫中,以視覺方式區分使用中的項目,不必依賴 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);
  });
}