holodepth

HTML5 Canvas · Girdi & etkileşim

Event koordinatları: tuval pikseline güvenilir köprü

DOM olayları size ekran / görünüm tabanlı sayılar verir; Canvas 2D bağlamı ise bitmap piksel ızgarası üzerinde çizer. İkisi arasındaki oran, CSS ile verilen görüntüleme boyutu ile canvas.width / canvas.height özniteliklerinin farkından doğar — aynı çizgi, düşük DPR ekranda «yeterli», retina ekranda ise bulanık veya kaymış görünür. Bu sayfa, tek bir client → canvas dönüşümünü sabitleyerek; fare, işaretçi ve klavye ile kesişen tüm araçların aynı matematiği paylaşmasını hedefler — uygulama olayları Pointer sistemi ve Fare girdisi sayfalarında ayrıntır; burada yalnızca sayıların anlamları ve köprü formülü ön plandadır.

Taşıma ve tutma ofseti desenleri Sürükleme mantığı · tutma ofseti köprüsüyle doğrudan bağlanır; yoğun hareket olaylarında çizimi Update vs render · kirli bayrak ile ayırma düşüncesi bu köprünün üzerinde çalışır — fakat köprü yanlışsa kare başına en iyi kod da imzayı düzeltmez.

Özet: sınama ve çizim uzayları

Alan / özellik Referans Canvas’ta rolü
clientX / clientY Görünüm alanı ( viewport ), kaydırma hariç getBoundingClientRect ile tuval sınırına göre göreli konum
pageX / pageY Sayfa kökü, belge kaydırması dahil Tuval sabit görünür alandaysa çoğu zaman client* yeter; kaydırılan gömülerde dikkat
offsetX / offsetY Olay target öğesine göre Kolaylık; transform / iç içe düzen / DPR ile sürpriz üretebilir — tek başına piksel sanmayın
canvas.width / height Bitmap boyutu ( backing store ) Çizim koordinatlarının üst sınırı; DPR ile bilinçli büyütmek keskinlik verir
getBoundingClientRect() CSS pikseli cinsinden ekrandaki kutu İstemci noktasını tuval içi orana bölmek için referans kutu

İstemci uzayı: clientX ve görünür dikdörtgen

MouseEvent ve PointerEvent için clientX / clientY, belge kaydırmasından bağımsız olarak pencerenin görünür alanına göre konumu verir. Tuvalin köşelerine göre göreli konumu bulmak için öğenin getBoundingClientRect() çıktısı kullanılır; çağrı anı ile olay anı aynı karede olduğu sürece ( senkron işleyici içinde ) senkronizasyon tutumludur. Kaydırma, animasyon veya üst çubukların açılıp kapanması dikdörtgeni hareket ettirir — uzun süre önbelleğe alınmış rect ile güncel olmayan olayı birleştirmek ince sapmalar üretir.

Çoklu monitör, tarayıcı yakınlaştırması ( page zoom ) ve işletim sistemi ölçeği client* ölçeğini değiştirir; fakat aynı kare içinde tuval ve olay birlikte yorumlandığında rect oranı genelde doğru kalır. Özet: tuval içi x için önce clientX - rect.left, sonra bu uzaklığı rect.width üzerinden oransal olarak canvas.width ile çarpın — dikey eksen aynayıdır.

Tek köprü: iç boyut oranı ve keskin çizim

Tuval öğesinin CSS genişliği görüntülenen boyuttur; width özniteliği ise arka tampon genişliğidir. İkisi farklıysa tarayıcı görüntüyü ölçekler — bu istenendir çünkü devicePixelRatio üzerinden retina keskinliği sağlamak için bitmap’i daha geniş tutarsınız. Köprü oranı
canvas.width / rect.width ve canvas.height / rect.height ile bulunur; en/boy oranı bozulmadıkça genelde ikisi yaklaşık eşidir, fakat müstakil ölçeklemelerde ( örn. hatalı stil ) ayrı ayrı kullanmak şarttır.

Piksel hizalı çizim ( 1 px çizgiler, kare ızgara ) istiyorsanız dönüşüm sonrası Math.floor veya 0.5 ofset stratejilerini merkezi yardımcıda uygulayın — dağınık yuvarlamalar çizgi titreşimi yaratır. Bu sayfa yuvarlama politikasını ürün kararı olarak bırakır; yalnızca tek yerde toplanması gerektiğini vurgular.

/**
 * İstemci (viewport) pikseli → tuval bitmap pikseli.
 * rect ve canvas ölçüleri anlık okunur; olay işleyicisi içinde çağırmak tutumludur.
 */
function clientToCanvas(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,
  };
}

offsetX ve offsetY: pratik ama bağlama duyarlı

offsetX / offsetY olayın target öğesine göre tanımlanır; birçok örnekte tek satırda tuval içi koordinat verir. Ancak üst düğümlerde transform, filter, iç içe kaydırma bölgeleri veya object-fit ile ölçeklenmiş gömülü içerik söz konusuysa bu değerler ile getBoundingClientRect köprüsü aynı sonucu vermeyebilir. Üretim kodunda kritik etkileşimlerde clientToCanvas kalıbını standart yapmak, ekip içi «neden ofset farklı?» tartışmalarını keser.

Olay kabarcıklanırken target ile currentTarget farklı olabilir; offset değerleri hedef öğeye bağlıdır — tuval yerine sarmalayıcıda dinliyorsanız offset tuval pikseli olmayabilir. Bu yüzden merkezi köprü, mümkünse doğrudan canvas referansı ve client* üzerinden kurulur.

Cihaz piksel oranı ve tuval boyutlandırma kararı

Yaygın desen: CSS boyutu düzen akışına göre sabitlenir, arka tampon canvas.width = Math.round(cssWidth * devicePixelRatio) ( ve yükseklik aynı ) ile büyütülür; böylece aynı CSS alanında daha fazla bitmap pikseli sunulur. Köprü formülü yine geçerlidir — çünkü getBoundingClientRect CSS pikseli döndürür, oran bitmap / CSS olur. DPR değişimlerinde ( pencere ekranlar arası taşınması ) yeniden boyutlandırma veya yeniden çizim tetiklemek gerekir; aksi halde bir süre bulanıklık veya kırpma görülür.

Tam tersi yaklaşım ( bitmap = CSS 1:1 ) basit eğitim sayfalarında iş görür fakat yüksek DPR ekranda pürüzlüdür. Hangi politikayı seçerseniz seçin, uygulama genelinde aynı köprüyü kullanın; bir modülde DPR ile çarpılmış, diğerinde çevrim yapılmamış koordinatlar bir araya gelince imza kayar.

Kaydırma, dönüşüm ve gömülü düzen kayması

Gövde veya ara paneller kaydırıldığında tuvalin rect’i hareket eder; client* ile birlikte okunduğunda göreli konum doğru kalır. Sorun, saklanmış rect veya sayfa koordinatları ile güncel olmayan bir önbellek kullanıldığında ortaya çıkar — örneğin animasyon bitiminde tek seferlik rect saklayıp sonra uzun süre o değerle çarpan kod.

CSS transform uygulanmış ata öğe, görünen kutu ile düzen kutusunu ayırabilir; getBoundingClientRect dönüştürülmüş sınırı verir, bu da tuval köprüsü için doğru kaynaktır. Karmaşık sahne grafikleri ( zoom / pan ) için dünya koordinatına ayrı bir ters dönüşüm katmanı gerekir — bu sayfa yalnızca DOM tuvalinin kendi dikdörtgenine göre köprüyü sabitler; iç zoom yığınınız ürün katmanınızdadır.

Anti-kalıplar: offset tek başına ve dağınık formül

offset = bitmap: offsetX / offsetY bazen tuval içinde doğru gibi görünür; fakat ata hiyerarşide transform, filtre veya iç içe kaydırma ile aynı matematiksel anlamı getBoundingClientRect köprüsüyle vermez. DPR büyütülmüş bitmap ile CSS kutusu farklı ölçekteyken «tek satırlık offset» yanıltıcıdır — özellikle görünür şekil ile gerçek tıklama hedefi ( hit slop ) ayrı tasarlandığında. Çözüm: Tek köprü üzerinden client* normalleştirmek; offset alanları bölümündeki uyarılarla uyumlu kalın. Fare sayfasındaki aynı tema için Fare girdisi · anti-kalıplar çapraz okunabilir — içerik tekrarı değil, hata sınıfının sabitlenmesidir.

Eski rect: Başlangıçta bir kez alınan getBoundingClientRect sonucu, sayfa kaydırıldığında, yan menü açıldığında veya esnek düzen tuvalleri yeniden ölçülendirdiğinde geçersiz kalır. Önbellekli rect ile çarpılan nokta, ekranda imleç doğru yerde olsa bile bitmap içinde kayar; hata ara sıra tetiklenir ve kötüye üne yol açar. Çözüm: olay işleyicisinde ( veya aynı kare içinde tetiklenen rAF yolunda ) rect’i yeniden okuyun veya boyut değişimini dinleyen katmanda köprüyü geçersiz kılın — Kaydırma ve transform ile birlikte düşünün.

Yalnız genişlik oranı: sx = canvas.width / rect.width değerini hem x hem y için kullanmak, tuvalin CSS’te oranı bozulmuş ( non-uniform scale ) durumunda daireleri elips, kareleri dikdörtgen imleç uzayında eğer. Kare korumalı dünya modeli istiyorsanız en/boy oranını CSS’te koruyun; streç bilinçli ise sx ve sy’yi clientToCanvas örneğindeki gibi ayrı tutun. Dağınık dosyalarda bir yerde tek oran, başka yerde çift oran kullanımı en sık «yalnız yatay doğru» sapmasını üretir.

pageX everywhere: pageX belge kaydırmasına göre mutlak konumdur; clientX görünür alana göredir. Gömülü uygulama, iframe veya iç kaydırma bölgelerinde ikisi farklı sapma gösterir. Tuval, görünür dikdörtgenle hizalanmış tek bir hedefse pratikte client* + rect köprüsü yeterli olur; «her zaman page» yaklaşımı tuval dışı bileşenlerle aynı kodu paylaştığınızda sessizce kırılır. Farklı strateji şartsa dönüşümü yine tek yardımcıya alın ve girdi kaynağını ( client / page ) parametreyle seçin — formülün kopyalanması değil, tek çatı altında iki giriş noktası hedeftir.

Dağınık formül ve mikro sapma: Bir dosyada bölme, başka dosyada yuvarlama, üçüncü dosyada ters eksen; 0,5 px kaymalar toplanıp seçim ve çizgi hizasında titreme oluşturur. Üretimde «koordinat politikasını» tek modül ( örn. clientToCanvas + isteğe bağlı snap ) olarak versiyonlayın; kod incelemesinde getBoundingClientRect ve offset geçen her satır bu modüle yönlendirilmelidir.

Bu sayfanın sınırı

3B projeksiyon veya kamera matrisi ile dünya uzayı dönüşümü burada ele alınmaz — bu sayfa 2D Canvas bitmap hedefleyicisidir. WebGL derinlikli koordinatları ayrı içerik haritasına aittir.

  • clientToCanvas tek modülde mi; kopya formül dağılmış mı?
  • Olay anında getBoundingClientRect mi okunuyor?
  • DPR / yeniden boyutlandırma sonrası arka tampon güncelleniyor mu?
  • sx / sy streç senaryosunda ayrı mı; tek oran varsayımına düşülmedi mi?
  • Belgede page* ile client* karışık mı; köprü tek girişten mi besleniyor?