İki Boyutlu Kaydırma

Kullanıcı iki boyutlu bir kaydırma kapsayıcısını kaydırdığında tarayıcılar genellikle kaydırma ekseni kilitleme sezgisel yöntemini ("raylı sistem" olarak da bilinir) uygular. Bir hareket, bir eksen boyunca dikey eksene kıyasla önemli ölçüde daha fazla hareketle başlarsa tarayıcı, kaydırmayı bu tek yatay veya dikey eksene kilitler ve dikey deltaları genellikle hareketin geri kalanı için yok sayar.

Eksen kilitleme, tek boyutlu okuma akışlarında yanlışlıkla yana kaymayı önlerken etkileşimli haritalar, yakınlaştırılmış resimler, sonsuz tuvaller, düğüm grafikleri ve büyük veri tabloları gibi iki boyutlu kaydırma yüzeylerinde sürtünme oluşturur. Kilit, hareket boyunca devam ettiğinden iki yönlü kaydırma işlemlerini etkiler. Bu, her iki eksenin aynı anda kaydırılması (çapraz kaydırma) veya tek bir hareket içinde birbiri ardına kaydırılması (ör. kavisli, dairesel veya L şeklindeki yollar) için geçerlidir. CSS scroll-axis-lock özelliği, bu sezgisel yaklaşımı doğrudan yerel bir kaydırma kapsayıcısında devre dışı bırakır. Böylece, oluşturucu iş parçacığı kaydırma, momentum fiziği, yerel kaydırma çubukları ve klavye ile gezinme korunurken kısıtlama olmadan iki boyutlu kaydırma yapılabilir.

Uygulama

1. İki boyutlu kaydırma kapsayıcısında eksen kilitlemeyi devre dışı bırakma ve aşırı kaydırmayı sınırlama

scroll-axis-lock: none özelliğini doğrudan iki boyutlu kaydırma kapsayıcısını (overflow: auto veya overflow: scroll) oluşturan öğeye uygulayın. 2D kaydırma kapsayıcısı, varsayılan auto değeri altında her iki eksendeki kaydırma deltalarını güncelleyebilir. auto, bir hareket ağırlıklı olarak bir eksen boyunca başladığında tarayıcının ray sezgisinin devreye girmesini sağlar. scroll-axis-lock: none ayarı, dokunmatik ekranlar, dokunmatik yüzeyler ve fare tekerleklerinde bu sınırlamanın devreye girmesini engeller.

Kaydırma kapsayıcısında scroll-axis-lock: none ile overscroll-behavior: none (veya overscroll-behavior: contain) değerlerini eşleştirin. Böylece, bir kenara ulaşıldığında kaydırma işlemi üst sayfaya zincirlenmez veya yanlışlıkla tarayıcıda aşırı kaydırma gezinmeleri (ör. yatay geri/ileri kaydırma gezinmesi veya yenilemek için kaydırma) tetiklenmez.

2. Klavye odağını ve erişilebilirliği koruma

2D kaydırma yüzeyinde genellikle odaklanılabilir alt denetimler (ör. harita veya yakınlaştırılmış resim) bulunmadığından klavye kullanıcılarının kapsayıcıya odaklanabilmesi ve ok tuşlarıyla kaydırabilmesi için tabindex="0", uygun bir role ve erişilebilir etiket ile görünür bir :focus-visible göstergesi ekleyin.

Örnek Kod

<!-- Make the 2D scroll container keyboard-focusable and labeled so keyboard
     users can focus the viewport and pan in all directions with Arrow keys. -->
<div class="pan-viewport" tabindex="0" role="region" aria-label="Interactive canvas">
  <div class="pan-surface">
    <!-- 2D overflowing content (map tiles, zoomed image, canvas nodes, or grid) -->
  </div>
</div>
.pan-viewport {
  /* Example viewport dimensions; adjust to fit your layout. */
  inline-size: 100%;
  block-size: 500px;

  /* MANDATORY: Establish a scroll container that can overflow on both axes. */
  overflow: auto;

  /* MANDATORY: Prevent browser scroll axis-locking ("railing") from kicking in
     so 2D gestures never get locked to a single axis. */
  scroll-axis-lock: none;

  /* Contain scrolling and prevent accidental swipe navigations at edges
     (use `none` or `contain`). */
  overscroll-behavior: none;
}

/* Provide a clear focus indicator when keyboard users focus the viewport. */
.pan-viewport:focus-visible {
  outline: 3px solid #005fcc;
  outline-offset: 2px;
}

.pan-surface {
  /* Example surface dimensions larger than .pan-viewport in both axes. */
  inline-size: 3000px;
  block-size: 3000px;
}

En İyi Uygulamalar ve Kısıtlamalar

  • Üst sarmalayıcıya değil, kaydırma kapsayıcısına uygulayın: scroll-axis-lock devralınmaz. Bu nedenle, sarmalayıcıda ayarlanması alt kaydırma kapsayıcılarını etkilemez.
  • Aşırı kaydırmayı kontrol altına alın ve yanlışlıkla gezinmeyi önleyin: 2D kaydırma kapsayıcısına overscroll-behavior: none veya overscroll-behavior: contain ekleyin. Böylece, bir sınıra kaydırma işlemi üst sayfaya bağlanmaz veya tarayıcıda geri/ileri kaydırma gezinme ve yenilemek için kaydırma hareketlerini tetiklemez.
  • İç içe yerleştirilmiş tek boyutlu paneller otomatik olarak daraltılmış durumda kalır: scroll-axis-lock devralınmadığından, .pan-viewport içine yerleştirilmiş bir kenar çubuğu veya denetleyici paneli varsayılan scroll-axis-lock: auto davranışını korur.
  • Standart dikey feed'lerde, makalelerde ve tek boyutlu listelerde auto seçeneğini etkin tutun: Eksen kilitleme, tek yönlü içeriklerde istenmeyen dikey kaymayı önler.
  • touch-action: none değerini koşulsuz olarak ayarlamayın: touch-action, tarayıcının dokunma hareketlerini işleyip işlemeyeceğini kontrol eder (dokunmatik yüzey veya tekerlek girişi üzerinde etkisi yoktur). Bu nedenle, bu ayarın yapılması yerel birleştirici dokunmatik kaydırmayı devre dışı bırakır. JavaScript işaretçi yedeğini uygularken yalnızca @supports not (scroll-axis-lock: none) içinde uygulayın.
  • transform ile değil, scrollLeft / scrollTop ile kaydırın: Yüzeyi transform ile çevirmek, öğenin kaydırma kapsayıcısı olarak çalışmasını engeller. Bu da yerel kaydırma çubuklarını, sınır sıkıştırmayı, klavye ok tuşuyla kaydırmayı, scroll etkinliklerini, IntersectionObserver ve kaydırmaya dayalı animasyonları bozar.

Yedek stratejiler

Uygulamanızın gereksinimlerine göre progresif geliştirme ile JavaScript yedek arasında seçim yapın:

1. seçenek: Progresif geliştirme (JavaScript yok)

Yerel birleştirici iş parçacığı dokunarak kaydırma, ataletle kaydırma momentumu ve sıkıştırarak yakınlaştırma özelliklerini korumak, desteklenmeyen tarayıcılarda eksen kilitlemeyi ortadan kaldırmaktan daha önemliyse progresif geliştirme kullanın:

  • Destekleyen tarayıcılar: Tarayıcı, kaydırma hareketleri sırasında 2D kaydırma kapsayıcısında eksen kilitleme ("ray") sezgisinin devreye girmesini engeller.
  • Desteklenmeyen tarayıcılar: scroll-axis-lock: none değerini tanımayan tarayıcılar, bildirimi güvenli bir şekilde yoksayar ve varsayılan auto 2D kaydırma davranışını korur. Bu davranış, her iki eksende de yerel olarak kaydırma yaparken ağırlıklı olarak tek bir eksen boyunca başlayan hareketleri kısıtlar.

2. seçenek: JavaScript yedek (scrollLeft/scrollTop ile wheel + pointermove)

Tüm tarayıcılarda sınırsız iki boyutlu kaydırma gerekiyorsa wheel ve işaretçi sürükleme hareketlerini koşullu olarak engellemek için @supports not (scroll-axis-lock: none) ve CSS.supports('scroll-axis-lock', 'none') kullanın.

wheel (e.deltaX, e.deltaY) ve pointermove (e.clientX, e.clientY), tarayıcının kaydırma motoru eksen kilitleme uygulamadan önce ham, kilidi açılmış 2D deltaları gösterdiğinden bu deltaları doğrudan scrollLeft ve scrollTop'ye uygulayabilirsiniz:

  • CSS transform yerine neden scrollLeft/scrollTop?: scrollLeft ve scrollTop güncellendiğinde öğe gerçek bir kaydırma kapsayıcısı olarak kalır. Tarayıcı, kaydırma konumlarını otomatik olarak [0, scrollWidth - clientWidth] ve [0, scrollHeight - clientHeight] ile sınırlandırır, yerel kaydırma çubukları senkronize kalır, klavyedeki ok tuşlarıyla kaydırma işlemi çalışmaya devam eder ve scroll etkinlikleri ile IntersectionObserver normal şekilde tetiklenir.
  • Ödünler: { passive: false } ile wheel'yi kesmek, kaydırmayı ana iş parçacığına taşır ve dokunmatik ekranlarda touch-action: none ayarını yapmak, pointerup'ten sonra yerel ataletle kaydırma momentumunu (ve yerel sıkıştırarak yakınlaştırmayı) devre dışı bırakır.
/* Only disable native touch scrolling when scroll-axis-lock is unsupported
   and the JavaScript pointer fallback below is active. */
@supports not (scroll-axis-lock: none) {
  .pan-viewport {
    touch-action: none;
  }
}
// Only attach the main-thread gesture fallback when native support is missing
if (!CSS.supports('scroll-axis-lock', 'none')) {
  const viewport = document.querySelector('.pan-viewport');
  let lastPointer = null;

  // 1. Trackpad and mouse wheel fallback
  viewport.addEventListener(
    'wheel',
    (e) => {
      e.preventDefault();
      viewport.scrollLeft += e.deltaX;
      viewport.scrollTop += e.deltaY;
    },
    { passive: false },
  );

  // 2. Touch and pointer drag fallback
  viewport.addEventListener('pointerdown', (e) => {
    // Ignore non-primary buttons and clicks on native scrollbars
    const rect = viewport.getBoundingClientRect();
    if (
      !e.isPrimary ||
      e.button !== 0 ||
      e.clientX - rect.left - viewport.clientLeft >= viewport.clientWidth ||
      e.clientY - rect.top - viewport.clientTop >= viewport.clientHeight
    ) {
      return;
    }
    lastPointer = { x: e.clientX, y: e.clientY };
    viewport.setPointerCapture(e.pointerId);
  });

  viewport.addEventListener('pointermove', (e) => {
    if (!lastPointer) return;
    viewport.scrollLeft -= e.clientX - lastPointer.x;
    viewport.scrollTop -= e.clientY - lastPointer.y;
    lastPointer = { x: e.clientX, y: e.clientY };
  });

  const endPointerPan = () => {
    lastPointer = null;
  };
  viewport.addEventListener('pointerup', endPointerPan);
  viewport.addEventListener('pointercancel', endPointerPan);
}