بازخورد هم‌زمان میزان کردن پیمایش

نمای کلی

کاربران انتظار دارند هنگام تعامل با عناصر میانای کاربر مانند گردونه‌ها یا گالری‌ها، بازخورد دیداری فوری دریافت کنند. میزان کردن پیمایش سنتی فقط پس‌از تکمیل اشاره پیمایش و ثابت شدن عنصر بازخورد ارائه می‌دهد. بااستفاده از «رویدادهای میزان کردن پیمایش»، به‌ویژه 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 به‌طور خودکار فعال نخواهند شد. بررسی هندسی یک‌باره‌ای را اجرا کنید تا واسط کاربر با موقعیت پیمایش اولیه همگام‌سازی شود.

// 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 پشتیبانی نمی‌کنند، واسط کاربر به‌طور پیش‌فرض بازخورد مشتاقانه درطول اشاره پیمایش ارائه نمی‌دهد و واسط کاربر پیوندشده با محتوا همگام‌سازی نخواهد شد.

اجباری: برای مرورگرهایی که پشتیبانی نمی‌کنند، یک جایگزین ارائه دهید، درغیراین‌صورت واسط کاربر پیوندشده با محتوا همگام‌سازی نخواهد شد.

باید ماهیت هم‌زمان scrollsnapchanging را با شنونده رویداد scroll همراه با requestAnimationFrame و محاسبات فاصله هندسی شبیه‌سازی کنید تا نزدیک‌ترین هدف چسبیدن را درحالی‌که کاربر فعالانه پیمایش می‌کند تعیین کنید.

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 را درطول اجرایشان فعال می‌کنند، واسط کاربر درطول پویانمایی پیمایش به‌طور روان همگام‌سازی می‌شود و به منطق سفارشی اضافی نیاز ندارد.