Sorotan Snap Carousel

Kueri penampung status scroll memungkinkan Anda menata gaya elemen berdasarkan status scroll saat ini, seperti apakah elemen "macet" (melalui pemosisian melekat) atau "diposisikan" (melalui pengepasan posisi scroll). Hal ini memungkinkan pengalaman carousel atau galeri yang item aktifnya dapat dibedakan secara visual tanpa mengandalkan pemroses peristiwa scroll atau observer persimpangan JavaScript.

Penerapan inti

Untuk menandai item yang dipaskan, Anda harus membuat penampung scroll-snap, menentukan target snap sebagai penampung scroll-state, lalu mengkueri status tersebut untuk menata gaya turunan.

1. Membuat penampung pengepasan posisi scroll

Penampung induk harus mengaktifkan 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. Menentukan target snap sebagai penampung status scroll

Setiap item dalam carousel yang harus dilacak untuk snapping harus dideklarasikan sebagai penampung 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. Membuat kueri status snapped

Karena kueri penampung menata gaya turunan, Anda harus menerapkan gaya sorotan ke elemen di dalam target snap. Karena container scroll ditetapkan untuk meluap pada sumbu x, gunakan kueri scroll-state(snapped: x).

WAJIB: Gabungkan gaya dalam @media (prefers-reduced-motion: no-preference) untuk hanya menampilkan efek kepada pengguna yang tidak meminta pengurangan gerakan. Bergantung pada kasus penggunaan, Anda dapat mempertahankan sebagian efeknya, tetapi dalam kasus ini, kartu berkedip dari putih ke biru dengan cara yang dapat menyebabkan masalah bagi beberapa pengguna, jadi kami menonaktifkannya sepenuhnya.

/* 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;
  }
}

Deskripsi snapped dapat mengkueri sumbu tertentu: x, y, inline, block, atau both.

Aksesibilitas

AVOID: menggunakan scroll-state dengan elemen interaktif.

Sorotan visual untuk item yang diposisikan dapat meningkatkan UX, tetapi item yang diposisikan tidak ditampilkan ke hierarki aksesibilitas. Tema visual yang diterapkan ke item yang diposisikan tidak boleh menunjukkan bahwa elemen tersebut aktif atau difokuskan, dan cincin fokus keyboard harus terlihat jelas dan berbeda dari sorotan snapped. Jika item yang diposisikan bersifat interaktif, Anda harus menggunakan praktik aksesibilitas standar lainnya untuk membuatnya dapat diakses.

Pengepasan terjadi karena scrolling, yang tidak memindahkan fokus keyboard. Namun, fokus keyboard dapat menyebabkan penampung scroll bergerak, sehingga menyebabkan perubahan pada item yang diposisikan, yang mungkin merupakan item yang difokuskan atau tidak. Hal ini kemungkinan akan membingungkan pengguna dan tidak dianjurkan.

Strategi penggantian

Untuk browser yang tidak mendukung kueri status scroll, Anda harus memberikan pengalaman dasar yang fungsional, yaitu semua item dapat dibaca, bahkan tanpa sorotan "aktif".

Deteksi fitur

Anda dapat menggunakan @supports untuk memberikan peningkatan hanya pada browser yang didukung:

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

Penggantian JavaScript

Jika penyorotan sangat penting untuk pengalaman pengguna, gunakan IntersectionObserver untuk menentukan item yang diposisikan. Sesuaikan area yang diamati menjadi irisan tipis di tengah carousel dengan memberikan rootMargin dengan nilai inline negatif. Misalnya, untuk menganggap elemen berpotongan jika berada di tengah 2% carousel, tetapkan rootMargin ke "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);
  });
}