Duyarlı Oyun Tuvali
Doğru şekilde ölçeklenmeyen bir tuval, farklı ekran boyutlarında bulanık görünür veya bozulur. Bu sorunu sayamayacağım kadar çok kez ayıkladım; sizi bu zahmetten kurtarayım.
Farklı ölçekleme modlarını deneyin:
Temel Sorun
Canvas'ın kolayca karıştırılabilen iki boyutu vardır. CSS boyutu, ekranda ne kadar büyük görüneceğini belirler. Dahili çözünürlük ise gerçekte kaç piksel çizdiğinizi belirler. Bunlar eşleşmezse bulanıklık veya bozulma oluşur.
Retina bir ekranda CSS değeriniz 800x600 olabilir, ancak ekranın fiziksel çözünürlüğü aslında 1600x1200 pikseldir. Canvas'ın dahili çözünürlüğü yalnızca 800x600 ise tarayıcı bunu büyütür ve her şey bulanık görünür.
Temel Duyarlı Kurulum
Bir canvas'ın pencereyi doldurmasını ve net görünmesini şu şekilde sağlayabilirsiniz:
const canvas = document.getElementById('game')
const ctx = canvas.getContext('2d')
function resize() {
const dpr = window.devicePixelRatio || 1
const width = window.innerWidth
const height = window.innerHeight
// Dahili çözünürlüğü fiziksel piksellerle eşleşecek şekilde ayarla
canvas.width = width * dpr
canvas.height = height * dpr
// CSS boyutunu mantıksal piksellerle eşleşecek şekilde ayarla
canvas.style.width = width + 'px'
canvas.style.height = height + 'px'
// Mantıksal piksellerle çizim yapabilmek için bağlamı ölçekle
ctx.scale(dpr, dpr)
}
window.addEventListener('resize', resize)
resize()İşin püf noktası ctx.scale(dpr, dpr) satırıdır. Bundan sonra (100, 100) konumuna çizim yapabilirsiniz ve DPR değerinden bağımsız olarak doğru yerde görünür. Canvas, 2x bir ekranda dahili olarak 200x200 çözünürlükte görüntü oluşturur, ancak kodunuzun bunu bilmesi gerekmez.
Dikkat edilmesi gereken bir nokta: ctx.scale birikimlidir. Yeniden boyutlandırma işlevini birden çok kez çağırırsanız ölçek giderek büyür. Ölçeklemeden önce ctx.setTransform(1, 0, 0, 1, 0, 0) ile dönüşümü sıfırlayın veya yalnızca bir kez ölçekleyin.
Letterbox ile Sabit En-Boy Oranı
Birçok oyun belirli bir en-boy oranına ihtiyaç duyar. 16:9 oyununuzun 21:9 ultra geniş bir monitörü doldurmak için esnetilmesini istemezsiniz. Letterbox, oranı korumak için siyah şeritler ekler.
const GAME_WIDTH = 1920
const GAME_HEIGHT = 1080
const ASPECT = GAME_WIDTH / GAME_HEIGHT
function resizeWithLetterbox() {
const windowWidth = window.innerWidth
const windowHeight = window.innerHeight
const windowAspect = windowWidth / windowHeight
let width, height
if (windowAspect > ASPECT) {
// Pencere oyundan daha genişse yanlara şerit ekle
height = windowHeight
width = height * ASPECT
} else {
// Pencere oyundan daha uzunsa üste ve alta şerit ekle
width = windowWidth
height = width / ASPECT
}
canvas.style.width = width + 'px'
canvas.style.height = height + 'px'
canvas.style.position = 'absolute'
canvas.style.left = (windowWidth - width) / 2 + 'px'
canvas.style.top = (windowHeight - height) / 2 + 'px'
// Dahili çözünürlük sabit kalır
canvas.width = GAME_WIDTH
canvas.height = GAME_HEIGHT
}Letterbox şeritlerinin görünmesi için gövde arka planını siyah yapın:
body {
margin: 0;
background: #000;
overflow: hidden;
}Piksel Sanatı İçin Tam Sayılı Ölçekleme
Piksel sanatı tam sayılı ölçeklerde (1x, 2x, 3x) net, 2.7x gibi kesirli ölçeklerde ise bulanık görünür. image-rendering: pixelated kullanılsa bile 2.5x ölçek, bazı piksellerin 2 ekran pikseli, diğerlerinin ise 3 ekran pikseli genişliğinde olması anlamına gelir.
const GAME_WIDTH = 320
const GAME_HEIGHT = 180
function resizePixelPerfect() {
const maxWidth = window.innerWidth
const maxHeight = window.innerHeight
// Sığabilecek en büyük tam sayılı ölçeği bul
const scaleX = Math.floor(maxWidth / GAME_WIDTH)
const scaleY = Math.floor(maxHeight / GAME_HEIGHT)
const scale = Math.max(1, Math.min(scaleX, scaleY))
const width = GAME_WIDTH * scale
const height = GAME_HEIGHT * scale
canvas.width = GAME_WIDTH
canvas.height = GAME_HEIGHT
canvas.style.width = width + 'px'
canvas.style.height = height + 'px'
canvas.style.imageRendering = 'pixelated'
}Bunun karşılığında oyununuz ekranı tam olarak doldurmaz. 320x180 boyutundaki bir oyun 1920x1080 ekranda 5x ölçeklenir (1600x900) ve kenarlarda siyah şeritler oluşur. Bazı oyuncular bunu tercih ederken diğerleri, biraz bulanık olsa bile oyunun ekranı doldurmasını ister.
WebGL Canvas Ölçekleme
WebGL için canvas boyutuna ek olarak görünüm alanını da güncellemeniz gerekir:
function resizeGL(gl, canvas) {
const dpr = window.devicePixelRatio || 1
const width = canvas.clientWidth * dpr
const height = canvas.clientHeight * dpr
if (canvas.width !== width || canvas.height !== height) {
canvas.width = width
canvas.height = height
gl.viewport(0, 0, width, height)
return true // Yeniden boyutlandırıldı; projeksiyon matrislerini güncellemeniz gerekebilir
}
return false
}Bunu yalnızca yeniden boyutlandırma olaylarında değil, görüntü oluşturma döngünüzün başında çağırın. Bazı tarayıcılar bir pencereyi monitörler arasında sürüklediğinizde DPR değerini değiştirir; bunu da yakalamak istersiniz.
Yeniden Boyutlandırma Olaylarını Seyrekleştirme
Kullanıcı pencerenin kenarını sürüklerken yeniden boyutlandırma olayları hızla tetiklenir. Yeniden boyutlandırma işleviniz maliyetliyse takılmalar yaşanır.
let resizeTimeout
window.addEventListener('resize', () => {
clearTimeout(resizeTimeout)
resizeTimeout = setTimeout(resize, 100)
})100 ms'lik gecikme, yeniden boyutlandırmanın yalnızca kullanıcı sürüklemeyi bıraktıktan sonra gerçekleşmesini sağlar. Sürükleme sırasında daha akıcı bir yönetim için bunun yerine ResizeObserver kullanın:
const observer = new ResizeObserver(entries => {
resize()
})
observer.observe(document.body)ResizeObserver daha seyrek tetiklenir ve tarayıcı tarafından daha iyi optimize edilir.
Tam Ekran
Fullscreen API, ekranın tamamını kullanmanızı sağlar:
async function toggleFullscreen() {
if (!document.fullscreenElement) {
await canvas.requestFullscreen()
} else {
await document.exitFullscreen()
}
}
document.addEventListener('fullscreenchange', resize)requestFullscreen işlevini tıklama gibi bir kullanıcı etkileşimine yanıt olarak çağırmalısınız. Aksi takdirde tarayıcılar bunu engeller. Ayrıca kullanılabilir alan değişeceği için tam ekrana girerken ve tam ekrandan çıkarken yeniden boyutlandırmayı unutmayın.
Platformla ilgili bir sınırlama: 2026 ortası itibarıyla Fullscreen API, iPhone Safari'de öğeleri tam ekrana alma özelliğini hâlâ kapsamıyor. requestFullscreen masaüstü tarayıcılarda ve iPadOS'te çalışır, ancak iPhone'da canvas gibi rastgele öğeler için desteklenmez (yalnızca yerel video tam ekranı çalışır). Desteği if (canvas.requestFullscreen) ile algılayın ve desteklenmediğinde canvas'ı görünür görünüm alanını dolduracak şekilde boyutlandırmak veya kullanıcılardan oyunu PWA olarak ana ekranlarına eklemelerini istemek gibi uygun bir alternatif sunun.
Her Şeyi Bir Araya Getirme
Yaygın durumların tümünü yöneten bir sınıf:
class ResponsiveCanvas {
constructor(canvas, options = {}) {
this.canvas = canvas
this.ctx = canvas.getContext('2d')
this.gameWidth = options.width || 1280
this.gameHeight = options.height || 720
this.pixelArt = options.pixelArt || false
this.letterbox = options.letterbox !== false
this.bind()
this.resize()
}
bind() {
window.addEventListener('resize', () => this.resize())
document.addEventListener('fullscreenchange', () => this.resize())
}
resize() {
const aspect = this.gameWidth / this.gameHeight
let width = window.innerWidth
let height = window.innerHeight
if (this.letterbox) {
if (width / height > aspect) {
width = height * aspect
} else {
height = width / aspect
}
}
if (this.pixelArt) {
const scale = Math.max(1, Math.floor(Math.min(
window.innerWidth / this.gameWidth,
window.innerHeight / this.gameHeight
)))
width = this.gameWidth * scale
height = this.gameHeight * scale
}
this.canvas.width = this.gameWidth
this.canvas.height = this.gameHeight
this.canvas.style.width = width + 'px'
this.canvas.style.height = height + 'px'
this.canvas.style.position = 'absolute'
this.canvas.style.left = (window.innerWidth - width) / 2 + 'px'
this.canvas.style.top = (window.innerHeight - height) / 2 + 'px'
if (this.pixelArt) {
this.canvas.style.imageRendering = 'pixelated'
}
}
get width() { return this.gameWidth }
get height() { return this.gameHeight }
}Şu şekilde kullanın:
const responsive = new ResponsiveCanvas(document.getElementById('game'), {
width: 320,
height: 180,
pixelArt: true,
letterbox: true
})
// Oyun kodunuzda responsive.width ve responsive.height kullanınSık Karşılaşılan Sorunlar
İnsanların sıkça gözden kaçırdığı birkaç nokta:
Canvas yeniden boyutlandırıldığında temizlenir. canvas.width veya canvas.height değerini ayarladığınızda canvas temizlenir. Her karede yeniden çizim yapmıyorsanız yeniden boyutlandırmadan sonra tekrar çizmeniz gerekir.
DPR değişebilir. Kullanıcı tarayıcı penceresini Retina bir ekrandan Retina olmayan bir ekrana sürüklerse DPR değişir. Bunu yalnızca yeniden boyutlandırma sırasında değil, görüntü oluşturma döngünüzde de kontrol edin.
iOS Safari görünüm alanı tuhaflıkları. Adres çubuğu gösterildiğinde veya gizlendiğinde görünüm alanının yüksekliği değişir. Mobil cihazlarda daha doğru boyutlandırma için window.visualViewport kullanın.
Bağlam durumu sıfırlanır. Canvas boyutlarını ayarlamak; dönüşümler, dolgu stilleri ve imageSmoothingEnabled dâhil olmak üzere bağlam durumunu sıfırlar. Yeniden boyutlandırmadan sonra bunları tekrar uygulayın.
Daha Fazla Kaynak
Canvas 2D oyun döngüsü, güncelleme ve görüntü oluşturma döngülerinin temellerini ele alır.
Piksel sanatı görüntüleme, net piksel grafiklerini daha ayrıntılı olarak inceler.
Mobil uyumlu web oyunları, dokunmatik girişi ve görünüm alanı sorunlarını ele alır.
WebGL temelleri, WebGL bağlamları için görünüm alanı ve canvas kurulumunu ele alır.
Hızlı yüklenen bir web oyunu yayınlama, performans için canvas boyutunu optimize etmeyi ele alır.