HTML5 Canvas · Girdi & etkileşim
Pointer sistemi: tuval üzerinde birleşik işaretçi olayları
Pointer Events spesifikasyonu, fare, dokunma ve kalem girdisini
tek bir olay ailesi altında toplar: her temas oturumu bir
pointerId taşır; tarayıcı «hangi parmak?» sorusunu bu kimlikle ayırır. Canvas 2D
uygulamasında çoklu dokunuş, sürükleme ve basınç duyarlı fırça için klasik
mouse* yerine (
veya onunla birlikte dikkatli şekilde ) pointer* kullanmak, platformlar arası
davranışı öngörülebilir kılar — fakat koordinatların yine istemci/CSS düzeni
uzayında geldiğini ve piksele çevirmek için ayrı bir köprü gerektiğini unutmayın:
Event koordinatları genel
çerçeveyi;
fare düğmeleri ve teker ayrıntısı
Fare girdisi sayfasında derinleşir.
Bu sayfa Pointer Events’in canvas hedefli sözleşmesini, setPointerCapture güvenli
kullanımını ve dokunma ile tarayıcı kaydırmasının nasıl yarıştığını işler; uzun süreli sürükleme
durum makineleri için
Sürükleme mantığı ve kare tabanlı çizim
için
requestAnimationFrame ile çapraz okuma önerilir.
Özet: sık pointer olayları
| Olay | Ne zaman? | Canvas’ta tipik kullanım |
|---|---|---|
pointerdown |
İşaretçi tuvalde veya alt öğede aktif oldu | Araç seçimi, yakalama başlatma, ön izleme noktası |
pointermove |
Konum veya basınç değişti | Fırça izi; sürüklerken yoğun — rAF ile birleştirme düşünün |
pointerup |
Temas veya düğme bitti | Çizgi kapatma, commit, yakalama doğal olarak düşer |
pointercancel |
Tarayıcı jesti ( kaydırma, sistem diyaloğu ) devreye girdi | Yarım hareketi geri al; durumu sıfırla |
pointerenter / pointerleave |
Öğe sınırı geçildi | HUD, imleç modu, hover ( fare benzeri ) |
gotpointercapture / lostpointercapture |
Yakalama kazanıldı veya dışarıdan düştü | Sürükleme bayrakları; beklenmedik kayıp için sigorta |
Birleşik girdi modeli: neden Pointer Events
Eski modelde tarayıcı hem mousedown hem touchstart üretir; çift
dinleme, çift çizim veya «bir yolda unutulan dinleyici» ile üretim hataları sık görülür.
Pointer Events ile aynı etkileşim için yalnızca pointerdown /
pointermove /
pointerup üçlüsünü işlersiniz; dokunmatik cihazda tarayıcı uyumluluk katmanı
mouse olaylarını da sentezleyebilir — bu yüzden hem pointer hem mouse dinlerseniz
çift tetikleme riskini bilerek yönetmelisiniz. Çoğu yeni canvas arayüzünde
yalnız Pointer Events yeterlidir; üst katman ikinci el kütüphaneler mouse varsayıyorsa
arada köprü yazmak gerekir.
pointerType alanı "mouse", "pen",
"touch"
(
vb. ) döndürür; aynı kod yolunda cihaz sınıfına göre ince ayar yapabilirsiniz — örneğin
kaleme
özel silgi uçu veya dokunuşa daha geniş tolerans. Bu ayrım canvas çizgisel kalitesini
doğrudan
etkiler: kalemde pressure varken farede sıfıra yakın olabilir.
Kimlik ve oturum: pointerId, isPrimary ve çoklu dokunuş
pointerId sayısal bir tanıtıcıdır; çoklu dokunuşta her parmak için farklıdır.
Durumunuzu bir Map<number, …> ile tutmak, hangi noktanın hareket ettiğini
çözümlemenin standart yoludur. isPrimary genelde ilk temas eden işaretçiyi (
özellikle fare ve tek dokunuşta ) işaretler; çoğu masaüstü aracında birincil olmayan
parmakları yok saymak veya yalnızca yakınlaştırma jestine ayırmak tasarım kararıdır — ikisi
bir arada kullanılacaksa önce kullanıcı testi şarttır.
Oturum hep pointerup veya pointercancel ile biter; ara durumda
parmak ekrandan kalkmadan başka jest devralırsa pointercancel görebilirsiniz.
Haritadan ilgili pointerId girdisini bu iki olayda da mutlaka silin — aksi
halde
«hayalet parmak» bırakırsınız ve sonraki hareketler yanlış çizilir.
Yaşam döngüsü: pointercancel ve durum geri alma
pointercancel, kullanıcı jestinin tarayıcı tarafından «alındığı» anlamına
gelir:
sayfa kaydırması, tarayıcı menüsü veya sistemin hızlı hareket tanıyıcısı devreye girdiğinde
tuval artık o temas zincirini garanti etmez. İptal geldiğinde pointerup
beklemek
hatalıdır; yarım çizimi temizleyip araç durumunu kapatan kod yolu şarttır. Bu, klasik mouse
modelinde daha seyrek görülen bir farktır — Pointer Events’e geçerken en çok atlanan
noktalardan
biridir.
pointermove farede çok sık tetiklenebilir; tam sahne çizimini doğrudan olay
içinde yapmak CPU’yu yakar. Kural:
Fare girdisi · yoğun
mousemove ile aynı — olayda yalnız veri güncelle, çizimi
requestAnimationFrame ile tekilleştir; kirli bayrak deseni için
Update vs render · kirli bayrak sayfasına bakın.
Yakalama: setPointerCapture ve lostpointercapture
Sürüklerken imleç veya parmak tuval sınırının dışına çıkarsa, yakalama olmadan olaylar başka
öğeye düşebilir veya kesilebilir. element.setPointerCapture(pointerId) çağrısı
(
genelde pointerdown içinde, kullanıcı etkileşimi zincirinde ) sonraki
pointermove /
pointerup olaylarını hedef öğeye yönlendirir. pointerup ve
pointercancel yakalamayı otomatik olarak düşürür; yine de uygulama içi durum
makineniz için lostpointercapture dinlemek, beklenmedik sistem müdahalelerinde
temizlik yapmanızı sağlar.
Yakalama, güvenlik ve kullanıcı denetimi ile sınırlandırılmıştır; her zaman kötüye kullanıma karşı düşünülerek tasarlanmıştır. Özelleştirilmiş sürükle-bırak akışlarında ( ön izleme, ızgara hizalama, çok adımlı yerleştirme ) Sürükleme mantığı sayfası durum geçişlerini derinleştirir; burada yalnızca API sınırı ve olay eşleşmesi hatırlatılır.
/**
* pointerdown: yakalama başlatır (aynı pointerId için).
* Olay işleyicileri AbortController ile sökülür — bellek sızıntısı olmasın.
*/
function attachPointerCaptureForDrag(canvas, { onMove }) {
const ac = new AbortController();
const { signal } = ac;
canvas.addEventListener(
'pointerdown',
(e) => {
canvas.setPointerCapture(e.pointerId);
},
{ signal },
);
canvas.addEventListener(
'pointermove',
(e) => {
if (e.buttons === 0 && e.pointerType === 'mouse') return;
onMove?.(e);
},
{ signal },
);
function release(/* e */) {
/* pointerup / cancel sonrası ek temizlik buraya */
}
canvas.addEventListener('pointerup', release, { signal });
canvas.addEventListener('pointercancel', release, { signal });
canvas.addEventListener('lostpointercapture', release, { signal });
return () => ac.abort();
}
Dokunma davranışı: touch-action ve passive dinleyiciler
Mobil ve dokunmatik dizüstüde tarayıcı, dikey kaydırmayı önceliklendirir. Tuval üzerinde tek
parmakla çizmek istiyorsanız, CSS touch-action: none (
veya seçici olarak pinch-zoom dışlaması ) ile jest ayrımını kontrol
etmelisiniz — aksi halde pointercancel ile çiziminiz sık kesilir veya sayfa
zıplar. touch-action salt CSS ile çözülebildiği için, her olayda
preventDefault ile kaydırmayı öldürmekten daha sürdürülebilir ve erişilebilir
dostudur.
Uygulamada tuval genelde bir sarmalayıcı içinde durur: touch-action değerini
etkileşim katmanına (
örn. tam boy sarmalayıcı veya şeffaf üst öğe ) vermek, altındaki sayfa alanının
kaydırılabilir
kalmasına izin verirken yalnız çizim yüzeyinde jestleri kilitlemenize olanak tanır.
touch-action: manipulation tek parmak kaydırmayı bırakıp çift parmak
yakınlaştırmayı
sınırlamak gibi orta yollar sunar; «tuvalde çiz, çevrede sayfa kay» senaryosunda hangi
öğenin jest alanı olduğunu net çizmek gerekir — aksi halde kullanıcı beklenmedik şekilde üst
katmanda veya tuvale takılı kalır.
Tarayıcı, kendi kaydırma/kirpme animasyonu ile rekabete girdiğinde pointercancel ve durum geri alma yolunun devreye gireceğini unutmayın. Yakınlaştırma veya üst menü açıldığında yarım çizgi bırakmak yerine iptal olayında fırçayı kapatmak, touch ilkesiyle birlikte düşünülecek tek bir davranış seti oluşturur.
Bazı ortamlarda dinleyici passive: true olarak kaydedilir; bu durumda
preventDefault() etkisiz kalır. Tuval zoom’unu elle kontrol etmek istediğinizde
dinleyici seçeneklerini bilinçli seçin — konu fare tekeri tarafında
Fare girdisi · dinleyici
yaşam döngüsü ile paraleldir. Çoğu canvas aracında birincil çözüm
touch-action değerini CSS ile doğru ayarlamak, ikincil çözüm ise gerçekten
passive: false gerektiğinde (
ve kullanıcı etkileşimi sırasında ) sınırlı preventDefault kullanmaktır —
ikisini
birlikte koşulsuz uygulamak hem performansı hem erişilebilirliği zorlar.
Basınç, iletişim alanı ve stylus: çizgi kalitesi
pressure 0..1 aralığında (
desteklenmediğinde 1 veya 0.5 varsayımlarına düşebilir ) çizgi genişliğini modüle etmek için
kullanılır. width ve height iletişim elipsinin boyutunu verir;
parmak alanı geniş olduğunda ince «mouse benzeri» nokta yerine doğal fırça hissi çıkar.
tiltX /
tiltY destekleniyorsa kalem eğimi ile hachure veya kaligrafi efektleri
mümkündür — tüm cihazlarda olmayacağını varsayarak kod yolunu dallandırın.
Pratikte en güvenli desen: önce ham değerleri okuyup sonra tek bir yardımcıda
«çizim birimi»ne çevirmektir. Örneğin lineWidth için
Math.max(minKalem, tabanGenişlik * (0.15 + 0.85 * pressure)) gibi alt ve üst
sınırlı eşlemeler, 0 veya 1 uçlarında patlayan imzaları yumuşatır; parmak dokunuşunda
width / height çoğu zaman basınçtan daha güvenilir bir «gövde» ipucudur —
ikisini birlikte (
ağırlıklı ortalama veya kinematik yumuşatma ) kullanmak fırça karakterini zenginleştirir.
Piksel dünyasına aktarırken yine tek bir clientX/clientY → tuval
piksel köprüsü kullanın; basınç ve genişlik değerlerini doğrudan çizim birimine ölçeklemek,
DPR ile uyumlu kalınlık üretir. Genel dönüşüm tablosu
Event koordinatları ile aynı
fikirde tutulmalıdır — burada yalnız Pointer’a özgü ek ölçüler eklenir.
Çizgi örnekleme aralığı düşük kare hızlarında kırılmaya başlarsa, ham
pointermove noktalarını doğrudan bağlamak yerine ara nokta (
örn. önceki olay ile lineer ara adım ) üretmek veya Bézier yumuşatması uygulmak gerekir —
bu, basınç ile birlikte ele alındığında el yazısı hissi ciddi biçimde iyileşir. Yoğun
taşıma olaylarında çizim gövdesini yine
rAF ile tekilleştirme düşüncesiyle tutmak,
hem stabilite hem pil tüketimi açısından faydalıdır; aşağıdaki örnek olaydan gelen
pressure ve konumu tek karede işler.
/**
* toCanvas: client → tuval pikseli. render: tek karede stroke/emit.
*/
function attachPointerMoveCoalesced(canvas, toCanvas, render) {
const ac = new AbortController();
const { signal } = ac;
let rafId = 0;
let latest = null;
function flush() {
rafId = 0;
if (!latest) return;
render(latest);
latest = null;
}
canvas.addEventListener(
'pointermove',
(e) => {
latest = { ...toCanvas(canvas, e.clientX, e.clientY), e };
if (!rafId) rafId = requestAnimationFrame(flush);
},
{ signal },
);
return () => {
cancelAnimationFrame(rafId);
ac.abort();
};
}
Anti-kalıplar: çift dinleme ve unutulan iptaller
Mouse ve pointer birlikte: Aynı etkileşimde her iki aileyi de koşulsuz dinlemek çoğu cihazda çift işlem üretir. Ya Pointer Events’e geçin ya da köprü katmanında birini tüketin.
pointercancel yok saymak: Yarım polyline veya sürüklenen ön izleme ekranda kalır; kullanıcı tarayıcı kaydırdığında tuval «yapışmış» görünür.
Çoklu pointerId temizliği: pointerup yolunda haritadan silmeyi
atlamak çok dokunuşlu testlerde patlar.
Yakalama sonrası kalıcı dinleyici: Bileşen unmount olduğunda
AbortController ile hem olayları hem planlı rAF’ı iptal edin (
yukarıdaki örnek desen ).
Bu sayfanın sınırı
Intersection Observer veya kamera tabanlı jestler burada ele alınmaz. Oyun kumandası için klavye ve ileride Gamepad API ayrı başlıktır — Klavye girdisi köprüsü canvas odak akışını tamamlar.
Ham dokunma olayları (
touch* ) ile Pointer Events farklı garanti seviyelerine sahiptir; yeni
canvas kodunda Pointer Events tercih edilmesi, üst düzey tutarlılık için önerilir.
pointercancelvelostpointercaptureiçin temizlik yolları yazıldı mı?- Çoklu
pointerIdharitası her çıkış olayında güncelleniyor mu? - Mobilde
touch-actiontuval jestleriyle uyumlu mu; üst katman jest alanını gölgeliyor mu? pressure/widthiçin cihaz düşümü ve çizgi genişliği sınırları tanımlı mı?- Yoğun
pointermoverAF ile birleştirildi mi? - Mouse ve pointer dinleyicileri çift iş yapmıyor mu?