Çevrimdışı web oyunları için PWA
Progresif Web Uygulamaları, oyuncuların oyununuzu yüklemesine ve çevrimdışı oynamasına olanak tanır. Bu eğitim, bir web oyununa PWA desteğinin nasıl ekleneceğini gösterir.
1) Web Uygulaması Manifesti
manifest.json dosyasını oluşturun:
{
"name": "Harika Oyunum",
"short_name": "HarikaOyun",
"description": "Çevrimdışı oynayabileceğiniz harika bir oyun",
"start_url": "/",
"display": "fullscreen",
"orientation": "landscape",
"background_color": "#1a1a2e",
"theme_color": "#4ade80",
"icons": [
{
"src": "/icons/icon-192.png",
"sizes": "192x192",
"type": "image/png"
},
{
"src": "/icons/icon-512.png",
"sizes": "512x512",
"type": "image/png"
},
{
"src": "/icons/icon-maskable.png",
"sizes": "512x512",
"type": "image/png",
"purpose": "maskable"
}
]
}HTML dosyanızda buna bağlantı verin:
<link rel="manifest" href="/manifest.json">
<meta name="theme-color" content="#4ade80">
<link rel="apple-touch-icon" href="/icons/icon-192.png">2) Temel service worker
sw.js dosyasını oluşturun:
const CACHE_NAME = 'game-v1'
const ASSETS = [
'/',
'/index.html',
'/game.js',
'/style.css',
'/assets/sprites.png',
'/assets/sounds/jump.mp3',
'/assets/sounds/music.mp3',
]
// Install: cache assets
self.addEventListener('install', (event) => {
event.waitUntil(
caches.open(CACHE_NAME).then((cache) => {
return cache.addAll(ASSETS)
})
)
})
// Activate: clean old caches
self.addEventListener('activate', (event) => {
event.waitUntil(
caches.keys().then((keys) => {
return Promise.all(
keys.filter(key => key !== CACHE_NAME)
.map(key => caches.delete(key))
)
})
)
})
// Fetch: serve from cache, fall back to network
self.addEventListener('fetch', (event) => {
event.respondWith(
caches.match(event.request).then((cached) => {
return cached || fetch(event.request)
})
)
})3) Service worker'ı kaydetme
if ('serviceWorker' in navigator) {
window.addEventListener('load', async () => {
try {
const registration = await navigator.serviceWorker.register('/sw.js')
console.log('SW registered:', registration.scope)
} catch (err) {
console.error('SW registration failed:', err)
}
})
}4) Ağ üzerinden güncellemeli önbellek öncelikli strateji
Oyunlar için daha iyidir: arka planda güncellemeler yapılırken hızlı yükleme sağlar.
self.addEventListener('fetch', (event) => {
event.respondWith(
caches.open(CACHE_NAME).then(async (cache) => {
const cached = await cache.match(event.request)
// Start network fetch in background
const fetchPromise = fetch(event.request).then((response) => {
if (response.ok) {
cache.put(event.request, response.clone())
}
return response
}).catch(() => null)
// Return cached immediately, or wait for network
return cached || fetchPromise
})
)
})5) Güncellemeler için sürümlendirilmiş önbellek
const CACHE_VERSION = 'v2'
const STATIC_CACHE = `static-${CACHE_VERSION}`
const DYNAMIC_CACHE = `dynamic-${CACHE_VERSION}`
const STATIC_ASSETS = [
'/',
'/index.html',
'/game.js',
// ... core assets that rarely change
]
self.addEventListener('install', (event) => {
self.skipWaiting() // Activate immediately
event.waitUntil(
caches.open(STATIC_CACHE).then(cache => cache.addAll(STATIC_ASSETS))
)
})
self.addEventListener('activate', (event) => {
event.waitUntil(
caches.keys().then(keys => {
return Promise.all(
keys.filter(key => !key.includes(CACHE_VERSION))
.map(key => caches.delete(key))
)
})
)
clients.claim() // Take control immediately
})6) Oyun güncellemelerini yönetme
Bir güncelleme kullanıma sunulduğunda oyuncuları bilgilendirin:
// In main app
let refreshing = false
navigator.serviceWorker.addEventListener('controllerchange', () => {
if (!refreshing) {
refreshing = true
showUpdateNotification()
}
})
function showUpdateNotification() {
const banner = document.createElement('div')
banner.innerHTML = `
<p>Oyun güncellendi! En son sürümü edinmek için yenileyin.</p>
<button onclick="location.reload()">Yenile</button>
`
banner.className = 'update-banner'
document.body.appendChild(banner)
}7) Çevrimdışı durumu algılama
function updateOnlineStatus() {
if (navigator.onLine) {
hideOfflineBanner()
syncGameData()
} else {
showOfflineBanner()
}
}
window.addEventListener('online', updateOnlineStatus)
window.addEventListener('offline', updateOnlineStatus)
function showOfflineBanner() {
document.getElementById('offline-banner').style.display = 'block'
}
function hideOfflineBanner() {
document.getElementById('offline-banner').style.display = 'none'
}8) Yükleme istemi
let deferredPrompt = null
window.addEventListener('beforeinstallprompt', (e) => {
e.preventDefault()
deferredPrompt = e
showInstallButton()
})
function showInstallButton() {
const btn = document.getElementById('install-btn')
btn.style.display = 'block'
btn.addEventListener('click', installApp)
}
async function installApp() {
if (!deferredPrompt) return
deferredPrompt.prompt()
const { outcome } = await deferredPrompt.userChoice
console.log('Install prompt outcome:', outcome)
deferredPrompt = null
document.getElementById('install-btn').style.display = 'none'
}
window.addEventListener('appinstalled', () => {
console.log('App installed!')
deferredPrompt = null
})iOS ve iPadOS'te beforeinstallprompt olayı hiçbir zaman tetiklenmez; bu nedenle yukarıdaki yükleme düğmesi yalnızca Chromium tarayıcılarında görünür. iPhone ve iPad kullanıcıları, Paylaş'a ve ardından "Ana Ekrana Ekle" seçeneğine dokunarak yükleme yapar. 2026'da değişen ve bilinmesi gereken bir nokta var: Safari 26'dan (iOS 26 / iPadOS 26) itibaren Ana Ekran'a eklenen her site, varsayılan olarak etkin olan "Web Uygulaması Olarak Aç" seçeneğiyle birlikte varsayılan olarak bir web uygulaması şeklinde açılır. iOS'te artık yüklenebilirlik şartı yoktur; ancak manifest ve service worker sunmak yine de çok daha iyi bir çevrimdışı deneyim sağlar.
9) Liderlik tabloları için arka plan senkronizasyonu
Destek konusunda önemli bir not: Background Sync API yalnızca Chromium tarayıcılarında (Chrome, Edge, Opera, Samsung Internet) uygulanmıştır. Firefox ve Safari (iOS dâhil) bunu desteklemez; bu nedenle her zaman özellik algılama kullanın ve skorları uygulamanın bir sonraki normal yüklenişinde gönderen bir yedek yöntem bulundurun.
// In service worker
self.addEventListener('sync', (event) => {
if (event.tag === 'sync-scores') {
event.waitUntil(syncScores())
}
})
async function syncScores() {
const db = await openDB('game', 1)
const pendingScores = await db.getAll('pending-scores')
for (const score of pendingScores) {
try {
await fetch('/api/scores', {
method: 'POST',
body: JSON.stringify(score),
headers: { 'Content-Type': 'application/json' }
})
await db.delete('pending-scores', score.id)
} catch (err) {
// Will retry on next sync
break
}
}
}
// In main app
async function submitScore(score) {
try {
await fetch('/api/scores', { method: 'POST', body: JSON.stringify(score) })
} catch {
// Save for later sync
const db = await openDB('game', 1)
await db.add('pending-scores', { ...score, id: Date.now() })
if ('serviceWorker' in navigator && 'sync' in window.registration) {
await navigator.serviceWorker.ready
await registration.sync.register('sync-scores')
}
}
}10) PWA'yı test etme
Chrome DevTools:
- Application > Service Workers
- Application > Manifest
- Application > Cache Storage
- Network > Offline onay kutusu
Lighthouse denetimi:
- PWA denetimini çalıştırın
- Yüklenebilirliği kontrol edin
- Çevrimdışı çalışma özelliğini kontrol edin
Gerçek cihazda test:
- Telefonun ana ekranına yükleyin
- Uçak modunu etkinleştirin
- Tüm özellikleri çevrimdışı test edin
İlgili içerikler
- Oyunları önbelleğe almak için service worker'lar
- IndexedDB ile oyun kayıtları
- Hızlı yüklenen bir web oyunu yayınlama
- Mobil uyumlu web oyunları — PWA oyunları için dokunmatik kontroller ve viewport yönetimi
- Akışlı varlık yükleme — service worker önbelleğiyle çalışan yükleme stratejileri
Harici Kaynaklar
- MDN: Progresif Web Uygulamaları — kapsamlı PWA belgeleri
- web.dev: PWA'yı Öğrenin — Google'ın PWA öğrenme yolu
- MDN: Web Uygulaması Manifesti — manifest dosyası referansı