ऑफलाइन web games के लिए PWA
Progressive Web Apps की मदद से players आपके game को install करके ऑफलाइन खेल सकते हैं. यह tutorial बताता है कि एक web game में PWA support कैसे जोड़ें.
1) Web App Manifest
manifest.json बनाएं:
{
"name": "My Awesome Game",
"short_name": "AwesomeGame",
"description": "An awesome game you can play offline",
"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 में link करें:
<link rel="manifest" href="/manifest.json">
<meta name="theme-color" content="#4ade80">
<link rel="apple-touch-icon" href="/icons/icon-192.png">2) बेसिक service worker
sw.js बनाएं:
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 register करें
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) network update के साथ cache-first
games के लिए बेहतर है, background updates के साथ तेज़ loading:
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) updates के लिए versioned cache
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) game updates संभालना
जब कोई update उपलब्ध हो तो players को सूचित करें:
// 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>Game updated! Refresh to get the latest version.</p>
<button onclick="location.reload()">Refresh</button>
`
banner.className = 'update-banner'
document.body.appendChild(banner)
}7) ऑफलाइन detection
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) Install prompt
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 और iPadOS पर beforeinstallprompt event कभी fire नहीं होता, इसलिए ऊपर वाला install button सिर्फ Chromium browsers में दिखता है. iPhone और iPad users Share पर tap करके फिर "Add to Home Screen" चुनकर install करते हैं. 2026 में एक चीज़ बदली है जो जानने लायक है: Safari 26 (iOS 26 / iPadOS 26) से, Home Screen में जोड़ी गई हर site डिफ़ॉल्ट रूप से एक web app की तरह खुलती है, और "Open as Web App" toggle डिफ़ॉल्ट रूप से on रहता है. अब iOS पर installability की कोई शर्त नहीं रही, फिर भी एक manifest और service worker ship करने से आपको कहीं बेहतर ऑफलाइन अनुभव मिलता है.
9) leaderboards के लिए background sync
support के बारे में ध्यान दें: Background Sync API सिर्फ Chromium browsers (Chrome, Edge, Opera, Samsung Internet) में लागू है. Firefox और Safari (iOS समेत) इसे support नहीं करते, इसलिए हमेशा feature-detect करें और एक fallback रखें जो अगली सामान्य app load पर scores submit करे.
// 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 testing
Chrome DevTools:
- Application > Service Workers
- Application > Manifest
- Application > Cache Storage
- Network > Offline checkbox
Lighthouse audit:
- PWA audit चलाएं
- installability जांचें
- ऑफलाइन capability जांचें
असली device पर testing:
- फ़ोन की home screen पर install करें
- airplane mode चालू करें
- सभी features ऑफलाइन test करें
संबंधित
- game caching के लिए service workers
- IndexedDB game सेव
- एक web game ship करें जो तेज़ी से load हो
- मोबाइल-फ्रेंडली web games — PWA games के लिए touch controls और viewport handling
- streaming asset loading — ऐसी loading रणनीतियां जो service worker caching के साथ काम करती हैं
बाहरी संसाधन
- MDN: Progressive Web Apps — पूरा PWA documentation
- web.dev: Learn PWA — Google का PWA learning path
- MDN: Web App Manifest — manifest file reference