Duruma Duyarlı Yapışkan Başlıklar

Yapışkan başlıklar yaygın bir kullanıcı arayüzü kalıbıdır ancak okunabilirliği korumak veya yerden tasarruf etmek için "yapışkan" hale geldiklerinde genellikle görünümlerini değiştirmeleri gerekir. Geleneksel çözümler, performans sorunlarına neden olabilecek JavaScript kaydırma dinleyicileri gerektiriyordu. Modern CSS, kaydırma durumu sorgularını kullanarak bu işlemi yerel olarak yapmanıza olanak tanır.

Uygulama Adımları

1. Yapışkan kapsayıcıyı oluşturma

Yapışkan öğe ve kaydırma durumu sorgusu için kapsayıcı görevi görecek bir kapsayıcıya ihtiyacınız vardır.

<!-- Wrap in a section to define the containment area for the sticky element -->
<div class="section">
  <div class="sticky-container">
    <div class="sticky-header">
      Section Header
    </div>
  </div>
  <div class="content">
    <!-- Content goes here -->
  </div>
</div>

2. Yapışkan Davranış ve Kapsayıcı Türü için CSS uygulama

container-type: scroll-state özelliğini, kaydırılabilir üst öğesinde değil, position: sticky öğesinde ayarlayın. Bu sorgular iç içe yerleştirilebileceğinden ve adsız kapsayıcılar çakışacağından her zaman bir container-name belirtin. Boyut sorgularıyla da birleştirebilirsiniz (ör. container-type: scroll-state inline-size).

.sticky-container {
  position: sticky;
  top: 0;
  /* MANDATORY: Always specify a container name to avoid collisions if CQs are nested */
  container-type: scroll-state;
  container-name: section-header;
  z-index: 10;
}

.sticky-header {
  /* Base styles for the header */
  background: #f0f0f0;
  padding: 20px;
  transition: background-color 0.3s, box-shadow 0.3s;
}

Önemli: Kaydırma durumu, kaydırma durumu kapsayıcısının alt öğeleri tarafından sorgulanır. @container sorgusundaki stiller .sticky-container öğesinin kendisine değil, alt öğelerine (ör. .sticky-header) uygulanır. Kapsayıcı öğesini kendi kaydırma durumu sorgusuyla stillendiremezsiniz.

3. Takılı kalma durumunu hedefleme

Başlık sabitlendiğinde stilleri uygulamak için @container scroll-state(...) sorgusunu kullanın.

/* Apply styles when the named container is stuck at the top */
@container section-header scroll-state(stuck: top) {
  .sticky-header {
    background: #0056b3;
    color: white;
    box-shadow: 0 4px 6px rgb(0 0 0 / 0.15);
  }
}

İpucu: Fiziksel kenarlar yerine akışla ilgili kenarları sorgulayarak uluslararasılaştırmayı (i18n) ve sağdan sola (RTL) düzenleri daha iyi desteklemek için stuck: inset-block-start veya stuck: inset-inline-start gibi mantıksal özellikleri de kullanabilirsiniz.

Boyut Değişiklikleriyle İlgili Notlar

Kaydırma sabitleme devre dışı bırakılmadığında, takılma sırasında düzeni etkileyen özellikleri (yükseklik, dolgu, yazı tipi boyutu) değiştirmek görsel titremeye neden olabilir. Yapışkan öğenin altındaki akış içi içerikte kaydırma sabitleme, düzen değişikliğini telafi etmek için kaydırma ofsetini ayarlar. Bu da öğeyi sıkışmış konumundan geri iter ve salınımı tetikler.

Bunu önlemek için yapışkan öğenin üst öğesinde kaydırma sabitlemeyi devre dışı bırakın:

.section {
  overflow-anchor: none;
}

overflow-anchor: none özelliğini :root veya body öğesine değil, position: sticky öğesinin doğrudan üst öğesine uygulayın. Aksi takdirde, sayfanın tamamı için kaydırma sabitleme devre dışı bırakılır. Bu sayede, kutu modeli özellikleri ve dönüşümler dahil olmak üzere sabit durumdaki tüm özellikleri dilediğiniz gibi değiştirebilirsiniz.

YAPMAYIN: Alt kısma sabitlenmiş öğeler için overflow-anchor: none kullanmayın. Kaydırma sabitleme, yalnızca mevcut kaydırma konumunun üzerindeki düzen kaymalarını telafi eder. Bu nedenle, altta sabitlenmiş öğeler üzerinde hiçbir etkisi yoktur. Altta sabitlenmiş başlıklar için sıkışmış durumdayken kutu modeli özelliklerini değiştirmekten kaçının.

Yedek stratejiler

Kaydırma durumu sorguları, progresif bir geliştirmedir. container-type: scroll-state özelliğini desteklemeyen tarayıcılarda başlık, position: sticky nedeniyle üst kısımda sabit kalır ancak görsel olarak dönüşmez. Çoğu kullanım alanı için önerilen yaklaşım budur.

Yapışkanlık gerekli değilse ancak farklı stil olmadan position: sticky, hiç yapışkanlık olmamasından daha kötü bir deneyim sunuyorsa position: sticky ve ilgili bildirimi bir @supports sorgusuna da sarmalayabilirsiniz:

.sticky-container {
  @supports (container-type: scroll-state) {
      position: sticky;
      top: 0;
      container-type: scroll-state;
      container-name: section-header;
      z-index: 10;
    }
}

İpucu: "Sabit" stiliniz okunabilirlik için farklı bir arka plan rengi gerektiriyorsa düzeninizi varsayılan stilin her yerde çalışacağı şekilde ayarlayabilirsiniz (progresif geliştirme). Yedek kullanmanız gerekiyorsa position: sticky davranışını bir @supports (container-type: scroll-state) sorgusu içinde geçitleyebilirsiniz.

Görsel dönüşüm, tasarım için kesinlikle kritikse (ör. yapışkan durum, içerik okunamaz hale gelmeden bir arka plan veya sıkıştırma sunuyorsa) IntersectionObserver kullanarak güçlü bir JavaScript yedek çözümü uygulayabilirsiniz. CSS stillerinizi bir .is-stuck sınıfı altında kopyalamanız gerekir. Aşağıdaki genel polyfill, @container scroll-state(stuck: top) davranışıyla eşleşecek şekilde yukarı doğru ilerlemek ve doğru kaydırma kapsayıcısını (varsayılan olarak görüntü alanı) bulmak için getComputedStyle öğesini kontrol eder:

function getScrollParent(node) {
  if (node == null || node === document.body || node === document.documentElement) {
    return null; // default to viewport
  }
  if (node.scrollHeight > node.clientHeight || node.scrollWidth > node.clientWidth) {
    const overflow = getComputedStyle(node).overflow;
    if (overflow !== 'visible' && overflow !== 'clip') {
      return node;
    }
  }
  return getScrollParent(node.parentNode);
}

document.querySelectorAll('.sticky-container').forEach(container => {
  const root = getScrollParent(container);
  
  const topOffset = parseFloat(getComputedStyle(container).top) || 0;
  
  const observer = new IntersectionObserver(
    ([e]) => {
      // Toggle the fallback class on the sticky header container
      e.target.classList.toggle('is-stuck', e.intersectionRatio < 1);
    },
    { 
      root: root,
      threshold: [1],
      rootMargin: `-${topOffset + 1}px 0px 0px 0px` 
    }
  );
  
  observer.observe(container);
});

Not: Bu genel IntersectionObserver kalıbı, scroll-state(scrollable) sorgusu için polyfill olarak da kullanılabilir.