Ö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:
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:
- Sabitlenen banner'a gölge uygulayabilirsiniz.
- Bir kullanıcı içeriğinizi okurken, takip edebilirsiniz.
- 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.
Kaydırma etkinlikleri olmayan kaydırma efektleri?
Yazının geri kalanında bu adlara referans verebilmek için bazı terimleri açıklayalım:
- Kayan kapsayıcı - "blog yayınları" listesidir.
- Başlıklar: Her bölümde
position:sticky
içeren mavi başlık. - Yapışkan bölümler: Her içerik bölümü. Sabit üstbilgilerin altında kaydırılan metin.
- "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.
Yukarı ve aşağı kaydırmayla ilgili dört durumu kapsayacak iki gözetmene ihtiyacımız var:
- Aşağı kaydırma: Üst gözetleyicisi kapsayıcının üst kısmını geçtiğinde header yapışkan hale gelir.
- 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.
- 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.
- 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:
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:
: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.
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
.