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:
<!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
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:
<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:
// 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":
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:
// 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:
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:
// 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:
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:
// 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:
// Tidak memerlukan SDK, tetapi postMessage dapat digunakan untuk pencapaian
window.parent.postMessage({ type: 'itch-achievement', data: { id: 'first-win' }}, '*')Newgrounds:
// Sertakan SDK Newgrounds.io
const ngio = new Newgrounds.io.core('APP_ID', 'AES_KEY')
ngio.callComponent('Medal.unlock', { id: 12345 })Cinevva:
// 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
// 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
<!-- 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
- Rilis game web yang dimuat dengan cepat
- Game web yang ramah perangkat seluler
- Untuk kreator
- COOP/COEP dan SharedArrayBuffer — header lintas origin yang memengaruhi penyematan iframe
- Cara Meluncurkan Game Anda di itch.io — itch.io menyematkan game browser secara bawaan dalam iframe
Sumber Daya Eksternal
- MDN: elemen iframe — referensi lengkap untuk atribut iframe
- MDN: Permissions Policy — mengendalikan fitur yang dapat digunakan iframe
- MDN: API postMessage — berkomunikasi antara iframe dan induk