Buenas prácticas para integrar juegos mediante iframe
Muchas plataformas (Itch.io, Newgrounds, Cinevva o tu propio sitio) integran juegos mediante iframes. Este tutorial explica cómo hacer que tu juego funcione correctamente cuando está integrado.
1) Configuración básica para la integración
Tu juego debe funcionar sin necesitar el control total de la página:
<!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) Detectar el contexto del iframe
const isEmbedded = window.self !== window.top
if (isEmbedded) {
// Adjust behavior for embedded context
hideExternalLinks()
adjustUIForSmallSize()
}
function hideExternalLinks() {
document.querySelectorAll('a[target="_blank"]').forEach(link => {
link.style.display = 'none'
})
}3) Permitir los permisos necesarios del iframe
La página principal controla lo que puede hacer tu iframe. Estos son algunos permisos habituales:
<iframe
src="https://game.example.com"
allow="fullscreen; autoplay; gamepad; pointer-lock"
sandbox="allow-scripts allow-same-origin allow-pointer-lock allow-popups"
></iframe>Tu juego debe gestionar correctamente la falta de permisos:
// Check if fullscreen is available
const canFullscreen = document.fullscreenEnabled || document.webkitFullscreenEnabled
if (!canFullscreen) {
document.getElementById('fullscreen-btn').style.display = 'none'
}4) Pantalla completa desde un iframe
La pantalla completa requiere el atributo 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) {
// Fullscreen not allowed - show message
showMessage('La pantalla completa no está disponible cuando el juego está integrado')
}
}5) Comunicación con la página principal
Usa postMessage para establecer una comunicación segura entre distintos orígenes:
En tu juego:
// Send message to parent
function notifyParent(type, data) {
if (window.parent !== window) {
window.parent.postMessage({ type, data, source: 'game' }, '*')
}
}
// Receive messages from parent
window.addEventListener('message', (event) => {
// Validate origin if needed
// 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
}
})
// Notify when game is ready
window.addEventListener('load', () => {
notifyParent('ready', { width: 800, height: 600 })
})
// Notify on game events
function onGameOver(score) {
notifyParent('gameover', { score })
}En la página principal:
const iframe = document.getElementById('game-iframe')
iframe.addEventListener('load', () => {
// Listen for messages from 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 ready:', data)
}
if (type === 'gameover') {
showScoreModal(data.score)
}
})
})
// Send commands to game
function pauseGame() {
iframe.contentWindow.postMessage({ type: 'pause' }, '*')
}6) Gestionar el foco
Los iframes pueden perder el foco e impedir que funcione la entrada del teclado:
// Auto-focus canvas when clicked
canvas.addEventListener('click', () => {
canvas.focus()
})
// Make canvas focusable
canvas.tabIndex = 1
// Handle focus loss
window.addEventListener('blur', () => {
// Reset held keys
input.left = input.right = input.up = input.down = false
if (isEmbedded) {
// Optionally pause
// pauseGame()
}
})
// Request focus from parent
function requestFocus() {
notifyParent('requestFocus', {})
}7) Tamaño adaptable del contenido integrado
Gestiona las distintas dimensiones de integración:
function handleResize() {
const width = window.innerWidth
const height = window.innerHeight
// Adjust UI based on size
if (width < 400 || height < 300) {
enableCompactUI()
} else {
enableFullUI()
}
// Scale game appropriately
resizeCanvas(width, height)
}
window.addEventListener('resize', handleResize)
handleResize()8) Indicador de carga para contenido integrado
Muestra algo de inmediato:
// Inline in HTML for instant display
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>Cargando...</p>
</div>
</div>
`
// Remove when game is ready
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 específicos de cada plataforma
Algunas plataformas tienen sus propias API:
Itch.io:
// No SDK required, but can use postMessage for achievements
window.parent.postMessage({ type: 'itch-achievement', data: { id: 'first-win' }}, '*')Newgrounds:
// Include Newgrounds.io SDK
const ngio = new Newgrounds.io.core('APP_ID', 'AES_KEY')
ngio.callComponent('Medal.unlock', { id: 12345 })Cinevva:
// Signal game is ready
window.parent.postMessage({ type: 'cinevva:ready' }, '*')
// Signal playable state
window.parent.postMessage({ type: 'cinevva:playable' }, '*')10) Consideraciones de seguridad
// Validate message origins when sensitive
window.addEventListener('message', (event) => {
const trustedOrigins = [
'https://itch.io',
'https://cinevva.com',
'https://yoursite.com'
]
if (!trustedOrigins.includes(event.origin)) {
return // Ignore untrusted messages
}
// Process message...
})
// Don't expose sensitive operations via postMessage
// Only allow whitelisted commands
const allowedCommands = ['pause', 'resume', 'setVolume', 'mute']
window.addEventListener('message', (event) => {
const { type } = event.data
if (!allowedCommands.includes(type)) return
// Handle command...
})Lista de comprobación para las pruebas
- Pruebas locales: Usa un servidor HTTP sencillo, no
file:// - Entre distintos orígenes: Prueba el juego integrado en un iframe real
- Permisos: Haz pruebas con un entorno aislado restringido
- Foco: Prueba el teclado después de hacer clic fuera del iframe
- Cambio de tamaño: Prueba distintas dimensiones de integración
- Dispositivos móviles: Prueba los controles táctiles en un contexto integrado
<!-- Test embed page -->
<!DOCTYPE html>
<html>
<body style="background: #333; padding: 20px;">
<h1 style="color: #fff;">Prueba de integración</h1>
<iframe
src="http://localhost:8000"
width="800"
height="600"
allow="fullscreen; autoplay; gamepad"
></iframe>
</body>
</html>Contenido relacionado
- Publica un juego web que cargue rápido
- Juegos web adaptados a dispositivos móviles
- Para creadores
- COOP/COEP y SharedArrayBuffer — cabeceras entre distintos orígenes que afectan a la integración mediante iframe
- Cómo lanzar tu juego en itch.io — itch.io integra de forma nativa los juegos de navegador mediante iframes
Recursos externos
- MDN: elemento iframe — referencia completa de los atributos de iframe
- MDN: política de permisos — controla qué funciones pueden usar los iframes
- MDN: API postMessage — comunicación entre el iframe y la página principal