HTML5 Canvas · Performans ve optimizasyon
Offscreen Canvas: DOM dışı tampon ve işçi iş parçacığı rasterı
OffscreenCanvas, bellekte tutulan ve sayfada görünmeyen bir çizim
yüzeyidir;
görünür <canvas> öğesinden bağımsız olarak aynı 2D bağlam
modelini sunar. Amaç: ağır veya seyrek güncellenen grafikleri ana iş parçacığından
ayırmak, katmanları önceden üretmek ve gerektiğinde
worker içinde pikselleri hazırlayıp sonucu güvenli biçimde görünür tuvala
aktarmaktır. Bu sayfa Canvas 2D bakış açısıyla kalıpları sabitler;
Katman tuval ve
yeniden
boyutlandırma
düşüncesiyle birlikte okunmalıdır — tam ekran «her kare sil–çiz» maliyetinin özeti
Yeniden çizim maliyeti başlığındadır.
WebGL tabanlı transferControlToOffscreen ve bağlam devri bu sayfanın odağı
değildir;
burada öncelik Bitmap / 2D raster çıktısının üretilmesi ve köprülenmesidir.
Nesne tahsisatını azaltmak için
Nesne havuzlama komşu başlıkta
işlenir.
Özet: ne zaman işe yarar?
| Desen | Kazanç | Ödeme |
|---|---|---|
| Ayrı tampon / katman önbake | Ana karede daha az doğrudan çizim | Ek bellek, boyut senkronu |
| Worker + ImageBitmap | Uzun raster ana iş parçacığında bloklamaz | Kopya / aktarım ve uyumluluk kontrolü |
| Düşük frekanslı etiket / atlas | Metin ve karmaşık yollar tek sefer | Güncellemede tampon yenileme disiplini |
Offscreen Canvas nedir ve tamponun konumu
Tarayıcı belleğinde tutulan bir çizim yüzeyi düşünün: hem genişlik / yükseklik ayarı yapılır
hem de getContext('2d') ile bağlam alınır; ancak belgede karşılığı olan bir
kutu yoktur — işte bu yüzey
OffscreenCanvas ile temsil edilir. DOM’a bağlı olmayan ikinci bir
<canvas>
oluşturmak (
document.createElement('canvas') sonrası DOM’a eklememek ) benzer bir tampon
sağlar; standart API ise oluşturma ve işçi iş parçacığı aktarımını öngörür şekilde
ortaktır.
Görünür tuval ile ilişki tipik olarak tek yönlüdür: önce görünmez yüzeyde çizersiniz, sonra
drawImage ile görünür bağlamda yapıştırırsınız veya
ImageBitmap üzerinden aktarırsınız. Bu desen, tek başına «otomatik
hızlanma» değildir — her karede tam boy görüntü kopyalarsanız piksel sayısı yine
maliyetinizi belirler (
piksel bütçesi ).
Kazanım, işin nerede ve ne sıklıkta yapıldığını seçebilmenizdedir.
Çift tampon ( double buffering ) düşüncesine yakınsar: bir yüzey «arka planda» güncellenir, bir başka karede birleştirilir; fakat tarayıcı zaten kendi bileşim yolunu kullanır — burada odak, uygulama düzeyinde hangi katmanların seyrek güncellendiğini ayırmaktır. Katman stratejisi ile doğrudan örtüşür; bu sayfa API düzeyini tamamlar.
Güvenli oluşturma ve ortam denetimi
Üretim kodunda özellik varlığını kontrol etmek zorunludur: eski tarayıcılar veya kısıtlı
ortamlarda OffscreenCanvas oluşturucusu bulunmayabilir. Güvenli
yaklaşım: önce API’yi dene, başarısızsa DOM’suz klasik
<canvas>
örneği oluştur — ikisi de aynı 2D çizim komutlarını kabul eder; tek fark taşıma ve işçi
iş parçacığı ergonomisiidir.
Aşağıdaki yardımcı hiçbir dış kaynağa bağlanmaz; kullanıcı girdisi kullanmaz ve yalnız boyut doğrulaması yapar. Gerçek projede üst sınır ( maksimum genişlik / yükseklik ) ürün politikasıyla sabitlenmeli, aksi halde tek bir hatalı ölçüm bellekte çok büyük tampon tetikleyebilir — bu bir güvenlik / kararlılık konusudur, sadece performans değil.
/**
* Görünmez tampon: OffscreenCanvas veya DOM'suz canvas.
* @param {number} widthDevicePx - Cihaz pikselleri (tam sayı, > 0).
* @param {number} heightDevicePx
* @returns {{ canvas: OffscreenCanvas|HTMLCanvasElement, kind: 'offscreen'|'canvas' }}
*/
export function createDrawingBuffer(widthDevicePx, heightDevicePx) {
const w = Math.max(1, Math.floor(Number(widthDevicePx)));
const h = Math.max(1, Math.floor(Number(heightDevicePx)));
if (typeof OffscreenCanvas !== 'undefined') {
return { canvas: new OffscreenCanvas(w, h), kind: 'offscreen' };
}
const fallback = document.createElement('canvas');
fallback.width = w;
fallback.height = h;
return { canvas: fallback, kind: 'canvas' };
}
İki boyutlu bağlam ve görünür tuval köprüsü
Hem OffscreenCanvas hem de görünür öğe üzerindeki bağlam aynı çizim
komutlarını anlar; fark, kullanıcı arayüzünde doğrudan görünmemenizdir. Köprüleme kalıbı:
görünmez bağlamda çiz → görünür bağlamda drawImage(kaynak, …) ile tek komutta
yapıştır. Böylece ana döngüde karmaşık yol / metin işleri yerine çoğu zaman tek bir kopya
maliyeti ödersiniz — kopyanın boyutu yine piksel sayısıyla ölçülür.
Alfa kanalı ve birleştirme kuralları görünür bağlamın mevcut globalAlpha ve
globalCompositeOperation ayarlarından etkilenir; katmanları birleştirirken bu
ayarları bilerek sıfırlamak (
örneğin globalAlpha = 1 ve 'source-over' ) beklenmedik
saydamlıkları azaltır.
Bağlam maliyeti başlığında
pahalı özellikler ayrı işlenmiştir — köprü kodunda varsayılanları bilinçli tutmak
yeterlidir.
/**
* @param {CanvasRenderingContext2D} screenCtx - Görünür tuval bağlamı
* @param {HTMLCanvasElement|OffscreenCanvas} layerCanvas - Önceden çizilmiş kaynak yüzey
* @param {number} dx
* @param {number} dy
*/
export function stampLayer(screenCtx, layerCanvas, dx, dy) {
screenCtx.globalAlpha = 1;
screenCtx.globalCompositeOperation = 'source-over';
screenCtx.drawImage(layerCanvas, dx, dy);
screenCtx.restore();
}
Çözünürlük, DPR ve çift tampon bellek maliyeti
Görünür <canvas> CSS boyutu ile iç tampon genişliği / yüksekliği (
cihaz pikselleri ) ayrı kavramlardır; görünmez tamponda ise doğrudan iç boyutu seçersiniz.
İki katman da aynı DPR ile ölçeklenmezse köşeler bulanıklaşır veya kırpma hataları oluşur.
Pratik kural: statik katmanı yeniden ürettiğiniz her olayda (
örneğin pencere yeniden boyutu ) hem görünür hem görünmez tamponun cihaz piksel boyutlarını
aynı politikayla güncelleyin.
Yeniden
boyutlandırma mantığı ile uyum bu yüzdendir.
Bellek maliyeti kabaca genişlik × yükseklik × 4 bayt (
çok kanallı tampon iç temsiline bağlı olarak değişebilir ) düzeyinde düşünülür; iki tampon
yaklaşık iki kat buffer demektir. Mobil cihazlarda agresif çözünürlük, tek başına bellek
baskısı ve çöp toplayıcı tetikleri doğurabilir — üst sınır ve düşürülmüş DPR politikası
ürün kararıdır.
Yeniden çizim maliyeti · DPR
bölümü aynı gerekçeyi görünür tuval tarafında işler.
Worker içinde raster ve ImageBitmap aktarımı
Uzun süren dolgu, çok sayıda metin ölçümü veya büyük prosedürel desen ana iş parçacığını
kilitleyebilir. Desteklenen ortamlarda aynı raster işi
Worker içindeki bir OffscreenCanvas üzerinde
yapılır; sonuç transferToImageBitmap() ile donmuş bir
ImageBitmap olarak ana iş parçacığına aktarılır. Aktarımda
postMessage ikinci argümanına bitmap eklenerek bellek taşınır — ana tarafta
kullanım bitince bitmap.close() çağrısı kaynakları serbest bırakır.
Bu desen her projede gerekli değildir; ek karmaşıklık (
mesaj sözleşmesi, hata yüzeyi, tarayıcı uyumu ) getirir. Serileştirme veya paylaşılan bellek
köprüleri burada işlenmez — odak Canvas 2D çıktısının güvenli aktarımıdır.
transferControlToOffscreen ile görünür öğeyi WebGL’e devretmek farklı bir
üretim hattıdır; burada yalnızca «işçi raster → bitmap → görünür drawImage»
zinciri anlatılır.
Aşağıdaki örnekler minimaldir: sabit boyut ve deterministik çizim içerir; gerçek kodda
iptal (
AbortSignal ), sıra numarası ve düşük öncelikli kuyruk
düşünülmelidir.
worker.onmessage atamasını her istekte yenilemek öğretici olsa da üretimde tek
kurulum ve mesaj alanında iş kimliği daha güvenlidir.
// Worker içinde — sabit örnek çizim (harici veri yok).
self.onmessage = (event) => {
const { width, height } = event.data || {};
const w = Math.max(1, Math.floor(Number(width)));
const h = Math.max(1, Math.floor(Number(height)));
if (typeof OffscreenCanvas === 'undefined') {
self.postMessage({ ok: false, reason: 'no-offscreen' });
return;
}
const osc = new OffscreenCanvas(w, h);
const ctx = osc.getContext('2d', { alpha: true });
if (!ctx) {
self.postMessage({ ok: false, reason: 'no-2d-context' });
return;
}
ctx.fillStyle = '#1a2332';
ctx.fillRect(0, 0, w, h);
ctx.strokeStyle = '#5ec8ff';
ctx.lineWidth = 2;
ctx.strokeRect(8, 8, w - 16, h - 16);
const bitmap = osc.transferToImageBitmap();
self.postMessage({ ok: true, bitmap }, [bitmap]);
};
/**
* @param {CanvasRenderingContext2D} visibleCtx
* @param {Worker} worker
* @param {{ width: number, height: number }} size
*/
export function requestRasterLayer(visibleCtx, worker, size) {
worker.onmessage = (event) => {
const msg = event.data || {};
if (!msg.ok || !msg.bitmap) return;
const bmp = msg.bitmap;
visibleCtx.save();
visibleCtx.setTransform(1, 0, 0, 1, 0, 0);
visibleCtx.globalAlpha = 1;
visibleCtx.globalCompositeOperation = 'source-over';
visibleCtx.drawImage(bmp, 0, 0);
visibleCtx.restore();
bmp.close();
};
worker.postMessage(size);
}
Tekrar kullanım senaryoları ve katman düzeni
Yaygın profesyonel kullanımlar: düşük frekansla değişen kullanıcı arayüzü etiketleri için küçük ara tampon, dünya özetinin bir kez çizilip üzerine dinamik öğelerin eklenmesi, karmaşık ama seyrek güncellenen desenlerin önbake edilmesi. Bu rol dağılımı, statik / dinamik katman ayrımı ile birebir örtüşür; görünmez tampon yalnız araçtır, mimari karar «neyi ne sıklıkla yenilersiniz?» sorusuna bağlıdır.
Varlık önbelleği ve görsel yükleme zaman çizelgesi
Varlık
önbelleği ve
görsel
yükleme
başlıklarında işlenir; burada yinelemenin anlamı yok — köprü şudur: decode edilmiş
görseller zaten bitmap kaynak olarak drawImage ile kullanılabilir; görünmez
tampon ise çok adımlı bileşik sahneyi tek dokuda toplamak için kullanılır.
Çağrı sırasını düzenlemek ( önce opak katmanlar, sonra saydam ) ana iş parçacığında bile sürtünmeyi düşürür; Batching mantığı bu düzeni üst düzeyde tamamlar — işçi tampon onun yerine geçmez, tamamlayıcıdır.
Sınırlar, senkron ve sık düşülen varsayımlar
Görünmez tampon «GPU hızlandırması garantisi» değildir; raster işin doğası gereği piksel
işlersiniz. Tam boy bitmap her kare kopyalanıyorsa ana iş parçacığında yine ağır bir
drawImage ödersiniz — işçi tarafındaki kazanımı sıfırlayabilir veya gecikmeyi
«bir kare geriden» hissedilir yapabilir.
Worker çıktısı ile kullanıcı etkileşimi arasında tek kare gecikme sıradandır; imleç takibi
gibi düşük gecikme gerektiren etkileşimleri doğrudan ana bağlamda tutmak daha güvenlidir.
Ayrıca her ortamda transferToImageBitmap veya 2D worker bağlamı aynı
davranmayabilir — özellik projeye küçük bir uyumluluk tablosu (
hedef tarayıcı sürümleri ) çıkarılmalıdır.
Bellek sızıntısı riski: aktarılan her ImageBitmap için eşleşen bir
close() politikası yoksa bellek büyümeye devam eder. Üretimde bütçe ve üst
sınır
(
eşzamanlı kaç işçi işi ) açık yazılmalıdır.
Yeniden çizim bayrağı ile
birleştirildiğinde «değişmedi» dalında işçi işi tetiklenmez — gereksiz bitmap üretimi
kesilir.
Bu sayfanın sınırı
WebGPU compute, WebGL bağlam devri ve üç boyutlu iş hattı burada ele alınmaz. Odak: Canvas 2D ile üretilen rasterın görünmez tampon ve işçi iş parçacığı üzerinden güvenli aktarımı ile ürün düzeyinde maliyet / bellek dengesidir.
- Tampon boyutu ürün üst sınırıyla sabitlendi mi ( kötümser ölçüm koruması)?
- Görünür ve görünmez yüzeyler aynı DPR politikasını paylaşıyor mu?
- Aktarılan her ImageBitmap için
close()yolu tanımlandı mı? - Tam boy kopya hâlâ kare bütçesini aşıyorsa strateji gözden geçirildi mi?
- İşçi raster ile etkileşim gecikmesi ürün için kabul edilebilir mi?