बहुत ज़्यादा शब्द हैं, पढ़ा नहीं गया
अहम जानकारी: ऐसा हो सकता है कि आपको अपने अगले ऐप्लिकेशन में scroll
इवेंट की ज़रूरत न पड़े. IntersectionObserver
का इस्तेमाल करके,
मैं दिखाता हूं कि position:sticky
एलिमेंट के ठीक हो जाने या उनके बने रहने पर, कस्टम इवेंट को कैसे चालू किया जा सकता है. यह सब स्क्रोल लिसनर के
इस्तेमाल के बिना किया गया है. यहां तक कि इसे साबित करने के लिए एक शानदार डेमो भी उपलब्ध है:
पेश है sticky-change
इवेंट
सीएसएस स्टिकी पोज़िशन का इस्तेमाल करने की एक मुख्य सीमा यह है कि यह प्रॉपर्टी के चालू होने पर, प्लैटफ़ॉर्म का सिग्नल नहीं देता. दूसरे शब्दों में कहें, तो यह पता करने में कोई इवेंट नहीं है कि कोई एलिमेंट कब स्टिकी हो जाता है या कब स्टिकी होना बंद हो जाता है.
नीचे दिया गया उदाहरण लें, जिसमें पैरंट कंटेनर के ऊपरी हिस्से से <div class="sticky">
की लंबाई 10 पिक्सल की होनी चाहिए:
.sticky {
position: sticky;
top: 10px;
}
क्या यह अच्छा नहीं होगा कि ब्राउज़र बता दे कि एलिमेंट कब उस मार्क पर हिट करते हैं?
साफ़ तौर पर सिर्फ़ मैं ही नहीं हूं
जिसे ऐसा लगता है. position:sticky
का सिग्नल कई इस्तेमाल के उदाहरणों से अनलॉक हो सकता है:
- बैनर के चिपकने पर उस पर ड्रॉप शैडो लगाएं.
- जब लोग आपका कॉन्टेंट पढ़ते हैं, तब उनकी प्रोग्रेस जानने के लिए, आंकड़ों के हिट रिकॉर्ड करें.
- जब उपयोगकर्ता पेज स्क्रोल करता है, तब फ़्लोटिंग TOC विजेट को मौजूदा सेक्शन में अपडेट करें.
इस्तेमाल के इन उदाहरणों को ध्यान में रखते हुए, हमने एक आखिरी लक्ष्य बनाया है: एक ऐसा इवेंट बनाएं जो position:sticky
एलिमेंट के ठीक होने पर ट्रिगर होता हो. चलिए, इसे
sticky-change
इवेंट कहते हैं:
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;
});
डेमो के ठीक हो जाने पर, इस इवेंट का इस्तेमाल ड्रॉप शैडो को हेडर बनाने के लिए किया जाता है. इससे पेज के सबसे ऊपर मौजूद नया टाइटल भी अपडेट हो जाता है.
क्या आपको बिना स्क्रोल इवेंट के इफ़ेक्ट स्क्रोल करने हैं?
चलिए, कुछ शब्दावली बना लेते हैं, ताकि मैं बाकी पोस्ट में इन नामों का उल्लेख कर सकूं:
- स्क्रोल करने वाला कंटेनर - कॉन्टेंट एरिया (दिखने वाला व्यूपोर्ट) जिसमें "ब्लॉग पोस्ट" की सूची होती है.
- हेडर - हर सेक्शन में मौजूद नीले रंग का टाइटल, जिसमें
position:sticky
मौजूद हों. - स्टिकी सेक्शन - हर कॉन्टेंट सेक्शन. स्टिकी हेडर के नीचे स्क्रोल करने वाला टेक्स्ट.
- "स्टिकी मोड" - जब
position:sticky
, एलिमेंट पर लागू होता है.
यह जानने के लिए कि कौनसा हेडर "स्टिकी मोड" में चला जाता है, हमें स्क्रोल करने वाले कंटेनर के स्क्रोल ऑफ़सेट तय करने का कोई तरीका चाहिए. इससे हमें अभी दिख रहे हेडर की गिनती करने का तरीका मिल जाएगा. हालांकि, scroll
इवेंट के बिना ऐसा करना बहुत मुश्किल होता है :) दूसरी समस्या यह है कि
position:sticky
, एलिमेंट के ठीक हो जाने पर उसे लेआउट से हटा देता है.
इसलिए, स्क्रोल इवेंट के बिना, हम हेडर पर लेआउट से जुड़ी कैलकुलेशन नहीं कर सकते.
स्क्रोल की जगह तय करने के लिए डंबी DOM जोड़ें
अब हम scroll
इवेंट के बजाय, IntersectionObserver
का इस्तेमाल करेंगे. इससे यह तय किया जा सकेगा कि headers स्टिकी मोड में कब आते और कब बंद होते हैं. हर स्टिकी सेक्शन में दो नोड जोड़ने पर, स्क्रोल की पोज़िशन का पता लगाने के लिए, सबसे ऊपर और एक सबसे नीचे नोड जोड़े जाएंगे. जब ये
मार्कर कंटेनर में आते हैं और उससे बाहर निकलते हैं, तो उनकी दिखने की सेटिंग बदल जाती है और
इंटरसेक्शन ऑब्ज़र्वर एक कॉलबैक चालू कर देता है.
ऊपर और नीचे स्क्रोल करने के चार मामलों को कवर करने के लिए, हमें दो सेंसर की ज़रूरत है:
- नीचे की ओर स्क्रोल करना - जब हेडर सबसे ऊपर की ओर जाता है, तो वह चिपचिपा हो जाता है.
- नीचे की ओर स्क्रोल करना - हेडर सेक्शन में सबसे नीचे पहुंचने पर, स्टिकी मोड से बाहर निकल जाता है और उसका निचला सेंटीनल कंटेनर के ऊपर से गुज़रता है.
- ऊपर की ओर स्क्रोल करने से - हेडर जब ऊपर से देखने के लिए स्क्रोल किया जाता है, तब स्टिकी मोड छोड़ दिया जाता है.
- ऊपर की ओर स्क्रोल करना - हेडर जब ऊपर से देखने के लिए पीछे की ओर जाता है, तो वह चिपचिपा हो जाता है.
स्क्रीनकास्ट को एक से चार के क्रम में देखना फ़ायदेमंद होता है:
सीएसएस
संवेदनशील जानकारी को हर सेक्शन के सबसे ऊपर और सबसे नीचे रखा जाता है.
.sticky_sentinel--top
, हेडर के सबसे ऊपर होता है और
.sticky_sentinel--bottom
, सेक्शन के सबसे नीचे होता है:
: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;
}
इंटरसेक्शन ऑब्ज़र्वर सेट अप करना
इंटरसेक्शन ऑब्ज़र्वर एसिंक्रोनस रूप से किसी टारगेट एलिमेंट और दस्तावेज़ व्यूपोर्ट या पैरंट कंटेनर के इंटरसेक्शन में बदलावों को देखते हैं. हमारे मामले में, हम पैरंट कंटेनर के साथ वाले इंटरसेक्शन पर नज़र रखते हैं.
मैजिक सॉस IntersectionObserver
है. हर निगरानीी को
स्क्रोल कंटेनर में उसके इंटरसेक्शन को देखने के लिए,
IntersectionObserver
मिलता है. जब कोई भेजने वाला व्यक्ति व्यूपोर्ट में स्क्रोल करता है, तो हमें पता चलता है कि
हेडर ठीक हो गया है या स्टिकी होना बंद हो गया है. इसी तरह, जब कोई भेजने वाला व्यूपोर्ट
से बाहर निकल जाता है.
सबसे पहले, मुझे हेडर और फ़ुटर संदर्भ के लिए ऑब्ज़र्वर सेट अप करने हैं:
/**
* 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'));
इसके बाद, जब .sticky_sentinel--top
एलिमेंट स्क्रोल करने वाले कंटेनर के ऊपर (किसी भी दिशा में) से होकर गुज़रता है, तब मैंने फ़ायर करने के लिए एक ऑब्ज़र्वर जोड़ा.
observeHeaders
फ़ंक्शन मुख्य सेंटिनल बनाता है और उन्हें हर सेक्शन में जोड़ता है. ऑब्ज़र्वर, कंटेनर के सबसे ऊपर वाले सेंटिनल के इंटरसेक्शन का हिसाब लगाता है और
तय करता है कि वह व्यूपोर्ट में जा रहा है या उसे छोड़ रहा है. इस
जानकारी से तय होता है कि सेक्शन हेडर चिपक रहा है या नहीं.
/**
* 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));
}
ऑब्ज़र्वर को threshold: [0]
के साथ कॉन्फ़िगर किया जाता है, ताकि सेंसर के दिखते ही
यह कॉलबैक चालू हो जाए.
यह प्रोसेस, बॉटम सेंटिनल (.sticky_sentinel--bottom
) के लिए मिलती-जुलती है.
जब फ़ुटर, स्क्रोल करने वाले कंटेनर के निचले हिस्से से गुज़रते हैं, तब फ़ायर करने के लिए एक दूसरा ऑब्ज़र्वर बनाया जाता है. observeFooters
फ़ंक्शन, सेंटिनल नोड बनाता है और उन्हें हर सेक्शन में जोड़ता है. ऑब्ज़र्वर, कंटेनर के निचले हिस्से से सेंटिनल के इंटरसेक्शन का हिसाब लगाता है
और यह तय करता है कि वह अंदर आ रहा है या नहीं. इस जानकारी से तय होता है कि सेक्शन हेडर चिपक रहा है
या नहीं है.
/**
* 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));
}
ऑब्ज़र्वर को threshold: [1]
के साथ कॉन्फ़िगर किया गया है, ताकि पूरा नोड व्यू के अंदर होने पर इसकी कॉलबैक ट्रिगर हो जाए.
आखिर में, sticky-change
कस्टम इवेंट को ट्रिगर करने और
सेंटिंगल जनरेट करने के लिए, मेरे पास दो काम हैं:
/**
* @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);
}
हो गया!
फ़ाइनल डेमो
जब position:sticky
वाले एलिमेंट ठीक हो जाते हैं और scroll
इवेंट का इस्तेमाल किए बिना स्क्रोल इफ़ेक्ट जोड़े जाते हैं, तब हमने कस्टम इवेंट बनाया है.
नतीजा
मैं अक्सर सोचती थी कि पिछले कई सालों में बने scroll
इवेंट पर आधारित यूज़र इंटरफ़ेस (यूआई) पैटर्न को बदलने के लिए, IntersectionObserver
एक मददगार टूल होगा या नहीं. पता चला कि इसका जवाब हां है और नहीं. IntersectionObserver
API के सिमेंटिक्स
की वजह से, हर काम में इसका इस्तेमाल करना मुश्किल हो जाता है. लेकिन जैसा कि
मैंने यहां दिखाया है, कुछ दिलचस्प तकनीकों के लिए इसका इस्तेमाल किया जा सकता है.
क्या स्टाइल में बदलावों का पता लगाने का कोई और तरीका है?
दरअसल ऐसा नहीं है. हमें किसी DOM एलिमेंट की स्टाइल में हुए बदलावों पर नज़र रखने का तरीका चाहिए था. माफ़ करें, वेब प्लैटफ़ॉर्म के एपीआई में ऐसा कुछ नहीं है जिसकी मदद से, स्टाइल में किए गए बदलाव देखे जा सकें.
MutationObserver
सबसे पहली पसंद होगी. हालांकि, यह ज़्यादातर मामलों में काम नहीं करता. उदाहरण के लिए, डेमो में हमें किसी एलिमेंट में sticky
क्लास जोड़े जाने पर कॉलबैक मिलेगा. हालांकि, एलिमेंट के कंप्यूट किए गए स्टाइल में बदलाव होने पर, हमें कॉलबैक नहीं मिलेगा.
याद रखें कि पेज लोड होने पर, sticky
क्लास का एलान पहले ही कर दिया गया था.
आने वाले समय में,
"स्टाइल म्यूटेशन ऑब्ज़र्वर"
म्यूटेशन ऑब्ज़र्वर का एक्सटेंशन, किसी एलिमेंट के कंप्यूट किए गए स्टाइल में हुए बदलावों पर नज़र रखने में मददगार हो सकता है.
position: sticky
.