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üleyin | Kaynak

sticky-change etkinliğiyle tanışın

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 inceleyin. Bu örnekte, <div class="sticky"> üst kapsayıcısının üst kısmından 10 piksel sabitlenmiştir:

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

Tarayıcı, öğelerin bu işarete ne zaman ulaştığını söylese ne iyi olurdu? 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, yüzen bir içindekiler widget'ını mevcut bölüme güncelleyin.

Bu kullanım alanlarını göz önünde bulundurarak bir nihai hedef belirledik: Bir position:sticky öğesi düzeltildiğinde tetiklenen bir etkinlik oluşturma. Bu etkinliğe sticky-change etkinliği adını verelim:

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ı.

Yazının geri kalanında bu adlara referans verebilmek için bazı terimleri açıklayalım:

  1. Kayan kapsayıcı - "blog yayınları" listesidir.
  2. Başlıklar: Her bölümde position:sticky içeren mavi başlık.
  3. Yapışkan bölümler: Her içerik bölümü. Sabit üstbilgilerin altında kaydırılan metin.
  4. "Sabit mod": position:sticky öğeye uygulandığında.

Hangi başlığın "yapışkan moda" gireceğini bilmek için kaydırma kapsayıcısının kaydırma ofsetini belirlememiz gerekir. Bu sayede, şu anda gösterilen başlığı hesaplayabiliriz. Ancak bunu scroll etkinlikleri olmadan yapmak oldukça zordur :) Diğer sorun da position:sticky'un sabitlendiğinde öğeyi düzenden kaldırmasıdır.

Bu nedenle, kaydırma etkinlikleri olmadan başlıklarda düzenlemeyle ilgili hesaplamaları yapma özelliğini kaybettik.

Kaydırma konumunu belirlemek için boş DOM ekleme

Başlıklar'ın ne zaman yapışkan moda girip çıktığını belirlemek için scroll etkinlikleri yerine bir IntersectionObserver kullanacağız. Her yapışkan bölüme birer tane (veya gözetleyici) iki düğüm eklemek, kaydırma konumunu belirlemek için yol noktası 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.

Gözetmen öğeleri gösterilmeden
Gizli sentinel öğeleri.

Yukarı ve aşağı kaydırmayla ilgili dört durumu kapsayacak iki gözetmene ihtiyacımız var:

  1. Aşağı kaydırma: Üst gözetleyicisi kapsayıcının üst kısmını geçtiğinde header yapışkan hale gelir.
  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

Gözetmenler 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:

Alt gözetmen eşiğine ulaşıyor.
Üst ve alt gözetmen öğ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;
}

Kesişim gözlemcilerini ayarlama

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

Sihirli sos IntersectionObserver. Her gözetmen, kaydırma kapsayıcısında kesişim görünürlüğünü gözlemlemek için bir IntersectionObserver alır. Bir gözetmen görünür görüntü alanına kaydırıldığında, bir üstbilginin sabitlendiğini veya yapışkanlığını kaybettiğini biliriz. Benzer şekilde, bir koruyucu çıkış yaptığında görüntü alanını değiştirebilirsiniz.

Öncelikle, üstbilgi ve altbilgi gözetmenleri için gözlemciler oluşturdum:

/**
 * 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, gözetmenin kapsayıcının üst kısmıyla kesişim noktasını hesaplar ve gözetmenin görüntü alanının içine 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 geri çağırma işlevi, gözetmen görünür hale gelir gelmez tetiklenir.

Bu işlem, 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 kesişim noktasının veya ayrılabilir. Bu bilgiler, bölüm başlığının sabitlenip sabitlenmeyeceğini belirler.

/**
 * 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 geri çağırma işlevi, düğümün tamamı görünümde olduğunda tetiklenir.

Son olarak, sticky-change özel etkinliğini tetiklemek ve gözetmenleri oluşturmak için kullandığım iki yardımcı programımı paylaşmak isterim:

/**
 * @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.

Nihai demo

position:sticky içeren öğeler sabitlendiğinde özel bir etkinlik oluşturduk ve scroll etkinlikleri kullanmadan kaydırma efektleri ekledik.

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. Yanıtın hem evet hem de hayır olduğu ortaya çıktı. IntersectionObserver API'sinin semantikleri, her şey için kullanılmasını zorlaştırıyor. 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. Bir DOM öğesindeki stil değişikliklerini gözlemleyebileceğimiz bir yönteme ihtiyacımız vardı. 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 sınıfı bir öğeye eklendiğinde geri çağırma alırız ancak öğenin hesaplanmış stili değiştiğinde geri çağırma almayız. 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.