HTML5 Canvas · Canvas API & context
Koordinat sistemi: eksen, ölçek ve dönüşüm
Canvas’ta her fillRect, lineTo veya drawImage çağrısı
bir (x, y) çiftine dayanır. Bu sayılar, bitmap’in sol üst köşesinden başlayan
piksel ızgarasındaki konumdur — okulda gördüğünüz «yukarı pozitif» ekseninden bilinçli olarak
farklıdır. Önceki sayfalarda yüzeyi ve çizim kalemini kurduk; burada «nereye çiziyorum,
döndürünce
ne oluyor, fare tıklaması neden uyuşmuyor?» sorularının canvas tarafındaki cevabını
netleştiririz.
Hedef, matematik dersi değil; 2D bağlamın uzay modeli. Dönüşüm yığınının tam
tablosu ve save/restore disiplini
State sistemi sayfasında; pencere–bitmap
ölçeklemesi Resize mantığı ile; fare
konumunun canvas’a taşınması
Event
coordinates ile devam eder.
Özet: canvas uzayının ana hatları
| Kavram | Canvas’ta | Bu sayfada |
|---|---|---|
| Orijin | (0, 0) sol üst | Eksen yönü, sınır kutusu |
| Birim | Bitmap pikseli (width/height) |
Attribute boyutu = koordinat aralığı |
| translate | Orijini kaydırır | Yerel uzay, sahne parçalama |
| rotate / scale | Mevcut orijin etrafında | Sıra ve birleşim; save ile güvenli kullanım |
| Hizalama | Yarım piksel kayması | 1 px çizgide bulanıklık önleme |
Canvas uzayı ≠ matematik grafiği
Analitik geometride genelde x sağa, y yukarı gider; canvas 2D bağlamında
y aşağı artar. (0, 0) bitmap’in sol üst köşesidir;
x = canvas.width
sağ kenar, y = canvas.height alt kenardır. Negatif koordinatlar çoğu zaman
görünür
alanın dışına düşer — çizim kırpılmazsa yine de «var» sayılır, fakat ekranda görünmez.
CSS ile konumlandırılmış bir div de sol üst referanslıdır; benzerlik burada
biter.
CSS layout kutusu ve akış modeli üzerinde çalışır; canvas ise tek bir
piksel
dizisine komut gönderir. «Elemanı ortala» yerine «dikdörtgenin sol üst köşesini
(cx - w/2, cy - h/2) yap» dersiniz — merkezleme formülü bu sayfanın pratik
çıktılarından biridir.
Three.js ve WebGL tarafında clip space ve kamera dönüşümleri farklı katmanlardadır; 2D
canvas
düzleminde ise çoğu proje doğrudan bu piksel uzayında kalır veya
translate/scale ile «dünya» taklit edilir.
Eksen, ölçek ve görünür sınır
Canvas oluşturma sayfasında
attribute width ve height bitmap çözünürlüğünü belirler; koordinat
ekseninin sayısal üst sınırı da budur. CSS ile canvas’ı büyütmek yalnızca
ekrandaki kutuyu ölçekler — içerideki (100, 50) hâlâ aynı pikseldir. Bu ayrım, «koordinatım
doğru ama çizgi kalın/bulanık» şikâyetlerinin yarısını resize ve DPR konusuna bağlar; piksel
oranı ayrıntısı resize sayfasına bırakılır.
fillRect(x, y, w, h) konumu sol üst köşe olarak okur; dairenin
merkezi değil. Path’te arc(x, y, r, …) merkez kullanır — aynı sayılar farklı
geometrik anlama gelebilir; karışıklık genelde API farkından kaynaklanır, eksenin
kendisinden
değil.
const w = canvas.width;
const h = canvas.height;
ctx.fillStyle = '#ff6b6b';
ctx.fillRect(0, 0, 12, 12); // orijin köşesi
ctx.fillStyle = '#2ee7f2';
ctx.fillRect(w - 40, 0, 40, 24); // sağ üst bölge
ctx.fillStyle = '#b56cff';
ctx.fillRect(0, h - 24, 40, 24); // sol alt bölge
Translate: Yerel uzay açmak
ctx.translate(dx, dy) mevcut koordinat sisteminin orijinini kaydırır; sonraki
tüm
çizim komutları bu yeni orijine göre yorumlanır. Piksel cinsinden düşünürseniz: dünya
koordinatlarındaki bir noktayı çizmek yerine önce «kâğıdı» kaydırırsınız, sonra o noktayı
(0, 0) etrafında tanımlarsınız. Bu, tekil bir dikdörtgen için fazladan satır gibi görünür;
onlarca parçadan oluşan karakter, UI bileşeni veya harita parçası çizerken tekrarlayan
x + offset, y + offset yazımını keser.
İki üretim kalıbı sık çıkar: nesne merkezli çizim (aşağıdaki
drawShip) ve kamera kaydırması (dünyayı ters yönde
translate(-scrollX, -scrollY) ile kaydırıp dünya içeriğini dünya koordinatında
bırakmak). İlkinde oyuncu ekranda sabit kalır dünya kayar; ikincisinde dünya sabit kalır
kamera
ilerler — matematik aynı yer değiştirme, oyun tasarımında karşılığı farklı.
Yerel orijin ve merkez
Bir gemiyi (x, y) dünya konumunda çizerken, gövdeyi dünya koordinatında
hesaplamak
yerine önce translate(x, y) yapıp gövdeyi
(-w/2, -h/2) civarında
tanımlamak yaygındır. Böylece dönüşüm (rotate) sonraki bölümde aynı yerel
orijin
etrafında kolay uygulanır. Dikkat: fillRect sol üst köşe referanslıdır;
merkezlemek
için genişlik ve yüksekliği yerel uzayda eksi yarı genişlikle başlatırsınız.
function drawShip(ctx, worldX, worldY) {
ctx.save();
ctx.translate(worldX, worldY); // dünya konumu = yeni orijin
ctx.fillStyle = '#2ee7f2';
// Yerel (0,0) gemi merkezi; sol üst köşe (-20,-12)
ctx.fillRect(-20, -12, 40, 24);
ctx.restore(); // matris ve stil önceki kareye dönmez
}
save mevcut dönüşüm + stil yığınının bir kopyasını alır; restore
bir
üst katmana döner — böylece bu fonksiyon çağrıldıktan sonra bağlamın «kirlenmediği» garanti
edilir. translate(-worldX, -worldY) ile elle geri almak hataya açıktır
(özellikle
araya başka translate girdiğinde).
Kamera ofseti (scroll)
Harita çizerken tüm fayans veya nesne koordinatlarını ekran uzayına çevirmek yerine, kare
başında bir kez translate(-camera.x, -camera.y) dersiniz; böylece dünya
içeriği «kendi koordinatlarında» kalır. Negatif kaydırma, dünyayı sağa/aşağı itilmiş gibi
gösterir — kamera sol üstten ilerliyormuş izlenimi verir.
function drawWorldLayer(ctx, camera) {
ctx.save();
ctx.translate(-camera.x, -camera.y);
// Burada drawTile(worldX, worldY) doğrudan dünya koordinatında çağrılır
drawTileMap(ctx);
ctx.restore();
}
translate bağlamın dönüşüm matrisini değiştirir;
fillStyle
gibi bir stil özelliği değildir. Biriken dönüşümler State
sistemi sayfasındaki save/restore veya beşinci bölümdeki
resetTransform ile kontrol edilir — hangi yöntemin ne zaman uygun olduğu orada
tablolaştırılır.
Bu sayfanın sınırı
İç içe save/ restore dengesi, kırpma (clip) ve
unutulan
geri alma hataları state sayfasının konusudur. Burada yalnızca şunu sabitleyin: her
«geçici yerel uzay» açışında karşılığı restore veya açıkça matris
sıfırlaması
planlanmalıdır.
rotate ve scale: açı ile ölçek
rotate(açı) radyan bekler; derece kullanıyorsanız
angle * Math.PI / 180
dönüşümünü üretim kodunda tek yerde toplayın. Dönüş, o andaki orijin
etrafında
uygulanır — bu yüzden nesneyi dünyada döndürmek için tipik sıra: dünya konumuna
translate, sonra rotate, sonra yerel geometri (ör. ok gövdesi),
en sonda restore. Orijini atlarsanız dönüş canvas’ın (0,0) sol üst köşesine
göre yapılır; «neden uçtan dönüyor?» sorusu çoğu zaman budur.
scale(sx, sy) her iki ekseni çarpar; sx = -1 yatay ayna,
sy = -1 dikey ayna üretir. Ölçek, hem şekli hem stroked çizgilerin
kalınlığını, fillText ile yazılan metnin piksel boyunu da etkiler
— bu
davranış path geometry’si ile ayrı; ince çizgili, sabit kalınlık UI için ölçeklemeyi
path’ten
sonra veya ayrı katmanda düşünmek gerekir (bu sayfada sadece farkı netleştiriyoruz).
Çağrı sırası: translate önce mi, rotate önce mi?
translate → rotate → çiz ile rotate → translate → çiz farklı
sonuç verir: ikinci durumda önce dönen eksen üzerinde kaydırma yapıldığı için nesne yörünge
çizer. Zihinsel kural: matris çarpımı sağdan sola okunur; bağlama eklenen
son
çağrı, nokta vektörü üzerinde «ilk uygulananmış» gibi düşünülebilir. Emin değilseniz küçük
bir
kare çizip iki sırayı yan yana deneyin; göz kararı doğrulama canvas geliştirmede yasal
yöntemdir.
Karmaşık sahnelerde her kare için resetTransform ve ardından tek
setTransform ile hedef matrisi kurmak, onlarca translate
birikmesini
önler — detay beşinci bölümde.
Coordinate Space Visualizer
ile iki sırayı aynı translate / rotate değerleriyle canlı
karşılaştırın; yerel eksenler (kırmızı X, yeşil Y) döndükçe uzayın nasıl döndüğünü görün.
const a = Math.PI / 6;
// A: önce konum, sonra dönüş — tipik sprite / ok (orijin etrafında döner)
function drawAtA(ctx, x, y) {
ctx.save();
ctx.translate(x, y);
ctx.rotate(a);
ctx.fillRect(-10, -4, 20, 8);
ctx.restore();
}
// B: önce dönüş, sonra öteleme — dönük eksen boyunca kayar
function drawAtB(ctx, x, y) {
ctx.save();
ctx.rotate(a);
ctx.translate(x, y);
ctx.fillRect(-10, -4, 20, 8);
ctx.restore();
}
(0,0) world origin;
kırmızı/yeşil world eksenleri sabittir. Nesne üzerindeki parlak eksenler
local space — rotate sonrası döner. Sıra düğmeleri
translate → rotate ile rotate → translate yörüngesini ayırır.
2D Context
Playground çizim API’sine odaklanır; bu demo uzay matematiğine.
Örnek: merkezde dönen ok
Aşağıdaki drawArrow, hedef (x, y) noktasını yeni orijin yapıp
açıyı
uyguladıktan sonra ok uçunu +x yönünde çizer. Bu, «oyuncu yönü = açı» eşlemesinin en kısa
yoludur.
function drawArrow(ctx, x, y, angle) {
ctx.save();
ctx.translate(x, y);
ctx.rotate(angle);
ctx.fillStyle = '#e8f4ff';
ctx.beginPath();
ctx.moveTo(0, 0);
ctx.lineTo(28, 0);
ctx.lineTo(20, -6);
ctx.lineTo(20, 6);
ctx.closePath();
ctx.fill();
ctx.restore();
}
Ölçek ve HUD ipucu
scale(2, 2) ile yaklaşım «zoom» hissi verir; fakat çizgi ve metin de ikiye
çıkar.
Oyun içi HUD’u dünya ölçeğinden ayırmak için çoğu proje dünya için
translate/scale, ardından resetTransform ve ekran sabit UI için
ikinci
bir çizim bloğu kullanır — tam ayrım State
sistemi ve beşinci bölümle birlikte netleşir.
transform, setTransform ve birleşim
2D bağlamda her çizim noktası önce afine dönüşüm matrisi ile çarpılır,
sonra
bitmap’e yazılır. translate, rotate, scale bu matrisi
çarparak günceller; transform(a, b, c, d, e, f) ise doğrudan 2×3 biçiminde (
[a c e] birinci satır, [b d f] ikinci satır, son sütun öteleme)
mevcut matrisin üzerine yeni bir dönüşüm ekler.
setTransform(a, b, c, d, e, f) kimlik matrisinden başlar; yani öncekileri yok
sayıp
verilen matrisi tek başına kurar. resetTransform() da eşdeğer bir «sıfırla»dır:
(1,0,0,1,0,0)
+ cihaz piksel oranı (DPR) uyumunu tarayıcı yönetir. Animasyon
döngüsünde
her kare başında yalnızca dünya çizimini tekrar kurmak istiyorsanız
resetTransform
sonra dünya translate’i güvenli temiz başlangıç verir — aksi halde önceki
karenin
translate birikimi sessizce taşınır.
Kare başı sıfırlama
Aşağıdaki örnek, drawScene çağrılmadan önce matrisin bilinçli olarak temiz
olduğunu varsayar. Oyun döngüsünde «her şeyi clearRect sonra çiz» ile birlikte düşünün;
yalnızca clear etmek dönüşümü sıfırlamaz.
function drawScene(ctx, camera) {
ctx.resetTransform(); // önceki karenin matrisi kalmaz
ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);
ctx.translate(-camera.x, -camera.y);
drawWorld(ctx);
}
setTransform ile tek seferde kurmak
Bilinen bir ölçek ve kaydırma için üç ayrı
scale/rotate/translate
yerine tek setTransform hem okunabilir hem hata riskini düşürür. Örnek: sadece
yatay aynalama + kaydırma (sprite sheet’te sol bakışı sağa çevirmek).
function drawSpriteFlipped(ctx, image, destX, destY, w, h) {
ctx.save();
// sx = -1 → x aynalanır; hedefe hizalamak için öteleme
ctx.setTransform(-1, 0, 0, 1, destX + w, destY);
ctx.drawImage(image, 0, 0, w, h, 0, 0, w, h);
ctx.restore();
}
ctx.getTransform(), desteklenen ortamlarda DOMMatrix
döndürür;
tıklanan ekran koordinatını dünya koordinatına çevirmek (ters matris) veya isabet testi için
ileri seviyede kullanılır — formül ve güvenlik
Event
coordinates ile bir arada düşünülür.
Özet: dönüşüm fillStyle gibi kalıcı bağlam durumudur. Geçici
deneyim
için save/restore, kare başı temiz başlangıç için
resetTransform / setTransform — ikisinin rolleri
State sistemi sayfasında birbirine
bağlanır.
Resize ile birlikte düşünün
canvas.width değişince bağlam durumu (dönüşüm dahil) sıfırlanır; piksel
oranı ve
CSS kutusu ile uyumlu yeniden boyut için
Resize mantığı sayfasına geçin.
Bu
sayfa koordinat sözleşmesini sabitler; cihazlar arası keskinlik orada ele alınır.
Piksel hizalama: keskin 1 px çizgiler
lineWidth = 1 ile tam sayı koordinatta (10, 20) çizilen yatay veya
dikey çizgi, piksel ızgarasının iki satırına yayılır ve bulanık görünür.
Çözüm:
eksene dik çizgide koordinatı n + 0.5 yapmak (ör.
ctx.moveTo(0, 40.5)).
Bu, canvas’ın ayrık piksel dünyasına özgü bir ayrıntıdır; vektör grafik editörlerindeki
«snap»
alışkanlığının kod karşılığıdır.
2D context sayfasında ince kontur
uyarısı bu yüzden koordinat sayfasına bağlanmıştı. Dönüşüm uygulandıktan sonra (özellikle
scale) yarım piksel kuralı bozulabilir; HUD ve grid çizgilerinde dönüşüm
öncesi/sonrası
katman ayırmak işe yarar.
ctx.strokeStyle = '#5ec8ff';
ctx.lineWidth = 1;
ctx.beginPath();
ctx.moveTo(0, 40.5);
ctx.lineTo(canvas.width, 40.5);
ctx.stroke();
Olay koordinatı: pencere ≠ canvas
Fare olaylarındaki clientX / clientY (veya offsetX)
viewport uzayındadır; canvas bitmap uzayı değil. CSS ile ölçeklenmiş
canvas’ta
tıklama konumunu çizim koordinatına çevirmek için öğenin ekrandaki dikdörtgeni ile iç
width/height oranı kullanılır — formül ve kenar durumları
Event
coordinates sayfasının konusudur.
Bu sayfada hatırlatma: çizim mantığınızı canvas.width ızgarasında yazın; girdi
katmanı geldiğinde aynı ızgaraya map edin. İki uzayı karıştırmak «sprite
tıklanmıyor» hatasının en yaygın nedenlerinden biridir.
- Çizim: attribute width/height piksel uzayı.
- Dönüşüm: translate → rotate → draw kalıbı; geri almak için
restore. - İnce çizgi: eksene dik n + 0.5 hizalama.
- Fare: viewport → canvas dönüşümü ayrı başlık.
Three.js ile çakışmadan okumak
2D canvas vs 3D sahne ekseni
Three.js’te nesne position, rotation, scale ile
sahne
grafiğinde taşınır; canvas 2D’de aynı fikir
translate/rotate/scale
ile bağlam matrisinde yaşar — fakat tek düzlem ve sol üst orijin
sabittir.
2D HUD overlay’i ayrı canvas’ta tutmak, WebGL NDC uzayı ile piksel uzayını
karıştırmamayı
sağlar. ByteOmi
Koordinat
sistemleri 3D katmanını anlatır; bu sayfa 2D bitmap eksenidir.
Sıradaki: resize ve piksel oranı
Eksen ve dönüşüm netleştikten sonra, canvas’ın fiziksel piksel boyutunu pencere ve
devicePixelRatio ile eşlemek
Resize mantığı sayfasına geçer.
Önceki adımlar:
2D context,
Canvas oluşturma.