holodepth

HTML5 Canvas · Girdi & etkileşim

Klavye girdisi: tuval odağı ve klavye olayları

Canvas öğesi, görünür bir çizim yüzeyi olduğu hâlde varsayılan olarak klavye odağı almaz; tarayıcı tuş olaylarını yalnızca odakta olan öğeye veya yakalama ( capture ) aşamasında dinleyiciye iletir. Basit bir çizim uygulamasında ok tuşlarıyla imleci kaydırmak, space ile pano açmak veya kısayollarla araç değiştirmek istiyorsanız önce tuvalin ( veya sarmalayıcısının ) klavye için odaklanabilir olduğundan emin olmalı, ardından keydown / keyup sözleşmesini doğru hedefte dinlemelisiniz — bu sayfa modeli Canvas 2D etkileşimine göre kurulur; fare ve tekerleğin ayrıntısı Fare girdisi ve Pointer sistemi komşu sayfalarında tutulur.

Hareket vektörünü her tuş vuruşunda değil, requestAnimationFrame ile birleştirmek için Update vs render ve Delta time sayfalarına köprü kurulur; böylece tuşlar oyun mantığında «durum», çizim ise «kare başına bir özet» kalır.

Özet: sık klavye olayları

Olay Ne zaman? Canvas’ta tipik kullanım
keydown Tuşa basıldı; basılı tutunca tekrar ( repeat ) ile de gelebilir Anlık eylem, basılı tutma başlangıcı, kısayol
keyup Tuş bırakıldı Basılı kümeden çıkarma, sürüklemeyi bitirme
keypress Deprecated — yazdırılabilir tuş odaklı eski model Yeni kodda kullanmayın; keydown tercih edin

Odak ve tuval hedefi: tabindex, click-to-focus, görünür geri bildirim

Çoğu tarayıcıda <canvas> öğesi varsayılan olarak klavye odağına aday değildir. Kullanıcı tuşa bastığında sizin dinleyicinizin çalışması için ya öğenin tabindex="0" ( veya pozitif stratejik sıra ) ile tab sırasına alınması ya da programlı focus() çağrısı gerekir — aksi halde olaylar belgedeki başka bir odakta ( örn. adres çubuğu, form, düğme ) tüketilir. Tuvali tıklayınca odaklamak yaygın bir desendir; erişilebilirlik için odak halkasını ( focus ring ) CSS ile tamamen yok etmek yerine, tuvalin etrafında hafif bir çerçeve veya üst başlıkta «klavye kısayolları için tıklayın» ipucu sunmak daha güvenli olur.

focusin / focusout ile hangi panelin aktif olduğunu izleyebilir, yalnız tuval odaktayken hareket veya kısayol işleyebilirsiniz. Bu, sayfa üzerinde metin girişi alanı varken kazara ok tuşlarının tuvali kaydırması gibi çakışmaları azaltır. Ağır iş yükünü doğrudan keydown içinde değil, aşağıda anlatıldığı gibi durum önbelleğine yazıp kare döngüsünde değerlendirmek daha sürdürülebilirdir.

/**
 * Tuvali klavye odağına aday yapar; tıklayınca odaklar.
 * dispose: sayfa/komponent kapanırken çağrılmalıdır.
 */
function attachCanvasKeyboardFocus(canvas) {
  if (!canvas.hasAttribute('tabindex')) canvas.tabIndex = 0;

  const ac = new AbortController();
  const { signal } = ac;

  canvas.addEventListener(
    'pointerdown',
    () => {
      canvas.focus({ preventScroll: true });
    },
    { signal },
  );

  return () => ac.abort();
}

code, key ve repeat: fiziksel düzenden bağımsız okuma

KeyboardEvent.code ( örn. KeyW, ArrowLeft, Space ) fiziksel veya konumsal tuşu tarif eder; klavye düzeni ( QWERTY / AZERTY ) değişse bile aynı fiziksel tuş aynı code ile gelme eğilimindedir — bu yüzden yön ve hareket mantığında code tercih etmek daha az sürprizlidir. KeyboardEvent.key ise kullanıcıya «ne karakter üretildi?» sorusunun cevabıdır; büyük harf, ölü tuş ( dead keys ) ve IME altında farklılaşır.

e.repeat === true otomatik tekrar ( tuşa uzun basınca işletim sisteminin ürettiği tekrar ) demektir; tek atımlık eylemlerde ( sil, kaydet, araç değiştir ) bu bayrağı filtrelemek çift tetiklemeyi önler. Oyunlarda ise sürekli hareket için keydown ile «basılı» işaretleyip keyup ile temizlemek, tekrar olaylarına güvenmekten daha deterministiktir.

shiftKey, ctrlKey, altKey, metaKey bayrakları fare olaylarıyla aynı ailedendir; kısayol seçerken macOS üzerinde metaKey, Windows üzerinde sıklıkla ctrlKey beklenir — kullanıcıya metin olarak «Ctrl/⌘+Z» gibi ifadelerle duyurmak, kodda ise her iki modify bayrağını kabul etmek genelde doğru yaklaşımdır.

Basılı tuş durumu: Set, blur ve sekmeyi kaybetme

Kare tabanlı canvas uygulamalarında tuşları «basıldı mı?» biçiminde modellemek için küçük bir Set<string> ( anahtar olarak e.code ) yaygındır. keydown içinde add, keyup içinde delete — hareket vektörünü her karede bu kümeden türetin. Böylece otomatik tekrar hızına bağlı kalmaz ve aynı karede hem «sol» hem «yukarı» basılı olabilir.

Kullanıcı sekmeyi değiştirirken ( blur ) veya görünürlük düşerken ( document.visibilityState ) basılı kümenin sıfırlanması gerekir; aksi halde «odak yokken bile sanal olarak sürekli basılı tuş» sapması görülür. Tam ekran çıkışı veya tarayıcı geliştirici araçlarını açmak da benzer şekilde tuşların «asılı» kalmasına yol açabilir — bu yüzden yedek olarak window düzeyinde blur dinleyip küme temizliği veya en azından tüm «hareket» bayraklarını düşürmek iyi bir sigortadır.

/**
 * hedef: odaklanabilir öğe (canvas veya sarmalayıcı).
 * getKeys(): okuma için canlı Set referansı (dışarı kopyalamayın).
 */
function attachKeyboardHeldState(hedef, options = {}) {
  const keys = new Set();
  const ac = new AbortController();
  const { signal } = ac;

  const clearAll = options.onClearAll ?? (() => keys.clear());

  hedef.addEventListener(
    'keydown',
    (e) => {
      keys.add(e.code);
    },
    { signal },
  );

  hedef.addEventListener(
    'keyup',
    (e) => {
      keys.delete(e.code);
    },
    { signal },
  );

  window.addEventListener('blur', clearAll, { signal });
  document.addEventListener('visibilitychange', () => {
    if (document.visibilityState === 'hidden') clearAll();
  }, { signal });

  return {
    dispose: () => ac.abort(),
    getKeys: () => keys,
  };
}

preventDefault ve tarayıcı kısayollarıyla yarış

Ara boşluk ile sayfa kaydırması, Ctrl+R ile yenileme gibi davranışlar tarayıcıya aittir; preventDefault() çağırmak bu zinciri kesebilir — fakat kullanıcı güveni ve erişilebilirlik için yalnız gerçekten tüketmek istediğiniz kombinasyonlarda, ve tuvale odak varken çağırmalısınız. Tüm keydown olaylarını koşulsuz iptal etmek hem özgür gezinmeyi bozar hem de ürünün güven vermesini zayıflatır.

Tam ekran veya imleç kilidi ( pointer lock ) senaryolarında tarayıcı politikası daha katıdır; bu sayfa gömülü canvas aracı varsayar ve bu API’lere girmez. Yine de ok tuşlarıyla oyun içi menü kaydırırken sayfanın da kaymamasını istiyorsanız, hedeflenen yön tuşlarında preventDefault düşünün — sonra geri almayı test edin ( özellikle mobil dış kulaklık ve ekran okuyucu ile ).

Kare döngüsü ile birleştirme: tuşlar güncelleme, canvas çizim

Tuş olayları imleçten daha seyrek de olsa ana iş parçacığında ( main thread ) çalışır; her keydown’da tüm sahneyi yeniden çizmek yerine, yalnız basılı küme veya ivme vektörünü güncelleyip requestAnimationFrame döngüsünde tek çizim yapın ( requestAnimationFrame ). Bu ayrım, Fare girdisi · yoğun mousemove bölümündeki rAF birleştirme ile aynı felsefedir: olay yolu ince, kare yolu kalın.

Dünya uzayında saniye başına sabit hız istiyorsanız basılı süreyi Delta time ile çarpın; yalnız kare başına sabit adım istiyorsanız tuş durumunu okuyup her karede eşit delta uygulayın — ikisini karıştırmamak stil meselesi değil, ölçek hatası meselesidir.

Anti-kalıplar: odağı unutmak ve IME sınırı

Odaksız dinleyici: document üzerinde dinleyip tuvalin aslında odak almadığını varsaymak; kullanıcı form doldururken veya arama kutusundayken beklenmedik kısayollar tetiklenir. Ya hedefe bağlayın ya da global dinleyicide açıkça «aktif mod» bayrağı taşıyın.

keypress ve karakter odaklı model: Yeni kodda keydown/keyup ile code/key kullanın; metin düzenleyici yazmak için contenteditable veya <input> ayrı tutulmalıdır — canvas üzerindeki daktilo deneyimi doğrudan klavye olaylarından yapı makul değildir.

Tekrar olayına güvenmek: Basılı hareket için yalnız e.repeat veya yalnız ilk keydown kullanmak, kenar durumlarında sapma üretir; küme tabanlı basılı durum daha güvenilirdir.

Kümenin temizlenmemesi: Sekme değişimi veya tam ekrandan çıkış sonrası hayalet tuşlar; yukarıdaki blur / visibilitychange sigortası şarttır.

Bu sayfanın sınırı

IME ile kompozisyon ( compositionstart / compositionend ) ve CJK metin girişi bu sayfada ayrıntılandırılmaz; çok dilli metin yazan bir canvas arayüzü tasarlıyorsanız önce uygun form kontrollerini kullanmayı değerlendirin. Oyun kumandası için Gamepad API ayrı bir konudur.

Sürükle-bırak ve imleç yakalama desenleri Sürükleme mantığı ile birleştirilir; fare düğmeleri ve teker için Fare girdisi sayfasına dönün.

  • Tuval veya sarmalayıcı tabindex / tıklanınca odak akışı tanımlı mı?
  • Hareket ve karakter ayrımında code ve key doğru seçildi mi?
  • Basılı küme keyup, blur ve gizlenmede temizleniyor mu?
  • preventDefault yalnız gerektiği kadar ve odak varken mi çağrılıyor?
  • Ağır çizim rAF ile ayrıldı mı; delta time ile uyumlu mu?