Özet
İşte sırrımız: Bir sonraki uygulamanızda scroll
etkinliklerine ihtiyacınız olmayabilir. IntersectionObserver
kullanarak, position:sticky
öğeleri düzeltildiğinde veya öğelerin yapışması durduğunda nasıl özel etkinlik tetikleyeceğinizi göstereceğim. Tüm bunları, kaydırma işleyicileri
kullanmadan yapabilirsiniz. Bunu kanıtlayacak harika bir demo bile var:
Karşınızda sticky-change
etkinliği
CSS yapışkan konumunu kullanmanın pratik sınırlamalarından biri, mülkün ne zaman etkin olduğunu bildiren bir platform sinyali sağlamamasıdır. Diğer bir deyişle, bir öğe ne zaman yapışkan hale geldiğini veya yapışkan olmayı bıraktığını bilmek söz konusu değildir.
<div class="sticky">
öğesinin üst kapsayıcısının üst kısmından 10 piksel uzaklıktaki örneği ele alalım:
.sticky {
position: sticky;
top: 10px;
}
Öğeler bu işarete ulaştığında tarayıcı bildirilse güzel olmaz mıydı?
Böyle düşünen tek kişi ben değilim. position:sticky
sinyali birçok kullanım alanının kilidini açabilir:
- Sabitlenen banner'a gölge uygulayın.
- Bir kullanıcı içeriğinizi okurken, ilerleme durumunu öğrenmek için analiz isabetlerini kaydedin.
- Kullanıcı sayfayı kaydırdığında, kayan TOC widget'ını geçerli bölüme güncelleyin.
Bu kullanım alanlarını göz önünde bulundurarak nihai bir hedef belirledik: Bir position:sticky
öğesi düzeltildiğinde tetiklenen bir etkinlik oluşturmak. Bunu sticky-change
etkinliği olarak adlandıralım:
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, düzeltildiğinde bir gölgeye başlık eklemek için bu etkinliği kullanır. Ayrıca sayfanın üst kısmındaki yeni başlık da güncellenir.
Kaydırma etkinlikleri olmadan kaydırma efektleri var mı?
İsterseniz yazının geri kalanında bu adlardan bahsedebilmek için biraz terminolojiyi sadeleştirelim:
- Kayan kapsayıcı - "Blog yayınları" listesini içeren içerik alanı (görünür görüntü alanı).
- Başlıklar - Her bölümde
position:sticky
içeren mavi başlık. - Sabit bölümler: Her bir içerik bölümü. Yapışkan başlıkların altında kayan metin.
- "Sabit mod": Öğeye
position:sticky
uygulanırken.
Hangi üstbilginin "sabit moda" girdiğini bilmek için kaydırma kapsayıcısının kaydırma ofsetini belirlemenin bir yolunu bulmamız gerekir. Bu, bize o anda gösterilen başlığı hesaplamanın bir yolunu sunar. Bununla birlikte, scroll
etkinlikleri olmadan bunu yapmak oldukça zor olur :) Diğer sorun, position:sticky
öğesinin öğe düzeltildiğinde düzenden kaldırılmasıdır.
Dolayısıyla, kaydırma etkinlikleri olmadığında başlıklarda düzenle ilgili hesaplamalar yapamayız.
Kaydırma konumunu belirlemek için önemsiz DOM ekleniyor
headers ne zaman sabit moda girip çıktığını belirlemek için scroll
etkinlikleri yerine IntersectionObserver
kullanacağız. Her yapışkan bölüme biri üstte, diğeri altta olacak şekilde iki düğüm (gözetmenler) eklemek, kaydırma konumunu bulmak için ara nokta görevi görür. Bu işaretçiler kapsayıcıya girip çıktıkça, görünürlükleri değişir ve Intersection Observer bir geri çağırma tetikler.
Yukarı ve aşağı kaydırma ile ilgili dört örneği ele almak için iki koruyucuya ihtiyacımız var:
- Aşağı kaydırma: Üst koruyucusu kapsayıcının üst kısmından geçtiğinde başlık yapışkan hale gelir.
- Aşağı kaydırma: Başlık, bölümün alt kısmına ulaştığında ve alt koruyucusu kapsayıcının üst kısmından geçtiğinde sabit moddan çıkar.
- Yukarı kaydırma: Başlık, üst koruyucusu yukarıdan görünüme geri kaydırdığında sabit moddan çıkar.
- Yukarı kaydırma: Başlık, alt koruyucusu üstten görünüme geri döndüğünde yapışkan hale gelir.
1'den 4'e kadar olan ekran video kaydını gerçekleşme sırasına göre izlemek faydalıdır:
CSS
Gözetmenler her bölümün üstünde ve altında konumlandırılır.
.sticky_sentinel--top
başlığın en üstünde, .sticky_sentinel--bottom
ise bölümün altındadı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özlemciler, bir hedef öğe ile belge görüntü alanının veya bir üst kapsayıcının kesişimindeki değişiklikleri eşzamansız olarak gözlemler. Örneğimizde, bir üst kapsayıcıyla kesişimleri gözlemliyoruz.
Sihirli sos IntersectionObserver
. Her bir koruyucu, kaydırma kapsayıcısı içindeki kesişim görünürlüğünü gözlemlemek için bir IntersectionObserver
alır. Bir güvenlik görevlisi, görünür görüntü alanına girdiğinde, bir başlığın sabitlendiğini veya yapışkan durumunun durduğunu anlarız. Benzer bir şekilde, bir güvenlik görevlisi
görüntü alanından çıktığında.
İlk olarak, üstbilgi ve altbilgi korumaları 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'));
Daha sonra, .sticky_sentinel--top
öğeleri kaydırma kapsayıcısının üst kısmından (her iki yönde) geçtiğinde etkinleşecek bir gözlemci ekledim.
observeHeaders
işlevi, en iyi koruyucuları oluşturur ve bunları her bir bölüme ekler. Gözlemci, koruyucunun container'ın üst kısmıyla kesişimini hesaplar ve görüntü alanına girip girmediğine karar verir. Bu bilgiler, bölüm başlığının kalıcı olup olmadığı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ılır. Bu nedenle, güvenlik gözetici görünür hale geldiğinde geri çağırma etkinleşir.
Bu süreç alt koruyucu (.sticky_sentinel--bottom
) için de geçerlidir. Altbilgiler kaydırma kapsayıcısının altından geçtiğinde etkinleşmek üzere ikinci bir gözlemci oluşturulur. observeFooters
işlevi, koruyucu düğümleri oluşturur ve bunları her bir bölüme ekler. Gözlemci, koruyucunun container'ın alt kısmıyla kesişimini hesaplar ve girişe mi yoksa ayrılıyor mu olduğuna karar verir. Bu bilgiler, bölüm başlığının kalıcı olup
olmadığını 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ılır. Bu nedenle, düğümün tamamı görünümde olduğunda geri çağırma etkinleşir.
Son olarak, sticky-change
özel etkinliğini tetiklemek ve koruyucuları oluşturmak için kullanabileceğim iki yardımcı programım vardır:
/**
* @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 düzeltildiğinde özel bir etkinlik oluşturduk ve scroll
etkinliklerini kullanmadan kaydırma efektleri ekledik.
Sonuç
IntersectionObserver
'in yıllar içinde gelişen scroll
etkinliğe dayalı kullanıcı arayüzü kalıplarından bazılarının yerine geçerken faydalı bir araç olup olmayacağını sık sık düşündüm. Cevabın hem evet hem de hayır olduğu ortaya çıktı. IntersectionObserver
API'nin anlamları, her şey için kullanılmasını zorlaştırıyor. Ama burada gösterdiğim gibi, bazı ilginç teknikler için kullanabilirsiniz.
Stil değişikliklerini tespit etmenin başka bir yolu var mı?
Pek sayılmaz. İhtiyacımız olan şey, DOM öğesindeki stil değişikliklerini gözlemlemenin bir yoluydu. Ne yazık ki web platformu API'lerinde stil değişikliklerini izlemenize olanak tanıyan hiçbir şey yoktur.
MutationObserver
, mantıklı bir ilk tercih olur ancak çoğu durumda işe yaramaz. Örneğin, demoda sticky
sınıfı bir öğeye eklendiğinde geri çağırma yapılır ancak öğenin hesaplanan stili değiştiğinde geri arama almazsınız.
sticky
sınıfının sayfa yüklemede zaten bildirilmiş olduğunu unutmayın.
Gelecekte, Mutasyon Gözlemcileri'ne yönelik bir"Style Mutation Observer" uzantısı, bir öğenin hesaplanan stillerindeki değişiklikleri gözlemlemek için yararlı olabilir.
position: sticky
.