CSS location:sticky etkinliği için

Özet

İşte sır: Bir sonraki uygulamanızda scroll etkinliklerine ihtiyacınız olmayabilir. Bir IntersectionObserver, position:sticky öğeleri sabitlendiğinde veya sabitlenmeyi bıraktığında özel etkinlikleri nasıl tetikleyebileceğinizi gösteriyorum. Şunlar olmadan: kullanımından bahsedeceğiz. Bunu kanıtlayacak harika bir demo bile var:

Demoyu görüntüle | Kaynak

Karşınızda sticky-change etkinliği

CSS sabit konumunu kullanmanın pratik sınırlamalarından biri, mülkün etkin olup olmadığını gösteren bir platform sinyali sağlamaz. Başka bir deyişle, bir öğenin ne zaman yapışkan hale geleceğini veya ne zaman yapışkan kalmayı bırakır.

Aşağıdaki örneği ele alalım. Bu örnekte, <div class="sticky"> üst kapsayıcısının üstüne yerleştirin:

.sticky {
  position: sticky;
  top: 10px;
}

Öğeler bu işarete çarptığında tarayıcının bunu bildirmesi güzel olmaz mıydı? Sadece ben değilim büyük önem taşıyor. position:sticky sinyali bir dizi kullanım alanının kilidini açabilir:

  1. Sabitlenen banner'a gölge uygulayabilirsiniz.
  2. Bir kullanıcı içeriğinizi okurken, takip edebilirsiniz.
  3. Kullanıcı sayfayı kaydırırken kayan TOC widget'ını bölümüne bakın.

Bu kullanım alanlarını göz önünde bulundurarak bir nihai hedef belirledik: bir position:sticky öğesi sabitlendiğinde tetiklenir. Biz buna sticky-change etkinlik:

document.addEventListener('sticky-change', e => {
  const header = e.detail.target;  // header became sticky or stopped sticking.
  const sticking = e.detail.stuck; // true when header is sticky.
  header.classList.toggle('shadow', sticking); // add drop shadow when sticking.

  document.querySelector('.who-is-sticking').textContent = header.textContent;
});

Demo, sabit hale geldiklerinde alt gölge olarak bu etkinliğe dönüştürebilir. Ayrıca yeni bir başlık görürsünüz.

Demoda efektler Scrollevents olmadan uygulanır.

Kaydırma etkinlikleri olmayan kaydırma efektleri?

Sayfanın yapısı.
Sayfanın yapısı.

Bu adlara referans verebilmek için biraz terminolojiyi keselim. yazın:

  1. Kayan kapsayıcı - "blog yayınları" listesidir.
  2. Başlıklar - position:sticky içeren her bölümde mavi başlık.
  3. Yapışkan bölümler: Her içerik bölümü. Ekranın altında kayan metin yapışkan başlıklar.
  4. "Sabit mod" - öğeye position:sticky uygulanırken.

Hangi üstbilginin "yapışkan moda" girdiğini öğrenmek için bunu kayan kapsayıcının kaydırma ofseti. Bu da bize, projenin gösterilen başlığı hesaplamak için kullanılır. Ancak bu durum oldukça scroll etkinlikleri olmadan yapmak zordur :) Diğer sorun ise position:sticky, düzeltildiğinde öğeyi düzenden kaldırır.

Dolayısıyla, kaydırma etkinlikleri olmadan düzenle ilgili işlemleri gerçekleştirme yeteneğini hesaplamalar yapabilir.

Kaydırma konumunu belirlemek için model DOM ekleniyor

Şunu yapmak için scroll etkinlikleri yerine IntersectionObserver kullanacağız: başlıkların sabit moda girip çıkmayacağını belirler. İki düğüm ekleme (biri üstte, diğeri üstte olmak üzere) her yapışkan bölüme kaydırıcılar, kaydırma konumunu bulmak için ara nokta görevi görür. Bu durumlarda işaretçilerin kapsayıcıya girmesine veya kapsayıcıdan ayrılmasına, görünürlüklerinin değişmesi Kesişim Gözlemcisi bir geri çağırma tetikler.

Sentinel öğeleri gösterilmiyor
Gizli koruyucu öğeler.

Yukarı ve aşağı kaydırma ile ilgili dört durumu kapsamak için iki koruyucuya ihtiyacımız var:

  1. Aşağı kaydırma: Başlık, üst koruyucunun üst çizgisi çakıştığında yapışkan hale gelir üst kısmına yerleştirin.
  2. Aşağı kaydırma: başlık, sayfanın en altına ulaştığında sabit moddan çıkar bölümünün ve alt kısmındaki koruyucunun, kapsayıcının üst tarafından kesiştiğini unutmayın.
  3. Yukarı kaydırma: Başlık, üst koruyucu kaydırıldığında yapışkan moddan çıkar yukarıdan görünüme geri dönebilir.
  4. Yukarı kaydırma: Başlık, alt koruyucunun arkası kesişirken yapışkan hale gelir yukarıdan görebilirsiniz.

Ekran video kaydını gerçekleşme sırasına göre 1'den 4'e kadar görüntülemek faydalıdır:

Kesişim Gözlemcileri, bekçiler kaydırma kapsayıcısını girin/kaydırın.

CSS

Nöbetçiler her bölümün üst ve alt kısmına yerleştirilir. .sticky_sentinel--top, başlığın üzerinde yer alır. .sticky_sentinel--bottom, bölümün alt kısmında yer alır:

Alttaki koruyucu, eşiğine ulaşıyor.
Üst ve alt koruyucu öğelerinin konumu.
:root {
  --default-padding: 16px;
  --header-height: 80px;
}
.sticky {
  position: sticky;
  top: 10px; /* adjust sentinel height/positioning based on this position. */
  height: var(--header-height);
  padding: 0 var(--default-padding);
}
.sticky_sentinel {
  position: absolute;
  left: 0;
  right: 0; /* needs dimensions */
  visibility: hidden;
}
.sticky_sentinel--top {
  /* Adjust the height and top values based on your on your sticky top position.
  e.g. make the height bigger and adjust the top so observeHeaders()'s
  IntersectionObserver fires as soon as the bottom of the sentinel crosses the
  top of the intersection container. */
  height: 40px;
  top: -24px;
}
.sticky_sentinel--bottom {
  /* Height should match the top of the header when it's at the bottom of the
  intersection container. */
  height: calc(var(--header-height) + var(--default-padding));
  bottom: 0;
}

Kavşak Gözlemcilerini Kurma

Kesişim Gözlemcileri, bir hedef öğe ve belge görüntü alanını veya bir üst kapsayıcıyı seçebilirsiniz. Bizim örneğimizde bir üst kapsayıcıyla kesişimler gözlemliyoruz.

Sihirli sos IntersectionObserver. Her bir koruyucu mesafedeki kesişim görünürlüğünü gözlemlemek için IntersectionObserver kaydırma kapsayıcı. Bir koruyucu, görünür görüntü alanına kaydırıldığında bir başlık sabitlenir veya artık yapışkan kalmaz. Benzer şekilde, bir koruyucu çıkış yaptığında görüntü alanını değiştirebilirsiniz.

İlk olarak, üstbilgi ve altbilgi koruyucuları için gözlemcileri ayarladım:

/**
 * Notifies when elements w/ the `sticky` class begin to stick or stop sticking.
 * Note: the elements should be children of `container`.
 * @param {!Element} container
 */
function observeStickyHeaderChanges(container) {
  observeHeaders(container);
  observeFooters(container);
}

observeStickyHeaderChanges(document.querySelector('#scroll-container'));

Ardından, .sticky_sentinel--top öğeleri geçtiğinde etkinleşecek bir gözlemci ekledim kayan kapsayıcı'nın üst kısmından (her iki yönde) dokunun. observeHeaders işlevi en iyi kordonları oluşturur ve her bölüme bakın. Gözlemci, koruyucunun kesişimini üst kısmına yerleştirilir ve görüntü alanına girip girmediğine karar verir. O bilgisi, bölüm üstbilgisinin yapılıp yapılmadığını belirler.

/**
 * Sets up an intersection observer to notify when elements with the class
 * `.sticky_sentinel--top` become visible/invisible at the top of the container.
 * @param {!Element} container
 */
function observeHeaders(container) {
  const observer = new IntersectionObserver((records, observer) => {
    for (const record of records) {
      const targetInfo = record.boundingClientRect;
      const stickyTarget = record.target.parentElement.querySelector('.sticky');
      const rootBoundsInfo = record.rootBounds;

      // Started sticking.
      if (targetInfo.bottom < rootBoundsInfo.top) {
        fireEvent(true, stickyTarget);
      }

      // Stopped sticking.
      if (targetInfo.bottom >= rootBoundsInfo.top &&
          targetInfo.bottom < rootBoundsInfo.bottom) {
       fireEvent(false, stickyTarget);
      }
    }
  }, {threshold: [0], root: container});

  // Add the top sentinels to each section and attach an observer.
  const sentinels = addSentinels(container, 'sticky_sentinel--top');
  sentinels.forEach(el => observer.observe(el));
}

Gözlemci threshold: [0] ile yapılandırıldığından hemen geri çağırması etkinleşir görünür hale gelir.

Bu süreç, alt taraftaki koruyucuda da (.sticky_sentinel--bottom) benzer bir süreçtir. Altbilgiler alttan geçtiğinde etkinleşmek üzere ikinci bir gözlemci oluşturulur kaydırma kapsayıcısının özellikleri arasındadır. observeFooters işlevi, sentinel düğümleri oluşturur ve bunları her bir bölüme ekler. Gözlemci alt ile kesişimine odaklanır ve bu noktanın en iyi veya ayrılabilir. Bu bilgiler, bölüm üstbilgisinin uygulayıp uygulamayacağınızı belirleyin.

/**
 * Sets up an intersection observer to notify when elements with the class
 * `.sticky_sentinel--bottom` become visible/invisible at the bottom of the
 * container.
 * @param {!Element} container
 */
function observeFooters(container) {
  const observer = new IntersectionObserver((records, observer) => {
    for (const record of records) {
      const targetInfo = record.boundingClientRect;
      const stickyTarget = record.target.parentElement.querySelector('.sticky');
      const rootBoundsInfo = record.rootBounds;
      const ratio = record.intersectionRatio;

      // Started sticking.
      if (targetInfo.bottom > rootBoundsInfo.top && ratio === 1) {
        fireEvent(true, stickyTarget);
      }

      // Stopped sticking.
      if (targetInfo.top < rootBoundsInfo.top &&
          targetInfo.bottom < rootBoundsInfo.bottom) {
        fireEvent(false, stickyTarget);
      }
    }
  }, {threshold: [1], root: container});

  // Add the bottom sentinels to each section and attach an observer.
  const sentinels = addSentinels(container, 'sticky_sentinel--bottom');
  sentinels.forEach(el => observer.observe(el));
}

Gözlemci threshold: [1] ile yapılandırıldığından tüm düğümün görünümü içindedir.

Son olarak, sticky-change özel etkinliğini tetiklemek için kullanabileceğim iki yardımcı programım var oluşturmak için şu adımları uygulayın:

/**
 * @param {!Element} container
 * @param {string} className
 */
function addSentinels(container, className) {
  return Array.from(container.querySelectorAll('.sticky')).map(el => {
    const sentinel = document.createElement('div');
    sentinel.classList.add('sticky_sentinel', className);
    return el.parentElement.appendChild(sentinel);
  });
}

/**
 * Dispatches the `sticky-event` custom event on the target element.
 * @param {boolean} stuck True if `target` is sticky.
 * @param {!Element} target Element to fire the event on.
 */
function fireEvent(stuck, target) {
  const e = new CustomEvent('sticky-change', {detail: {stuck, target}});
  document.dispatchEvent(e);
}

İşte bu kadar.

Son demo

position:sticky içeren öğeler haline geldiğinde özel bir etkinlik oluşturduk. scroll etkinlik kullanılmadan sabit ve eklenmiş kaydırma efektleri var.

Demoyu görüntüle | Kaynak

Sonuç

IntersectionObserver adlı çocuğum genellikle yardımcı olan bazı scroll etkinliğe dayalı kullanıcı arayüzü kalıplarının yerine zaman içinde gelişti. Cevabın evet ve hayır olduğu anlaşılıyor. Anlamsal IntersectionObserver API'nin kullanımı her şey için kullanımı zorlaştırır. Ama Az önce gösterdiğim gibi bazı ilginç teknikler için kullanabilirsiniz.

Stil değişikliklerini tespit etmenin başka bir yolu nedir?

Pek sayılmaz. İhtiyacımız olan şey, bir DOM öğesindeki stil değişikliklerini gözlemlemekti. Maalesef web platformu API'lerinde aşağıdakileri yapmanıza olanak tanıyan hiçbir şey yoktur. saat stili değişiklikleri.

MutationObserver, mantıklı bir ilk tercihtir ancak işe yaramaz. gerekir. Örneğin, demoda sticky bir kullanıcı geri aranırsa sınıfı bir öğeye eklenir. Ancak öğenin hesaplanan stili değiştiğinde eklenmez. sticky sınıfının sayfa yükleme sırasında zaten bildirilmiş olduğunu unutmayın.

Gelecekte, "Style Mutation Observer" (Stil Değişimi Gözlemci) mutasyon gözlemcilerine ekleme yapmak, tablodaki değişiklikleri gözlemlemek ve öğesinin hesaplanan stillerini değiştirmenizi sağlar. position: sticky.