holodepth

HTML5 Canvas · Canvas API & context

State mantığı: bağlam yığını, save ve restore

CanvasRenderingContext2D tek bir nesne gibi görünse de içeride kalıcı durum taşır: dolgu rengi, çizgi kalınlığı, dönüşüm matrisi, kırpma bölgesi, şeffaflık ve birleştirme modu… Bir bölümü geçici olarak değiştirip sonra eski haline dönmek istediğinizde her özelliği tek tek geri yazmak hata üretir. Tarayıcının sunduğu çözüm save() ve restore() ile yığın tabanlı anlık görüntü saklamaktır — bu sayfa o yığını, olası hataları ve diğer başlıklarla sınırını canvas üreticisi gözüyle anlatır.

Koordinat ve matris Koordinat sistemi sayfasında; boyut değişince bağlamın sıfırlandığı senaryo Resize mantığı ile kesişir. Path ve dolgu kuralları Path sistemi ve Stroke vs fill başlıklarında; burada yalnızca «hangi stil hangi dalda kaldı?» sorusuna odaklanırız.

Özet: bağlam yığınının ana hatları

Kavram İşlev Dikkat
save() O anki bağlam durumunu yığına iter Her dal için dengeleyici restore
restore() Son kaydı geri yükler (LIFO) Fazla restore sessiz hata üretebilir
Yığın derinliği Birden fazla iç içe katman HUD / modal / mini-sahne desenleri
clip() Kırpma mevcut path’ten Kırpma da save ile korunur

Kalıcı durum: tek ctx, çok görünüm

2D context sayfasında geçmişti: fillStyle değişince sonraki tüm dolgular yeni renk kullanır. Bu «tek kalemlik» modeli hızlıdır; fakat aynı kare içinde biri koyu arka plan, biri yarı saydam ön plan çizmek istediğinizde renkleri elle sıfırlamak unutulmaya açıktır. save/ restore, bu geçişi otomatik ve sıra güvenli yapar: bir dal açıp stil seti uygularsınız, dalı kapatınca önceki set geri gelir.

Zihinsel çerçeve: bağlamı bir «kurulu makine» gibi düşünün — düğmeler stil özellikleri, dişliler dönüşüm matrisi, cam kırpma alanı. save anlık fotoğraf çeker, restore makineyi o fotoğraftaki ayarlara döndürür. Fotoğraf sayısı yığın derinliğidir; çekilen fotoğraf yokken restore çağrısı tarayıcıda genelde yığından çıkaracak kayıt kalmadığı için işlem etkisiz kalır veya uygulamanız yığın hatasına düşer — üretimde çift sayım ve kod gözden geçirme şarttır.

Ne kaydedilir, ne kaydedilmez?

Spesifikasyona göre yığına girenler arasında dönüşüm matrisi, kırpma, kompozit ayarları, gölgeler, çizgi ve metin stilleri yer alır; path nesnesi (geçerli alt yol tanımı) kaydın parçası değildir — beginPath ile kurduğunuz yol, save’ten önce veya sonra dalınıza göre yönetilir. Pratik kural: clip yapmadan önce path’i kapatıp save düşünün; path komutları Path sistemi sayfasında detaylanır.

Piksel verisi (bitmap içeriği) yığına girmez; save çizilmiş görüntüyü geri almaz, yalnızca çizim ayarlarını saklar. Temizlemek için clearRect veya yeniden boyama Clear & redraw düşüncesindedir.

save ve restore: LIFO sözleşmesi

save() çağrısı yığına bir anlık bağlam görüntüsü iter. restore() en üstteki kaydı çıkarır ve bağlamı o duruma döndürür — bu nedenle LIFO (son giren ilk çıkar) düşüncesi şarttır. İç içe üç seviye UI çiziyorsanız üç çift çağrı beklenir: save → … → restore en içteki dal için, dışa doğru genişleyen kayıtlar üst üste.

function drawPanel(ctx, x, y) {
  ctx.save();
  ctx.translate(x, y);
  ctx.fillStyle = 'rgba(18, 24, 34, 0.92)';
  ctx.strokeStyle = '#2ee7f2';
  ctx.lineWidth = 2;
  ctx.fillRect(0, 0, 220, 120);
  ctx.strokeRect(0, 0, 220, 120);
  ctx.restore();
  // restore sonrası fillStyle, strokeStyle, lineWidth, translate önceki kareye döner
}

Çift sayım disiplini

Üretim kodunda save açtığınız her yolda erken dönüş olsa bile restore çağrılmasını garanti eden bir yapı tercih edin — örneğin try/finally veya tek sorumluluklu küçük çizim fonksiyonları. İç fonksiyon hata fırlatırsa yığın dengesiz kalabilir; sonraki çizimler beklenmedik stilde devam eder — bu sınıf hataları koddaki tüm save sayısını gözle sayarak bulmak gerekir.

Context State Stack Visualizer ile save/restore yığınını, canlı ctx durumunu ve «restore unut» sızıntısını deneyin.

Context State Stack Visualizer: Her save() yığına bir frame ekler; restore() LIFO ile geri alır. «restore unut» ile stil ve dönüşüm birikir — redraw ile dünya katmanına sızıntıyı görün. Stil komutları için 2D Context Playground.

İç içe katmanlar: dünya, panel, ipucu

Oyunlarda sık desen: önce dünya için translate(-camera) + dünya çizimi, ardından save ile HUD koordinatına geçiş — böylece dünya dönüşümü HUD’u kirletmez. İkinci save ile küçük bir tooltip çizilir; her sekme kendi restore ile kapanır. Derinlik, yığındaki kayıt sayısı kadardır; tarayıcılar makul bir üst sınır uygular, aşırı iç içe çağrıdan kaçının.

Metin çizimi için font, textAlign, textBaseline da bağlam durumudur; HUD’da merkez hizalı, dünyada sol hizalı yazı aynı karede çakışmasın diye bu özellikleri dal içinde set edip restore ile temizlemek doğal eşleşmedir.

function drawHudTooltip(ctx, px, py, text) {
  ctx.save();
  ctx.setTransform(1, 0, 0, 1, 0, 0); // dünya transformundan sıyır
  ctx.font = '600 14px DM Sans, sans-serif';

  ctx.save();
  ctx.translate(px, py);
  const w = ctx.measureText(text).width + 16;
  ctx.fillStyle = 'rgba(232, 244, 255, 0.95)';
  ctx.fillRect(-w / 2, -32, w, 28);
  ctx.fillStyle = '#121820';
  ctx.textAlign = 'center';
  ctx.textBaseline = 'middle';
  ctx.fillText(text, 0, -18);
  ctx.restore();

  ctx.restore();
}

clip: kırpma bölgesi ve yığın

clip() (veya clip(path, fillRule)) mevcut alt yolu kırpma maskesi olarak uygular; sonraki çizimler yalnızca bu kesişim içinde görünür. Kırpma bölgesi de bağlam durumundadır — save öncesi ve sonrası farklı maskeler oluşturmak için kullanılır. Path’i nasıl kuracağınız Path sistemi konusudur; burada sadece «maskeli dal aç, çiz, dalı kapat» akışı önemlidir.

function drawInCircle(ctx, cx, cy, r, drawContents) {
  ctx.save();
  ctx.beginPath();
  ctx.arc(cx, cy, r, 0, Math.PI * 2);
  ctx.clip();
  drawContents(ctx); // yalnızca daire içi çizilir
  ctx.restore();
}

globalCompositeOperation ve globalAlpha

globalAlpha ve globalCompositeOperation de kalıcı bağlam özellikleridir — tek efekt için ayarlayıp unutursanız sonraki tüm çizimler soluk veya yanlış blend modunda kalır. save/restore bu «sızıntıyı» kesen en pratik araçtır. Tam mod tablosu ve örnekler Composite operations sayfasındadır; burada yalnızca yığınla birlikte düşünmek yeterli.

globalAlpha, o anda çizilen her şeye çarpan bir üst sınırdır: dolgu ve çizgi renklerindeki saydamlıkla birlikte işlenir; yarım bırakılan alfa, metin ve sprite’ları aynı ölçüde soluk gösterir. Bileşik işlem modu da kare boyunca kalır — bu yüzden «önce normal dünya, sonra bir katmanda özel blend, sonra tekrar varsayılan» düzeninde her özel katman kendi save/restore çiftine sığdırılmalıdır. Üst üste bindirme sırasını değiştirmek istemiyorsanız, zayıf alfa ile UI üzerinde deneme yapmayın; dalı kapatıp çıktıyı kontrol edin.

Parlama veya «ekle» (lighter) efektini tek sprite için kullanıp ardından source-over dönmeniz gerekiyorsa bunu dal içinde yapın; aksi halde partikül sisteminiz tüm sonraki UI katmanını bozar. Aynı desen, geçici olarak koyulaştırma veya silikleştirme için düşük globalAlpha kullandığınız mini sahneler için de geçerli: efektli dal bittikten sonra ana bağlamın opak ve normal bileşimi kaldığından emin olun.

Gölge (shadowBlur, shadowColor …) bağlamın parçasıdır; alfa veya bileşik mod değişince gölge izi de beklenmedik görünebilir. Özel efekt + gölge kombinasyonunu yine tek dalda toplayın; karmaşık birleşimleri dokümante edilmiş yardımcı fonksiyonlara bölün ki çağıran kod tek satırda «temiz bağlam» varsayabilsin.

function drawAdditivePass(ctx, drawPass) {
  ctx.save();
  ctx.globalCompositeOperation = 'lighter';
  ctx.globalAlpha = 0.55;
  drawPass(ctx);
  ctx.restore();
  // restore sonrası: source-over ve çağıranın alfa / işlem modu
}

Dönüşüm ve save: aynı sahnede birlikte

translate / rotate / scale matrisi yığında saklanır. Bir nesneyi yerel uzayda çizip sonra dünya uzayına dönmek için Koordinat sistemi sayfasındaki translate → çiz → restore kalıbı ile örtüşür. setTransform veya resetTransform kullanıyorsanız save, o anki matrisi de paketler — kare başı sıfırlama stratejisi koordinat sayfasında köprülenmiştir; bu başlıkta odak, «dal içinde serbest dönüş, dal dışında temiz eksen» disiplinidir.

save() tek anlık görüntüdedir: o anda hem dönüşüm matrisi hem dolgu çizgisi hem kırpma hem de §5’teki alfa ve bileşik mod aynı kayda girer. Bu yüzden bir varlığı döndürüp çizmek, ardından dünyayı «eskisi gibi» sürdürmek için çoğunlukla yalnızca bir çift çağrı yeterli — araya sızıp kalan rotate kümülatif birikiminden kaçınmış olursunuz. restore atlaması, sahneyi giderek kaydıran koordinat hatalarının en sık nedenidir.

Yerel uzay dalı ve kümülatif dönüşüm tuzağı

Aynı ctx üzerinde ardışık translate/rotate çağrıları matrisi çarpar; her adım bir öncekine eklenir. Bir bileşen kendi merkezinde çiziliyorsa desen şudur: save → öğe için translate/rotate → çizim → restore. Böylece bir sonraki öge «temiz» bir üst düzlem transformu ile başlar. Çoklu ögeyi tek dalda üst üste yığmak yerine, öge başına dal kullanmak hata yüzeyini daraltır — özellikle animasyon veya dinamik pivotlarda.

Dünya kamerası (ör. translate(-camX, -camY)) genelde karenin büyük dalında, tek bir varlık ise iç içe daha küçük bir dalda ele alınır; iç dal bittiğinde dış dalın kamera dönüşümü korunur. §3’teki HUD örneğinde olduğu gibi, ekran piksel uzayına sıçramak için bazen önce iç restore, sonra setTransform(1,0,0,1,0,0) ile tam sıfırlama gerekir — hangi dalın neyi taşıdığını anlamlı isimlerle ayrılmış fonksiyonlara bölmek, yığını akılda tutmayı kolaylaştırır.

function drawIconAt(ctx, worldX, worldY, angleRad, drawIconLocal) {
  ctx.save();
  ctx.translate(worldX, worldY);
  ctx.rotate(angleRad);
  // drawIconLocal: (0,0) ikon merkezi veya sol üst sözleşmesine göre
  drawIconLocal(ctx);
  ctx.restore();
}

Anti-kalıplar ve hata ayıklama

Eşleşmeyen restore: Bir save eksik veya fazla restore yığını deler. Üç şüpheli belirti: beklenmedik globalAlpha, kaymış orijin, kaybolan kırpma. Geçici çözüm: kare başında setTransform(1,0,0,1,0,0) + tam stil kurulumu (ancak kök nedeni yığını dengelemek); kalıcı çözüm: save/restore çiftlerini fonksiyon sınırlarına kilitlemek.

Stil sızıntısı: Kütüphane çağrısı içinde fillStyle değiştirip geri yazmayan kod, sahneyi kirletir. Her modül kendi save/restore’unu sarmalı veya dokümantasyonla «bağlamı değiştirmez» taahhüdü vermelidir.

  • İç içe save sayısı = beklenen restore sayısı mı?
  • Dal içinde return olsa bile restore garanti mi?
  • Üçüncü parti çizim öncesi/sonrası bağlam «temiz» mi?

Resize: yığın yetmez, bitmap yenilenir

Resize mantığı sayfasında geçmişti: canvas.width / height atandığında yığın da dahil tüm bağlam durumu sıfırlanır — bu, save ile saklanmış hayali bir geri dönüş yoktur. Boyut değiştikten sonra yığın derinliği anlamını yitirir; uygulama genelde applyCanvasDefaults ve tam sahne yeniden çizimi yapar. State yönetimi resize sonrasında «önceki karenin yığını»nı kurtarmaz; yeni karedir.

Bu sayfanın sınırı

Animasyon döngüsünün kendisi (requestAnimationFrame, güncelle/çiz ayrımı) ayrı klasördedir. Bağlam yığını yalnızca tek çizim geçişindeki dal yönetimidir; oyun durumu veya sahne grafik düğümleri Oyun durumu gibi konularda modellenir — canvas state ile karıştırmayın.

Sıradaki: path ve şekil çizimi

Stil ve dönüşüm dallarını güvenceye aldıktan sonra geometri komutlarına derinlemesine geçmek için Path sistemi sayfasına devam edebilirsiniz. Önceki adımlar: Resize mantığı, Koordinat sistemi.