HTML5 Canvas · Canvas API & context
Resize mantığı: görünen kutu, piksel ızgarası ve DPR
Canvas’ta «tam ekran yap» demek yalnızca CSS width: 100% yazmak değildir. Tarayıcı
öğeyi ekranda büyütür; iç bitmap çoğu zaman eski piksel sayısında kalır — sonuç bulanık
ölçeklenmiş
çizimdir. Bu sayfa layout kutusu (CSS) ile iç çözünürlük
(canvas.width / height) arasındaki köprüyü kurar:
devicePixelRatio, yeniden boyut olayları ve bağlamın sıfırlanması birlikte
düşünülür.
Koordinat ekseni ve dönüşüm Koordinat sistemi sayfasında; yüzeyin ilk kurulumu Canvas oluşturma ile sabitlendi. Burada pencere büyüdükçe bitmap’i nasıl yeniden ayarlayacağınız, olay koordinatı eşlemesinin neden buna bağlı olduğu ve durum yığını neden sıfırlandığı anlatılır — tam stack detayı State sistemi sayfasına köprülenir.
Özet: resize kararının ana hatları
| Soru | Yanıt | Pratik |
|---|---|---|
| Neden bulanık? | CSS büyüttü, bitmap küçük kaldı | Backing store = css × DPR (yuvarlama ile) |
| Boyut kimde? | Attribute = piksel; CSS = görünen kutu | Ölç → attribute yaz |
width değişince? |
Bağlam sıfırlanır | Stil ve dönüşümü yeniden kur |
| Ne zaman dinle? | Pencere, konteyner, tam ekran | ResizeObserver + isteğe resize |
İki katman: CSS kutusu ve bitmap
Canvas oluşturma sayfasında
ayrılmıştı: <canvas width="600" height="400"> iç ızgarayı,
style="width:100%" ise sayfadaki görünen dikdörtgeni belirler. İkisi senkron
değilse tarayıcı bitmap’i filtre ile ölçekler (çoğu zaman bikübik veya
benzeri)
— vektör dünyasındaki «rasterize et» adımını kullanıcı her karede fark etmiş olur.
Resize mantığının özü: önce hedef mantıksal boyutu belirleyin (genelde CSS
pikseli), sonra devicePixelRatio ile çarpıp fiziksel piksel
sayısına yuvarlayın, canvas.width ve canvas.height atayın. Böylece
bir çizim koordinatı hâlâ bir pikseldir; koordinat sistemi sayfasındaki ızgarayla uyum
korunur.
devicePixelRatio ve keskin piksel
window.devicePixelRatio (DPR), bir CSS pikselinin kaç cihaz pikseline karşılık
geldiğini söyler. Retina ekranlarda 2 veya 3 görülür; tarayıcı yakınlaştırınca değişebilir.
Canvas backing store’u yalnızca CSS genişliğe eşitlerseniz çizgi halen yumuşak görünür; DPR
ile
çarpmak, bitmap’i fiziksel grid’e oturtur.
Önemli nüans: DPR çizim API’sinin bir parçası değildir; layout ve
görüntüleme
katmanının ölçek bilgisidir. Siz «bu canvas şu CSS kutusuna sığsın» dersiniz, sonra o
kutunun
cihaz pikseli karşılığını hesaplarsınız. Aynı DPR, medya sorguları veya
matchMedia('(resolution: …)') ile birlikte düşünülebilir; canvas tarafında
pratik
kaynak hâlâ çoğu zaman window.devicePixelRatio okumasıdır.
Yuvarlama ve «neredeyse keskin»
Math.floor(cssPx * dpr) genelde güvenli alt sınır verir;
Math.round
bazen daha simetrik sonuç üretir. Çok ince farklarda yarım piksel kayması olabilir; kritik
HUD
grid’lerinde bir kez test edin. DPR tamsayı değilse (bazı Windows ölçekleri %125, %150) «tam
keskin» her zaman mümkün olmayabilir; kabul toleransı üretim kararıdır.
Koordinat
sayfasındaki ince çizgi hizalaması, DPR sonrası bile scale ile
bozulabilir —
grid çizgilerini HUD katmanında minimal dönüşümle çizmek ayrı bir alışkanlıktır.
function backingDimensions(cssW, cssH, dpr = window.devicePixelRatio || 1) {
return {
width: Math.max(1, Math.floor(cssW * dpr)),
height: Math.max(1, Math.floor(cssH * dpr)),
dpr,
};
}
Yakınlaştırma ve tam ekran
Kullanıcı sayfayı %110 yaptığında veya tam ekran geçtiğinde DPR veya CSS boyutu
değişebilir;
dinleyici (resize / ResizeObserver) tetiklenmezse eski bitmap
ile
kalmak mümkündür. Üretimde «DPR veya kutuyu etkileyen her değişiklikte fit çalışsın»
disiplinini benimseyin; beşinci bölümdeki gözlemci bunun ana taşıyıcısıdır.
Canvas Resize & DPR Lab ile CSS kutusu, bitmap, DPR eşlemesi, CSS-only bulanıklık tuzakı ve girdi remap’i canlı deneyin.
client ↔ bitmap eşlemesini görün. Oluşturma sayfasındaki
Resolution
Lab attribute/CSS ayrımına odaklanır; bu demo DPR + resize döngüsüdür.
width / height ataması bağlamı sıfırlar
Özellik ataması: canvas.width = w veya canvas.height = h
(veya ikisi) yapıldığında tarayıcı bitmap’i yeniden ayırır ve çizim bağlamının tüm
durumunu sıfırlar: dönüşüm matrisi, klip, stil özellikleri, yol, shadow…
(spesifikasyon
gereği). Bu yüzden resize işleminden sonra getContext('2d') hâlâ aynı nesneyi
döndürse bile görünür çizim ayarlarınız kaybolmuş olur.
Özellikle sıfırlananlar arasında globalAlpha,
globalCompositeOperation, çizgi ve metin stilleri,
imageSmoothing*,
mevcut path ve setTransform ile kurulmuş matris sayılır — yani
koordinat ve
2D context sayfalarında öğrendiğiniz
her
«kalıcı ctx ayarı» fiilen yok olur. Bu, hatanın kaynağını bulmayı zorlaştırır: resize
sonrası ilk
karede «neden hep siyah doldurma var?» sorusunun cevabı çoğu zaman varsayılan bağlamdır.
Üretim kalıbı: kur → çiz
Üretim kalıbı: boyutu güncelle → bağlam varsayılanına döndüğü için temel stil ve
gerekirse
transform kur → sahneyi yeniden çiz.
State sistemi sayfasındaki
save/restore yığını, resize anında otomatik geri gelmez; saklanmış
snapshot’lar da attribute değişince geçersizleşmiş sayılır. Çözüm: tek bir
applyCanvasDefaults(ctx) (ve gerekiyorsa
configureWorldTransform(ctx))
fonksiyonunu resize ve ilk başlatmada aynen çağırmak.
function applyCanvasDefaults(ctx) {
ctx.setTransform(1, 0, 0, 1, 0, 0);
ctx.globalAlpha = 1;
ctx.globalCompositeOperation = 'source-over';
ctx.imageSmoothingEnabled = true;
ctx.fillStyle = '#0b0f14';
ctx.strokeStyle = '#2ee7f2';
ctx.lineWidth = 1;
// font, textAlign vb. ihtiyaca göre
}
function onCanvasResized(canvas, ctx) {
applyCanvasDefaults(ctx);
drawScene(ctx); // Tam sahne yeniden çizimi
}
save/restore burada yetmez
Resize öncesi yapılmış save() stack’i, bitmap yeniden ayrıldığında eski
durumu
geri yüklemez — yığın mantığı
State sistemi içinde anlatılır;
bu
bölümün özü: boyut değiştiyse kurulum rutinini tekrar çalıştır.
fitCanvasToContainer: tipik kalıp
Aşağıdaki kalıp, mantıksal boyutu (CSS pikseli) alır, DPR ile çarpar ve
attribute’lara yazar. Ölçüm kaynağı olarak konteynerın clientWidth /
clientHeight, getBoundingClientRect() veya
ResizeObserver’ın contentRect değerleri kullanılabilir — önemli
olan,
okuduğunuz sayının layout ile uyumlu tek bir kurala bağlı olmasıdır. Farklı kaynaklar
alt piksel veya scrollbar dahil/hariç tutarlılığı yaratabilir; beşinci bölümde gözlemci ile
sabitlemek yaygındır.
CSS ile eşitleme
Backing store büyüdüyse görünen kutunun da aynı oranda büyüdüğünden emin olun:
canvas.style.width = cssW + 'px' ve height genelde
contentRect ile aynı sayılara ayarlanır. Aksi halde tarayıcı yine bitmap’i
kutuya
sığdırmak için ölçekler — bu sayfanın birinci bölümündeki bulanıklığa dönersiniz.
function fitCanvasToCssPixels(canvas, cssW, cssH) {
const dpr = window.devicePixelRatio || 1;
const bw = Math.max(1, Math.floor(cssW * dpr));
const bh = Math.max(1, Math.floor(cssH * dpr));
if (canvas.width === bw && canvas.height === bh) {
return false; // attribute dokunulmadı → bağlam sıfırlanmadı
}
canvas.width = bw;
canvas.height = bh;
canvas.style.width = `${cssW}px`;
canvas.style.height = `${cssH}px`;
return true; // üçüncü bölüm: bağlam sıfır → applyCanvasDefaults çağır
}
fit’in true dönmesi, çizim ayarlarınızı sıfırladığınız anlamına
gelir
— bu yüzden çağıran tarafta «boyut değiştiyse defaults + tam redraw» zinciri kurulur. Yan
etkisiz
tekrar boyut atamalarından kaçınmak için yukarıdaki erken return false
faydalıdır.
ResizeObserver ve pencere resize
window.addEventListener('resize', …) yalnızca viewport değişiminde ateşlenir;
yan panele göre büyüyen bir flex içindeki canvas için konteyner boyutu değişse
bile
tetiklenmeyebilir. Modern çözüm: canvas’ı saran elemana ResizeObserver bağlamak
—
boyut değişimi DOM ölçümünden gelir.
Fırtınalı resize’da (sürüklerken sürekli olay) doğrudan ağır çizim yapmayın; bir bayrak veya
requestAnimationFrame ile tek karede birleştirmek
requestAnimationFrame
disipliniyle örtüşür. Detaylı yeniden çizim maliyeti
Redraw
cost sayfasına bırakılır.
function observeCanvasHost(hostEl, canvas, onSized) {
const ro = new ResizeObserver((entries) => {
for (const entry of entries) {
const cr = entry.contentRect;
onSized(cr.width, cr.height);
}
});
ro.observe(hostEl);
return () => ro.disconnect();
}
Girdi koordinatı resize ile birlikte
Çizim koordinatı = bitmap pikseli; fare olayı ekran veya öğe uzayındadır.
canvas.width ve görünen kutu değiştikçe ölçek oranı da değişir. Harita
tıklaması,
drag-select veya UI isabet testi yapıyorsanız olaydan gelen offsetX /
offsetY veya client çiftini, o andaki iç boyuta projekte etmeniz
gerekir. Resize & DPR Lab
üzerinde imleci gezdirerek client ↔ bitmap eşlemesini canlı
görün.
Oran: CSS kutusu ↔ bitmap
Basit çarpan: scaleX = canvas.width / rect.width,
scaleY = canvas.height / rect.height — burada rect canvas öğesinin
getBoundingClientRect() çıktısıdır (CSS pikseli). Ardından örneğin
bitmapX = (clientX - rect.left) * scaleX ile iç uzaya geçilir; yüksekliğe dik
düzenlemeler, tam ekran ve retina için
Event
coordinates sayfasında tablolaştırılır.
Resize sonrası «tıklama kaydı» çoğu zaman şu iki sebepten biridir: (1) eski
canvas.width ile oran hesaplamak, (2) CSS kutusu ile Bitmap’i senkronlamamış
olmak.
Fit fonksiyonu hem görüntüyü hem girdi oranını tutarlı tutar.
function eventToBitmap(canvas, clientX, clientY) {
const rect = canvas.getBoundingClientRect();
const scaleX = canvas.width / rect.width;
const scaleY = canvas.height / rect.height;
return {
x: (clientX - rect.left) * scaleX,
y: (clientY - rect.top) * scaleY,
};
}
Three.js / WebGL ile paralel okuma
Aynı problem, farklı API
Three.js WebGLRenderer.setSize(width, height, false) çağrısı görünen
boyutu; üçüncü parametre ile updateStyle kontrol edilir.
setPixelRatio(Math.min(window.devicePixelRatio, limit)) ise DPR tavanı
koyarak hem keskinliği hem maliyeti yönetir — 2D tarafta sizin
floor(css * dpr) ile yaptığınıza benzer bir soyutlama.
2D canvas’ta attribute ve CSS senkronunu elle yazarsınız; WebGL tarafında renderer çoğu temiz başlatmayı üstlenir. Önemli fark: Three sahnesinde «nesne koordinatları» kamera ve projeksiyon ile çarpılır; 2D bitmap ise doğrudan piksel ızgarasıdır. Bu yüzden HUD için ayrı 2D canvas katmanı kullanmak sık kalıptır. Karşılaştırma: Canvas vs WebGL.
Sıradaki: durum yığını
Boyut ayarlandıktan sonra stil ve dönüşümü güvenle yönetmek için
State sistemi sayfasına geçin —
orada
save/restore yığını, clip ile etkileşim ve iç içe
dönüşüm senaryoları derinleşir. Resize bu sayfada bağlamı «boşaltır»; state sayfası aynı
bağlamı
kasıtlı olarak nasıl katmanlayacağınızı anlatır.
Önceki adımlar: Koordinat sistemi, 2D context.