Skip to content

मोबाइल-फ्रेंडली web games

मोबाइल सबसे बड़ा gaming platform है. कुछ बदलावों से आपका web game फोन और टैबलेट पर शानदार तरीके से चलता है.

1) Viewport setup

zoom को रोकें और सही scaling सुनिश्चित करें:

html
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">

2) Fullscreen canvas

css
* {
  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

js
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

js
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

js
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

js
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

js
// 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

js
// 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

js
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 होता है?
js
// 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)
  }
}

संबंधित

बाहरी संसाधन