এক নজরে
ক্যারোজেল বা গ্যালারির মতো UI এলিমেন্টের সাথে ইন্টার্যাক্ট করার সময় ব্যবহারকারীরা তাৎক্ষণিক ভিজ্যুয়াল ফিডব্যাক আশা করেন। স্ক্রল জেসচার সম্পূর্ণ হওয়ার এবং এলিমেন্ট সেটেল হওয়ার পরে ট্র্যাডিশনাল স্ক্রল স্ন্যাপ শুধুমাত্র ফিডব্যাক প্রদান করে। স্ক্রল স্ন্যাপ ইভেন্ট, বিশেষ করে scrollsnapchanging ব্যবহার করে, আপনি স্ক্রল জেসচার চলাকালীন রিয়েল-টাইম ফিডব্যাক দিতে পারেন, ব্যবহারকারী তার টাচ বা মাউস রিলিজ করার আগে মুলতুবি থাকা স্ন্যাপ টার্গেট হাইলাইট করতে পারেন।
প্রয়োগ
১. scrollsnapchanging-এ শুনুন
স্ক্রল কন্টেনারে scrollsnapchanging-এর জন্য ইভেন্ট লিসনার অ্যাটাচ করুন। ব্রাউজার যখন নির্ধারণ করে যে নতুন স্ন্যাপ টার্গেট বেছে নেওয়া হতে পারে, তখন এই ইভেন্ট ট্রিগার হয়।
const container = document.querySelector('#gallery');
const thumbnails = document.querySelectorAll('.thumbnail');
const items = document.querySelectorAll('.gallery-item');
container.addEventListener('scrollsnapchanging', (event) => {
// Highlight pending snap target during scroll for real-time feedback.
const pendingTarget = event.snapTargetInline;
const index = [...items].indexOf(pendingTarget);
if (index === -1 || !thumbnails[index]) return;
// Use lightweight class toggle to avoid layout thrashing during rapid events.
// Note: aria-current is NOT toggled here. It tracks the settled "current"
// item, which is updated in the scrollsnapchange handler below.
thumbnails.forEach((thumb) => thumb.classList.remove('pending'));
thumbnails[index].classList.add('pending');
});
এই উদাহরণে snapTargetInline ব্যবহার করা হয়েছে কারণ গ্যালারি অনুভূমিকভাবে স্ক্রল করে। আপনার স্ক্রল কন্টেনার উল্লম্বভাবে স্ক্রল করলে, পরিবর্তে snapTargetBlock ব্যবহার করুন।
২. scrollsnapchange-এ শুনুন
স্ক্রল জেসচার সম্পূর্ণ হলে এবং এলিমেন্টটি আসলে স্ন্যাপ করলে, স্টেটটি চূড়ান্ত করতে, scrollsnapchange ইভেন্টটি শুনুন। চূড়ান্ত অ্যাক্টিভ স্ট্যাটাস প্রতিষ্ঠা করার জন্য এটি প্রয়োজন।
container.addEventListener('scrollsnapchange', (event) => {
// Promote pending state to active on scroll completion.
const snappedTarget = event.snapTargetInline;
const index = [...items].indexOf(snappedTarget);
if (index === -1 || !thumbnails[index]) return;
// Establish final active state and clean up pending.
thumbnails.forEach((thumb) => {
thumb.classList.remove('pending', 'active');
thumb.removeAttribute('aria-current');
});
thumbnails[index].classList.add('active');
thumbnails[index].setAttribute('aria-current', 'true');
});
৩. প্রাথমিক অবস্থা সিঙ্ক করুন
পৃষ্ঠা লোড হওয়ার সময়, ব্রাউজার স্ক্রল পজিশন ফিরিয়ে আনতে পারে (যেমন, ইতিহাস ট্রাভার্সাল বা অ্যাঙ্কর লিঙ্কের মাধ্যমে)। scrollsnapchange বা scroll ইভেন্টের কোনওটিই অটোমেটিক ফায়ার হবে না। প্রাথমিক স্ক্রল পজিশনের সাথে UI সিঙ্ক করতে, একবার জিওমেট্রিক চেক রান করুন।
// Note: For item.offsetLeft to be relative to the container,
// the container MUST be the offsetParent (e.g., `position: relative`).
const findClosestItemIndex = () => {
// Center-distance assumes scroll-snap-align: center on items.
// For start-aligned snap, compare scrollLeft to item.offsetLeft directly.
const containerCenter = container.scrollLeft + container.clientWidth / 2;
let closestIndex = 0;
let minDistance = Infinity;
items.forEach((item, index) => {
const itemCenter = item.offsetLeft + item.offsetWidth / 2;
const distance = Math.abs(containerCenter - itemCenter);
if (distance < minDistance) {
minDistance = distance;
closestIndex = index;
}
});
return closestIndex;
};
const initActiveItem = () => {
const closestIndex = findClosestItemIndex();
if (!thumbnails[closestIndex]) return;
thumbnails.forEach((thumb) => {
thumb.classList.remove('pending', 'active');
thumb.removeAttribute('aria-current');
});
thumbnails[closestIndex].classList.add('active');
thumbnails[closestIndex].setAttribute('aria-current', 'true');
};
if (document.readyState === 'complete') {
initActiveItem();
} else {
window.addEventListener('load', initActiveItem, { once: true });
}
ফলব্যাক স্ট্র্যাটেজি
যেসব ব্রাউজারে scrollsnapchanging কাজ করে না, ডিফল্ট হিসেবে স্ক্রল জেসচার চলাকালীন UI ইগার ফিডব্যাক প্রদান করবে না এবং লিঙ্ক করা UI কন্টেন্ট থেকে ডিসিঙ্ক্রোনাইজ হয়ে যাবে।
বাধ্যতামূলক: যেসব ব্রাউজারে কাজ করে না সেগুলির জন্য ফলব্যাক প্রদান করুন, অন্যথায় লিঙ্ক করা UI কন্টেন্টের সাথে সিঙ্ক করা থাকবে না।
ব্যবহারকারী স্ক্রল করার সময় সবচেয়ে কাছের স্ন্যাপ টার্গেট নির্ধারণ করতে requestAnimationFrame এবং জ্যামিতিক দূরত্ব গণনার সাথে scroll ইভেন্ট লিসেনার ব্যবহার করে scrollsnapchanging-এর রিয়েল-টাইম প্রকৃতি সিমুলেট করুন।
if ('onscrollsnapchanging' in Element.prototype) {
// Use native scroll snap events
} else {
// Fallback: use scroll + requestAnimationFrame for eager feedback
// (assumes the same container, thumbnails, items defined in step 1)
let scrollTimeout;
let rafId = null;
const promotePendingToActive = () => {
const closestIndex = findClosestItemIndex();
if (!thumbnails[closestIndex]) return;
thumbnails.forEach((thumb) => {
thumb.classList.remove('pending', 'active');
thumb.removeAttribute('aria-current');
});
thumbnails[closestIndex].classList.add('active');
thumbnails[closestIndex].setAttribute('aria-current', 'true');
};
container.addEventListener('scroll', () => {
if (rafId) return;
rafId = requestAnimationFrame(() => {
rafId = null;
const closestIndex = findClosestItemIndex();
if (!thumbnails[closestIndex]) return;
// DO NOT forget to clean up stale pending classes
thumbnails.forEach((thumb) => thumb.classList.remove('pending'));
thumbnails[closestIndex].classList.add('pending');
});
// Debounce fallback for browsers that don't support scrollend
clearTimeout(scrollTimeout);
scrollTimeout = setTimeout(promotePendingToActive, 100);
}, { passive: true });
// Fallback: use Baseline `scrollend` event to promote pending to active cleanly where supported
container.addEventListener('scrollend', () => {
clearTimeout(scrollTimeout);
promotePendingToActive();
});
}
জ্যামিতিক scroll + requestAnimationFrame ফলব্যাক, প্রোগ্রাম্যাটিক স্ক্রলিং সঠিকভাবে ম্যানেজ করা সহ নেটিভ স্ন্যাপ প্রেডিকশনের আচরণকে ঘনিষ্ঠভাবে অনুকরণ করে। যেহেতু scrollIntoView-এর মতো ফাংশন স্বাভাবিকভাবেই তাদের এক্সিকিউশনের সময় scroll ইভেন্ট ফায়ার করে, তাই UI অতিরিক্ত কাস্টম লজিকের প্রয়োজন ছাড়াই স্ক্রল অ্যানিমেশন জুড়ে মসৃণভাবে সিঙ্ক্রোনাইজড থাকবে।