Programatik kaydırma tamamlandıktan sonra efektleri tetikleme

Programatik olarak bir bölüme veya öğeye kaydırırken (özellikle behavior: 'smooth' ile) hemen bir takip işlemi tetiklemek (ör. vurgulama durumunu yanıp söndürme, giriş animasyonu başlatma veya odağı taşıma), hedef henüz ekran dışındayken veya kaydırma işlemi devam ederken efektin çalışmasına neden olur.

Daha önce geliştiriciler, setTimeout() ile kaydırma süresini tahmin etmek (uzun kaydırmalarda çok erken, kısa kaydırmalarda ise çok geç tetiklenir) veya bir scrollend etkinlik işleyiciyi yedek zamanlayıcıyla manuel olarak Promise içine sarmak (scrollend, hedef zaten görünür durumdayken ve kaydırma yapılmadığında tetiklenmediği için) zorunda kalıyordu.

Element (scrollIntoView(), scroll(), scrollTo(), scrollBy()) ve Window (scroll(), scrollTo(), scrollBy()) üzerindeki programatik kaydırma yöntemleri, kaydırma işlemi tamamlandığında çözümlenen bir Promise döndürür. Hedef zaten istenen kaydırma konumundaysa ve kaydırma gerekmiyorsa döndürülen Promise, askıda kalmadan hemen çözümlenir.

Uygulama

  1. İşleyiciyi async olarak işaretleyin: Kaydırmayı tetikleyen etkinlik işleyici veya gezinme işleyicisi için async işlevini kullanın.
  2. Doğrudan kaydırma yöntemini bekleme: await element.scrollIntoView({ behavior: 'smooth', ... }) (veya await element.scrollTo(...), await window.scrollTo(...) vb.) yöntemini çağırın. Tarayıcı, kaydırma animasyonunu tamamlayıp kaydırma konumu sabitlenene kadar yürütme duraklatılır.
  3. Kaydırma sonrası efekti uygulama: await ifadesi tamamlandıktan sonra vurgulama sınıfını açıp kapatın, animasyonu başlatın veya odağı taşıyın.

Kesintiye Uğrayan Kaydırmalar ve Odak Yönetimi

  • Hızlı/kesintili kaydırmaları işleme: Devam eden bir kolay kaydırma işlemi tamamlanmadan önce aynı kaydırma kapsayıcısında yeni bir kaydırma işlemi başlarsa tarayıcı, devam eden kolay kaydırma işlemini iptal eder ve bu kapsayıcıdaki tüm bekleyen kaydırma vaatlerini çözer (reddetmez). Kullanıcı hızlıca tıkladığında, değiştirilen kaydırmalar için kaydırma sonrası efekti engellemeniz gerekiyorsa artan bir işlem kimliğini izleyin ve daha yeni bir kaydırma başlatıldıysa await işleminden sonra erken dönün:
let currentScrollId = 0;
const sections = document.querySelectorAll('section');

async function scrollToAndHighlight(targetElement) {
  const scrollId = ++currentScrollId;
  sections.forEach((sec) => sec.classList.remove('highlight'));

  await targetElement.scrollIntoView({ behavior: 'smooth', block: 'center' });

  // Abort follow-up effect if another scroll was triggered while awaiting
  if (scrollId !== currentScrollId) return;

  targetElement.classList.add('highlight');
}
  • Kaydırdıktan sonra hedefe odaklanma: await targetElement.scrollIntoView(...) işleminden sonra klavye odağını hedef öğeye (veya tabindex="-1" ile içindeki bir başlığa) taşırken { preventScroll: true } değerini focus() öğesine iletin. Böylece odak çağrısı ikinci bir rakip kaydırma işlemi başlatmaz:
await targetElement.scrollIntoView({ behavior: 'smooth', block: 'center' });
targetHeading.focus({ preventScroll: true });

Kısıtlamalar ve En İyi Uygulamalar

  • ZORUNLU: setTimeout() ile kaydırma sürelerini tahmin etmek yerine doğrudan scrollIntoView(), scroll(), scrollTo() veya scrollBy() tarafından döndürülen Promise değerini bekleyin.
  • Birincil uygulamanız olarak scrollend öğesini manuel bir new Promise() içine SARMAYIN. Hedef zaten görünür durumdayken ve kaydırma mesafesi kat edilmediğinde scrollend tetiklenmez. Kaydırma yöntemi ise bu durumda otomatik olarak çözümlenir.
  • DO: Bir kaydırma yöntemini bekledikten sonra .focus() yöntemini çağırırken { preventScroll: true } değerini iletin. Böylece tarayıcı ikinci kez atlamaz veya kaydırmaz.
  • DO: Guard post-scroll effects with a request token if rapid user interactions can trigger overlapping smooth scrolls on the same container, since interrupting a smooth scroll resolves (rather than rejects) the previous promise.
  • Kök kaydırıcıyla ilgili bilinen Chrome hatası: Chrome'da, dokümanın kök kaydırıcısı kaydırılırken scrollIntoView() tarafından döndürülen Promise çok erken çözümlenir. İç içe yerleştirilmiş bir kaydırma kapsayıcısında scrollIntoView() beklenmesi, beklendiği gibi çalışır. Bu hatayı düzeltmek için içeriğinizi div ve overflow-y: auto gibi bir kaydırma kapsayıcısına sarmayın.

Yedek stratejiler

Kaydırma yöntemi vaatlerini desteklemeyen tarayıcılarda scrollIntoView(), scroll(), scrollTo() ve scrollBy(), Promise yerine undefined döndürür.

  • Progresif geliştirme (varsayılan await davranışı): await undefined, bir sonraki mikro görevde hemen çözümlenen geçerli bir JavaScript olduğundan await element.scrollIntoView({ behavior: 'smooth' }) yazmak, desteklenmeyen tarayıcılarda hata oluşturmaz. Bunun yerine, kaydırma işlemi başlar başlamaz kaydırma sonrası kodu çalıştırarak sorunsuz bir şekilde çalışır. Geçici vurgulama gibi kritik olmayan görsel süslemeler için bu genellikle yeterlidir.
  • Güvenilir yedek (scrollend + zaman aşımı): Baseline hedefiniz scroll-promises özelliğini desteklemiyorsa ve kaydırma sonrası işlemin eski tarayıcılarda kaydırma işlemi tamamlanana kadar beklemesi gerekiyorsa döndürülen değerin Promise (result instanceof Promise) olup olmadığını kontrol edin. undefined olduğunda, öğe zaten görünümdeyse ve kaydırma işlemi gerçekleşmiyorsa kısa bir yedek zaman aşımı ile kaydırma kapsayıcısında scrollend etkinliğini bekleyin:
function scrollIntoViewAsync(element, options, scroller = window) {
  const result = element.scrollIntoView(options);

  // Feature detect: in supporting browsers, scrollIntoView() returns a Promise
  if (result instanceof Promise) {
    return result;
  }

  // Fallback for browsers where scrollIntoView() returns undefined
  return new Promise((resolve) => {
    let timeoutId;

    const finish = () => {
      clearTimeout(timeoutId);
      scroller.removeEventListener('scrollend', finish);
      scroller.removeEventListener('scroll', onScroll);
      resolve();
    };

    const onScroll = () => {
      // Restart the idle timer on every scroll event. If scrollend never fires
      // (browsers without scrollend support), this resolves once scrolling goes idle.
      clearTimeout(timeoutId);
      timeoutId = setTimeout(finish, 100);
    };

    scroller.addEventListener('scrollend', finish, { once: true });
    scroller.addEventListener('scroll', onScroll, { passive: true });

    // If no scroll event fires within the idle window (e.g., already in view), resolve.
    // 100ms is an example value; it only needs to exceed the gap between scroll events.
    timeoutId = setTimeout(finish, 100);
  });
}