Skip to content

Praktik terbaik penyematan game dengan iframe

Banyak platform (Itch.io, Newgrounds, Cinevva, situs Anda sendiri) menyematkan game dalam iframe. Tutorial ini membahas cara membuat game Anda berfungsi dengan baik saat disematkan.

1) Penyiapan dasar agar dapat disematkan

Game Anda harus dapat berfungsi tanpa memerlukan kendali penuh atas halaman:

html
<!DOCTYPE html>
<html>
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <style>
    * { margin: 0; padding: 0; }
    html, body { width: 100%; height: 100%; overflow: hidden; }
    canvas { display: block; width: 100%; height: 100%; }
  </style>
</head>
<body>
  <canvas id="game"></canvas>
  <script src="game.js"></script>
</body>
</html>

2) Mendeteksi konteks iframe

js
const isEmbedded = window.self !== window.top

if (isEmbedded) {
  // Sesuaikan perilaku untuk konteks penyematan
  hideExternalLinks()
  adjustUIForSmallSize()
}

function hideExternalLinks() {
  document.querySelectorAll('a[target="_blank"]').forEach(link => {
    link.style.display = 'none'
  })
}

3) Mengizinkan izin iframe yang diperlukan

Halaman induk mengendalikan apa yang dapat dilakukan iframe Anda. Izin yang umum digunakan:

html
<iframe 
  src="https://game.example.com"
  allow="fullscreen; autoplay; gamepad; pointer-lock"
  sandbox="allow-scripts allow-same-origin allow-pointer-lock allow-popups"
></iframe>

Game Anda harus menangani izin yang tidak tersedia dengan baik:

js
// Periksa apakah mode layar penuh tersedia
const canFullscreen = document.fullscreenEnabled || document.webkitFullscreenEnabled

if (!canFullscreen) {
  document.getElementById('fullscreen-btn').style.display = 'none'
}

4) Mode layar penuh dari iframe

Mode layar penuh memerlukan atribut allow="fullscreen":

js
async function requestFullscreen() {
  const elem = document.documentElement
  
  try {
    if (elem.requestFullscreen) {
      await elem.requestFullscreen()
    } else if (elem.webkitRequestFullscreen) {
      await elem.webkitRequestFullscreen()
    }
  } catch (err) {
    // Mode layar penuh tidak diizinkan—tampilkan pesan
    showMessage('Mode layar penuh tidak tersedia saat disematkan')
  }
}

5) Berkomunikasi dengan halaman induk

Gunakan postMessage untuk komunikasi lintas origin yang aman:

Dalam game Anda:

js
// Kirim pesan ke induk
function notifyParent(type, data) {
  if (window.parent !== window) {
    window.parent.postMessage({ type, data, source: 'game' }, '*')
  }
}

// Terima pesan dari induk
window.addEventListener('message', (event) => {
  // Validasi origin jika diperlukan
  // if (event.origin !== 'https://trusted-host.com') return
  
  const { type, data } = event.data
  
  switch (type) {
    case 'pause':
      pauseGame()
      break
    case 'resume':
      resumeGame()
      break
    case 'setVolume':
      setVolume(data.volume)
      break
  }
})

// Beri tahu saat game siap
window.addEventListener('load', () => {
  notifyParent('ready', { width: 800, height: 600 })
})

// Beri tahu saat terjadi peristiwa dalam game
function onGameOver(score) {
  notifyParent('gameover', { score })
}

Dalam halaman induk:

js
const iframe = document.getElementById('game-iframe')

iframe.addEventListener('load', () => {
  // Dengarkan pesan dari game
  window.addEventListener('message', (event) => {
    if (event.source !== iframe.contentWindow) return
    if (event.data.source !== 'game') return
    
    const { type, data } = event.data
    
    if (type === 'ready') {
      console.log('Game siap:', data)
    }
    if (type === 'gameover') {
      showScoreModal(data.score)
    }
  })
})

// Kirim perintah ke game
function pauseGame() {
  iframe.contentWindow.postMessage({ type: 'pause' }, '*')
}

6) Menangani fokus

Iframe dapat kehilangan fokus sehingga input keyboard tidak berfungsi:

js
// Fokuskan canvas secara otomatis saat diklik
canvas.addEventListener('click', () => {
  canvas.focus()
})

// Jadikan canvas dapat menerima fokus
canvas.tabIndex = 1

// Tangani hilangnya fokus
window.addEventListener('blur', () => {
  // Atur ulang tombol yang sedang ditekan
  input.left = input.right = input.up = input.down = false
  
  if (isEmbedded) {
    // Jeda jika diinginkan
    // pauseGame()
  }
})

// Minta fokus dari induk
function requestFocus() {
  notifyParent('requestFocus', {})
}

7) Ukuran penyematan yang responsif

Tangani berbagai dimensi penyematan:

js
function handleResize() {
  const width = window.innerWidth
  const height = window.innerHeight
  
  // Sesuaikan UI berdasarkan ukuran
  if (width < 400 || height < 300) {
    enableCompactUI()
  } else {
    enableFullUI()
  }
  
  // Skalakan game dengan tepat
  resizeCanvas(width, height)
}

window.addEventListener('resize', handleResize)
handleResize()

8) Indikator pemuatan untuk penyematan

Tampilkan sesuatu dengan segera:

js
// Sisipkan langsung dalam HTML agar segera ditampilkan
const loadingHTML = `
  <div id="loading" style="
    position: fixed;
    inset: 0;
    display: flex;
    align-items: center;
    justify-content: center;
    background: #1a1a2e;
    color: #fff;
    font-family: sans-serif;
  ">
    <div>
      <div class="spinner"></div>
      <p>Memuat...</p>
    </div>
  </div>
`

// Hapus saat game siap
function hideLoading() {
  const loading = document.getElementById('loading')
  if (loading) {
    loading.style.opacity = '0'
    loading.style.transition = 'opacity 0.3s'
    setTimeout(() => loading.remove(), 300)
  }
}

9) SDK khusus platform

Beberapa platform memiliki API sendiri:

Itch.io:

js
// Tidak memerlukan SDK, tetapi postMessage dapat digunakan untuk pencapaian
window.parent.postMessage({ type: 'itch-achievement', data: { id: 'first-win' }}, '*')

Newgrounds:

js
// Sertakan SDK Newgrounds.io
const ngio = new Newgrounds.io.core('APP_ID', 'AES_KEY')
ngio.callComponent('Medal.unlock', { id: 12345 })

Cinevva:

js
// Beri sinyal bahwa game sudah siap
window.parent.postMessage({ type: 'cinevva:ready' }, '*')

// Beri sinyal bahwa game sudah dapat dimainkan
window.parent.postMessage({ type: 'cinevva:playable' }, '*')

10) Pertimbangan keamanan

js
// Validasi origin pesan untuk operasi sensitif
window.addEventListener('message', (event) => {
  const trustedOrigins = [
    'https://itch.io',
    'https://cinevva.com',
    'https://yoursite.com'
  ]
  
  if (!trustedOrigins.includes(event.origin)) {
    return // Abaikan pesan yang tidak tepercaya
  }
  
  // Proses pesan...
})

// Jangan mengekspos operasi sensitif melalui postMessage
// Hanya izinkan perintah yang masuk daftar izin
const allowedCommands = ['pause', 'resume', 'setVolume', 'mute']

window.addEventListener('message', (event) => {
  const { type } = event.data
  if (!allowedCommands.includes(type)) return
  
  // Tangani perintah...
})

Daftar periksa pengujian

  • Pengujian lokal: Gunakan server HTTP sederhana, bukan file://
  • Lintas origin: Uji dengan penyematan iframe yang sebenarnya
  • Izin: Uji dengan sandbox yang dibatasi
  • Fokus: Uji keyboard setelah mengeklik di luar iframe
  • Pengubahan ukuran: Uji pada berbagai ukuran penyematan
  • Perangkat seluler: Uji input sentuh dalam konteks penyematan
html
<!-- Halaman pengujian penyematan -->
<!DOCTYPE html>
<html>
<body style="background: #333; padding: 20px;">
  <h1 style="color: #fff;">Pengujian Penyematan</h1>
  <iframe 
    src="http://localhost:8000" 
    width="800" 
    height="600"
    allow="fullscreen; autoplay; gamepad"
  ></iframe>
</body>
</html>

Terkait

Sumber Daya Eksternal