holodepth

HTML5 Canvas · Girdi & etkileşim

Fare girdisi: canvas üzerinde mouse olayları

Canvas öğesi, DOM ağacında düğüm olduğu sürece çoğu klasik fare olayı için hedef olabilir: taşınma, basma, bırakma, tekerlek ve tıklama zinciri. Önemli olan, olay nesnesinden okunan koordinatların ekran veya CSS düzeni uzayında olduğu, piksel çizmek için ise canvas bağlam koordinatlarına dönüştürmeniz gerektiğidir — bu dönüşümün ayrıntısı Event koordinatları sayfasında; burada olay türleri, düğme modeli, dinleyici güvenliği ve tuvale özgü düşünce tarzı ön plandadır.

Birleştirilmiş çizim ve kirli bayrak desenleri Update vs render ile; sürekli sürükleme uygulaması Sürükleme mantığı ile — burada çapraz platform için de temel olan Pointer Events öncesi klasik mouse modeli ele alınır ( Pointer sistemi köprü).

Özet: sık mouse olayları

Olay Ne zaman? Canvas’ta tipik kullanım
mousemove İşaretçi öğe üzerindeyken hareket Fırça, hover seçim, okuma çubuğu
mousedown / mouseup Düğme basıldı / bırakıldı Araç seçimi, geçici ön izleme
click Aynı öğede bas-bırak tamamlandı Düğme, hücre seçimi (basit)
wheel Tekerlek / dokunmatik yatay iki parmak Yakınlaştırma, kaydırma
mouseenter / mouseleave Öğe sınırı geçildi HUD vurgusu, imleç ipuçları

Olay ailesi: hedef, kabarcıklanma ve çift tıklama

Fare olayları çoğu zaman kabarcıklanır ( bubble ): canvas üzerindeki mousedown önce tuvalde tetiklenir, sonra ata öğelere doğru yükselir. Olayı yalnız tuvalde işlemek istiyorsanız stopPropagation düşünün — fakat form veya üst katman overlay ile çakışma yaratmazsanız genelde bırakmak daha az sürprizlidir.

click, aynı öğe üzerinde uyumlu bas-bırak sonrası üretilir; ara taşımalarda basma bir alt öğede, bırakma tuvalde olursa click beklediğiniz gibi gelmeyebilir — sürükle-bırak ve hassas seçim için çoğu zaman mousedown/ mouseup çifti veya Pointer sistemi tercih edilir.

dblclick tarayıcı ve işletim sistemi gecikme ayarlarına duyarlıdır; oyun veya çizim aracında çift tıklama ile tek tıklama çakışmasını önlemek için zaman eşiği veya tool modu şarttır. Mobil cihazlarda çift dokunma jesti farklı davranabilir — canvas hedefli ürünlerde dokunmatik yolu göz önünde bulundurun.

Düğmeler ve modify tuşları: button ve buttons

MouseEvent.button hangi fiziksel düğmenin bu olayı tetiklediğini sayar: genelde sol 0, orta 1, sağ 2 — fakat solak sistem ayarı veya cihaz sürücüsü sapma gösterebilir. buttons ise o an basılı tutulan düğmelerin bit maskesidir; hareket sırasında sol basılıyken sağ da basılırsa maske güncellenir.

shiftKey, ctrlKey, altKey, metaKey ile kısayol semantiği kurulur; örneğin şekil çiziminde shift ile eksen kilidi yaygındır. Bu bayraklar klavye olayı değildir — yine de tuval odağı olmadan okunabilir; kısayol çatışması yaşamamak için tarayıcı veya işletim sistemi rezervlerini gözden geçirin.

Sağ tık bağlam menüsü için contextmenu olayını dinleyip preventDefault ile yerel menüyü bastırabilirsiniz; kullanıcı arayüzü erişilebilirlik açısından alternatif menü yolu sunmak iyi pratiktir.

const MOUSE = { LEFT: 0, MIDDLE: 1, RIGHT: 2 };

function primaryAction(e) {
  return e.button === MOUSE.LEFT;
}

function modifiers(e) {
  return {
    shift: e.shiftKey,
    ctrl: e.ctrlKey,
    alt: e.altKey,
    meta: e.metaKey,
  };
}

Koordinat dönüşümü köprüsü: client’tan tuval pikseline

clientX / clientY görünüm penceresine göre verilir; CSS ile ölçeklenmiş veya
object-fit ile sığdırılmış canvas’ta iç piksel ızgarası ile bire bir örtüşmez. Üretimde getBoundingClientRect() ile görünür dikdörtgen okunur, iç width/height ile ölçek oranı kurulur — tam türetim, kenar durumları ve devicePixelRatio ilişkisi Event koordinatları ve Resize mantığı ile sınırlı örtüşür; burada yalnızca «mouse olayı → sayı → çizim argümanı» zincirinin canvas üreticisi için zorunlu olduğu vurgulanır.

Aynı anda birden fazla canvas veya dönüşüm matrisi ( ctx.translate …) ile çalışıyorsanız, olayı dünya uzayına taşıma tek yerde toplanmalıdır — aksi halde hata ayıklamada hangi katmanda kaydığınızı bulmak zorlaşır.

Dokunmatik cihazlarda fare olayları sentetik olarak üretilebilir veya gecikmeli olabilir; çapraz cihaz hedefliyorsanız uzun vadede Pointer sistemi tercih edilir — bu sayfa klasik mouse sözleşmesini sabitler.

/**
 * CSS ölçekli canvas → dahili piksel koordinatı (yaklaşım; edge-case Event koordinatları sayfasında).
 */
function clientPointToCanvas(canvas, clientX, clientY) {
  const r = canvas.getBoundingClientRect();
  const sx = canvas.width / r.width;
  const sy = canvas.height / r.height;
  return { x: (clientX - r.left) * sx, y: (clientY - r.top) * sy };
}

Dinleyici bağlama: passive, capture ve temizlik

canvas.addEventListener('mousemove', handler) en yaygın yoldur; capture: true nadiren gerektirir — üst katmanlar olayı yutmadan önce yakalamak istediğinizde kullanılır. Kaldırma için aynı fonksiyon referansıyla removeEventListener şarttır; anonim ok fonksiyonları tek seferlik bağlantı için uygundur fakat sonra kaldıramazsınız.

Modern tarayıcılarda AbortController ile { signal } geçmek, tek satırda toplu temizlik sağlar: sinyal iptal edildiğinde tüm dinleyiciler düşer — React / Vue cleanup aşamasında özellikle kullanışlıdır.

wheel ile preventDefault (sayfa kaydırmayı engellemek için) çağıracaksanız dinleyiciyi { passive: false } ile kaydetmelisiniz — aksi halde uyarı veya yok sayılma ile karşılaşırsınız. Bu, kaydırma hissi ve erişilebilirlik için dikkatle kullanılmalıdır; tüm sayfayı tuvalde kilitlemek kullanıcıyı rahatsız edebilir.

/**
 * canvas üzerinde mouse dinleyicilerini signal ile bağlar; dispose'da AbortController.abort().
 */
function attachMouse(canvas, handlers) {
  const ac = new AbortController();
  const opt = { signal: ac.signal };
  for (const [type, fn] of Object.entries(handlers)) {
    canvas.addEventListener(type, fn, opt);
  }
  return () => ac.abort();
}

Odak, imleç ve CSS: pointer-events ile üst öğeler

Canvas üzerinde şeffaf bölgelerde fare «görünmez» öğelere düşebilir — üstte position: absolute bir katman varsa olaylar tuvali atlar. Lekeli cam etkisi istiyorsanız üst katmanda pointer-events: none vererek olayları alta iletirsiniz; yalnız belirli düğmeler tıklanacaksa alt tuval + üst interaktif HTML karışımı bilinçli tasarlanır.

tabindex ile tuval odaklanabilir yapılabilir; klavye olayları için gereklidir ( Klavye girdisi ). Fare tek başına çoğu zaman odağı taşımaz — tıklama ile çizim başlatırken odak davranışını gözlemleyin; tarayıcı önceden seçili öğeyi etkileyebilir.

Özel imleç ( cursor: crosshair vb.) CSS ile atanırsa, araç değişiminde sınıf değiştirmek canvas içi mantığı okumayı kolaylaştırır — imleç stilini sık sık bağlam özelliğinden değiştirmek yerine kapsayıcı üzerinde tutmak sızıntı riskini azaltır.

Yoğun mousemove ve kare ile birleştirme

mousemove saniyede yüzlerce kez tetiklenebilir; her seferinde tam sahne çizmek CPU’yu yakar. Üretim deseni: olayda yalnız durumu veya ara koordinatı güncelle, çizimi requestAnimationFrame içinde tekilleştir ( requestAnimationFrame ). Böylece kare başına en fazla bir kompozit geçiş yapılır; imleç örnekleme oranı ekran yenilemesinden hızlı olsa bile piksel yolu tıkanmaz.

Kirli bayrak ( Update vs render · kirli bayrak ) ile birlikte düşünülebilir: yalnız imleç hareket ettiyse ve görsel gerçekten güncellenecekse requestAnimationFrame planlayın; statik sahnede gereksiz plan iptalleri de bellek tahsisini tetikleyebilir — «planlı kimlik var mı?» kontrolü basit bir bayrakla çözülür.

Global window üzerinde mousemove dinlemek, tuval dışında da ateşlenir; sürüklerken tuval dışına çıkıldığında hala koordinat almak için bazen gereklidir — fakat mutlaka mouseup ( ve mümkünse mouseleave / blur yedekleri ) ile eşleştirerek serbest bırakıldığında dinleyiciyi kaldırın, aksi halde bellekte ve CPU’da kalıcı dinleyici kalır. Oturum lost pointer capture benzeri kenar durumları için Sürükleme mantığı sayfası derinleşir; burada yalnızca «global dinleyici = geçici kira» kuralı hatırlatılır.

Throttle ( lodash throttle vb.) alternatiftir; sabit ms ile üst sınır koyar ve düşük güçlü cihazlarda iş kuyruğunu budar. Fakat tarayıcı zaten ekran hızına göre boyar — çoğu canvas etkileşiminde rAF birleştirme daha az sürprizlidir çünkü görsel çıktı zaten kare tabanlıdır; throttle’ı ağ isteği veya DOM ölçümü gibi rAF dışı yan işlerle birlikte değerlendirin.

Aynı turda hem olay hem rAF içinde ağır hesap yapmak kazancı yerler: olay yolunda yalnız koordinat ve hafif bayraklar; yoğun geometri ve drawImage yığınları kare planlı blokta kalmalıdır.

/**
 * toCanvas: örn. clientPointToCanvas. render: tek karede çizim.
 * Aynı karede birden fazla mousemove tek render çağrısında birleşir.
 */
function attachMoveCoalesced(canvas, toCanvas, render) {
  const ac = new AbortController();
  let rafId = 0;
  let latest = null;

  function flush() {
    rafId = 0;
    if (!latest) return;
    render(latest);
  }

  canvas.addEventListener(
    'mousemove',
    (e) => {
      latest = toCanvas(canvas, e.clientX, e.clientY);
      if (!rafId) rafId = requestAnimationFrame(flush);
    },
    { signal: ac.signal },
  );

  return () => {
    cancelAnimationFrame(rafId);
    ac.abort();
  };
}

Anti-kalıplar: koordinat ve dinleyici sızıntısı

offsetX doğrudan piksel sanması: offsetX / offsetY bazen tüval piksellerine denk gelmemekle birlikte — özellikle CSS transform / object-fit, iç içe ofsetli üst öğeler veya yüksek DPR — kullanıcılar tarafından «tek satırlık» kolaylık sanılır. Canvas üzerinde çizim koordinatı için getBoundingClientRect + iç boyut oranı ( Client → canvas köprüsü ) tek kaynak olmalı; aynı formülün farklı olaylarda kopyalanması en küçük sabit farkla metreler sapma üretir. Evrensel koordinat terimi için Event koordinatları sayfasına dönün.

window mousemove kalıntısı: Sürüklerken document / window dinlemek makuldür; fakat mouseup ( ve sekme kaybı için blur ) sonrası removeEventListener veya AbortController ile tamamen sökülmezse, kullanıcı artık sürüklemese bile her imleç hareketi rAF, çarpışma veya günlük yazar. Bu kalıntı CPU ve pil profiliyle değil, «neden boşta %5 CPU?» ile ortaya çıkar. Geçici kira desenini Yoğun mousemove ve kare ile birleştirme ve Sürükleme mantığı ile hizalayın.

passive wheel: Birçok yığın wheel dinleyicisini varsayılan olarak passive: true kaydeder; bu durumda zoom / pan için preventDefault() sessizce işe yaramaz ve sayfa kaydırması ile tuval zoom’u aynı jestte yarışır. Zoom’u tuvalde kilitlemek istiyorsanız açıkça passive: false istemeli ve gerçekten iptal edecekseniz çağırmalısınız — aksi halde ana iş parçacığını gereksiz yere bloke edersiniz. Ayrıntı Dinleyici yaşam döngüsü içindeydi.

button ile buttons karışması: mousedown / click / contextmenu gibi olaylarda tetikleyici düğme genelde net okunur; oysa mousemove ve mouseenter akışında buttons bit kümesi, button alanından farklı semantik tutabilir ( tarayıcı ve platform farkları dahil ). Çok tuşlu sürükleme, orta teker «pan» ve araç değiştirme mantığını yalnız masaüstünde değil, farklı işletim sistemlerinde de test edin. Düğme alanları ile çapraz kontrol şarttır.

Bu sayfanın sınırı

Oyun kumandası, stylüs baskı eğrisi ve çoklu dokunuş Pointer Events ve özel API’lerle daha iyi modellenir — bu sayfa klasik DOM MouseEvent modelidir.

İmleç kilidi ( pointer lock ) ve tam ekran birinci şahıs deneyimi ayrı güvenlik / kullanıcı izni konusudur; burada yalnızca embed canvas etkileşimi varsayılır.

  • Piksel dönüşümü tek client → canvas yardımcıda mı; offsetX ile karışık ikinci yol kalmadı mı?
  • wheel için passive: false gerekiyor mu, gerekiyorsa ayarlandı mı?
  • Ağır mousemove rAF ile birleştirildi mi?
  • Global dinleyiciler sürükleme sonunda kaldırıldı mı?
  • Üst overlay pointer-events yüzünden olay tuvali mi atlıyor?