मोबाइल-फ्रेंडली web games
मोबाइल सबसे बड़ा gaming platform है. कुछ बदलावों से आपका web game फोन और टैबलेट पर शानदार तरीके से चलता है.
1) Viewport setup
zoom को रोकें और सही scaling सुनिश्चित करें:
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">2) Fullscreen canvas
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
html, body {
width: 100%;
height: 100%;
overflow: hidden;
background: #000;
touch-action: none; /* Prevent browser gestures */
}
#game {
display: block;
width: 100%;
height: 100%;
}अगर आप UI का साइज़ height: 100% के बजाय viewport height से तय करते हैं, तो vh के बजाय dynamic viewport units dvh, svh, और lvh को चुनें. ये मोबाइल browser के सिकुड़ते/फैलते toolbar का ध्यान रखते हैं, जिसे 100vh नज़रअंदाज़ करता है (iOS पर 100vh toolbar-छिपी height होती है, इसलिए आपके layout का निचला हिस्सा कट जाता है). ये units Baseline Widely Available तक पहुंच चुके हैं और Chrome/Edge 108+, Firefox 101+, और Safari/iOS Safari 15.4+ में सपोर्टेड हैं, यानी करीब 92% users.
3) Touch event handling
const canvas = document.getElementById('game')
// Prevent default touch behaviors
canvas.addEventListener('touchstart', (e) => e.preventDefault(), { passive: false })
canvas.addEventListener('touchmove', (e) => e.preventDefault(), { passive: false })
// Track touches
const touches = new Map()
canvas.addEventListener('touchstart', (e) => {
for (const touch of e.changedTouches) {
touches.set(touch.identifier, {
x: touch.clientX,
y: touch.clientY,
startX: touch.clientX,
startY: touch.clientY,
})
}
})
canvas.addEventListener('touchmove', (e) => {
for (const touch of e.changedTouches) {
const t = touches.get(touch.identifier)
if (t) {
t.x = touch.clientX
t.y = touch.clientY
}
}
})
canvas.addEventListener('touchend', (e) => {
for (const touch of e.changedTouches) {
touches.delete(touch.identifier)
}
})अगर आपके game को mouse या stylus के साथ भी काम करना है, तो touch events के बजाय Pointer Events (pointerdown/pointermove/pointerup) पर विचार करें. MDN अब इन्हें एक ही, device-agnostic input model के तौर पर सुझाता है: एक code path touch, mouse, और pen को संभालता है, और multi-touch अब भी pointerId के ज़रिए काम करता है (जो ऊपर इस्तेमाल किए गए touch.identifier का समकक्ष है). touch-only games के लिए touch events बिल्कुल ठीक रहते हैं.
4) Virtual joystick
class VirtualJoystick {
constructor(x, y, radius) {
this.baseX = x
this.baseY = y
this.radius = radius
this.knobRadius = radius * 0.4
this.active = false
this.touchId = null
this.dx = 0
this.dy = 0
}
handleTouchStart(touch) {
const dist = Math.hypot(touch.clientX - this.baseX, touch.clientY - this.baseY)
if (dist < this.radius) {
this.active = true
this.touchId = touch.identifier
this.updatePosition(touch.clientX, touch.clientY)
}
}
handleTouchMove(touch) {
if (touch.identifier === this.touchId) {
this.updatePosition(touch.clientX, touch.clientY)
}
}
handleTouchEnd(touch) {
if (touch.identifier === this.touchId) {
this.active = false
this.touchId = null
this.dx = 0
this.dy = 0
}
}
updatePosition(x, y) {
let dx = x - this.baseX
let dy = y - this.baseY
const dist = Math.hypot(dx, dy)
if (dist > this.radius) {
dx = (dx / dist) * this.radius
dy = (dy / dist) * this.radius
}
this.dx = dx / this.radius
this.dy = dy / this.radius
}
draw(ctx) {
// Base circle
ctx.beginPath()
ctx.arc(this.baseX, this.baseY, this.radius, 0, Math.PI * 2)
ctx.fillStyle = 'rgba(255, 255, 255, 0.2)'
ctx.fill()
// Knob
const knobX = this.baseX + this.dx * this.radius
const knobY = this.baseY + this.dy * this.radius
ctx.beginPath()
ctx.arc(knobX, knobY, this.knobRadius, 0, Math.PI * 2)
ctx.fillStyle = 'rgba(255, 255, 255, 0.5)'
ctx.fill()
}
getInput() {
return { x: this.dx, y: this.dy }
}
}5) Touch buttons
class TouchButton {
constructor(x, y, radius, label) {
this.x = x
this.y = y
this.radius = radius
this.label = label
this.pressed = false
this.touchId = null
}
contains(px, py) {
return Math.hypot(px - this.x, py - this.y) < this.radius
}
handleTouchStart(touch) {
if (this.contains(touch.clientX, touch.clientY)) {
this.pressed = true
this.touchId = touch.identifier
}
}
handleTouchEnd(touch) {
if (touch.identifier === this.touchId) {
this.pressed = false
this.touchId = null
}
}
draw(ctx) {
ctx.beginPath()
ctx.arc(this.x, this.y, this.radius, 0, Math.PI * 2)
ctx.fillStyle = this.pressed ? 'rgba(255, 255, 255, 0.5)' : 'rgba(255, 255, 255, 0.2)'
ctx.fill()
ctx.font = 'bold 24px sans-serif'
ctx.fillStyle = '#fff'
ctx.textAlign = 'center'
ctx.textBaseline = 'middle'
ctx.fillText(this.label, this.x, this.y)
}
}6) Orientation handling
function checkOrientation() {
const isLandscape = window.innerWidth > window.innerHeight
if (!isLandscape) {
showRotatePrompt()
} else {
hideRotatePrompt()
resizeCanvas()
}
}
window.addEventListener('resize', checkOrientation)
window.addEventListener('orientationchange', checkOrientation)
function showRotatePrompt() {
document.getElementById('rotate-prompt').style.display = 'flex'
}
function hideRotatePrompt() {
document.getElementById('rotate-prompt').style.display = 'none'
}7) Performance optimization
// Reduce resolution on low-end devices
function getDeviceScale() {
const dpr = window.devicePixelRatio || 1
const isLowEnd = navigator.hardwareConcurrency <= 2
return isLowEnd ? Math.min(dpr, 1.5) : Math.min(dpr, 2)
}
// Throttle expensive operations
let lastHeavyUpdate = 0
function maybeDoHeavyWork(timestamp) {
if (timestamp - lastHeavyUpdate > 100) { // 10 fps for heavy stuff
doExpensiveCalculations()
lastHeavyUpdate = timestamp
}
}
// Use lower quality assets
function getAssetQuality() {
const memory = navigator.deviceMemory || 4
if (memory <= 2) return 'low'
if (memory <= 4) return 'medium'
return 'high'
}8) Battery considerations
// Check battery and reduce effects
async function checkBattery() {
if ('getBattery' in navigator) {
const battery = await navigator.getBattery()
if (battery.level < 0.2 && !battery.charging) {
enableBatterySaver()
}
battery.addEventListener('levelchange', () => {
if (battery.level < 0.2 && !battery.charging) {
enableBatterySaver()
}
})
}
}
function enableBatterySaver() {
game.particleCount = Math.floor(game.particleCount * 0.5)
game.targetFps = 30
console.log('Battery saver enabled')
}9) Fullscreen API
async function requestFullscreen() {
const elem = document.documentElement
try {
if (elem.requestFullscreen) {
await elem.requestFullscreen()
} else if (elem.webkitRequestFullscreen) {
await elem.webkitRequestFullscreen()
}
// Lock orientation
if (screen.orientation?.lock) {
await screen.orientation.lock('landscape')
}
} catch (err) {
console.warn('Fullscreen failed:', err)
}
}
// Call on user interaction
document.getElementById('play-btn').addEventListener('click', () => {
requestFullscreen()
startGame()
})एक बात ध्यान रखें: Fullscreen API आम elements के लिए iPhone पर काम नहीं करता. iOS Safari सिर्फ़ native video elements के लिए fullscreen देता है (non-standard webkitEnterFullscreen के ज़रिए), इसलिए documentElement.requestFullscreen() iPhone पर reject हो जाएगा. ऊपर दिया गया try/catch इसे आराम से संभाल लेता है, इसलिए अपने game को iPhone पर बिना असली fullscreen के भी खेलने लायक बनाएं और fullscreen को iPad, Android, और desktop पर एक enhancement के तौर पर मानें जहां यह सपोर्टेड है. screen.orientation.lock() कॉल की भी iPhone पर वही सीमा है, इसलिए इस पर निर्भर न रहें.
10) Testing checklist
ship करने से पहले:
- Touch responsiveness — असली devices पर test करें, सिर्फ़ emulators पर नहीं
- अलग-अलग screen sizes — फोन, टैबलेट, iPad Pro
- दोनों orientations — या एक पर lock करें
- Low-end devices — 2-3 साल पुराने फोन पर test करें
- धीमे networks — 3G simulation पर test करें
- Safari iOS — audio, fullscreen के साथ इसकी अपनी quirks हैं
- Chrome Android — सबसे आम browser
- Interruptions — फोन कॉल, notifications
- Memory pressure — background tab के बाद क्या यह recover होता है?
// Debug touch visualization
function drawTouchDebug(ctx) {
for (const [id, touch] of touches) {
ctx.beginPath()
ctx.arc(touch.x, touch.y, 30, 0, Math.PI * 2)
ctx.fillStyle = 'rgba(255, 0, 0, 0.5)'
ctx.fill()
ctx.fillStyle = '#fff'
ctx.fillText(id.toString(), touch.x, touch.y)
}
}संबंधित
- Game input handling
- Responsive game canvas
- PWA for offline games
- Web Audio API for games — iOS audio पाबंदियों को संभालना
- तेज़ी से लोड होने वाला web game ship करें — मोबाइल connections के लिए load times को optimize करना
- Iframe embedding games — मोबाइल-विशिष्ट embed बातें
बाहरी संसाधन
- MDN: Touch events — multi-touch handling
- MDN: Screen Orientation API — orientation lock करना
- MDN: Fullscreen API — मोबाइल पर fullscreen में जाना
- web.dev: Responsive design — responsive patterns जो game UI पर लागू होते हैं